Переглянути джерело

feat(coupon-center): 接入 MySQL 数据源并补充建表脚本

- add coupon center core and dto models backed by MySQL with Redis cache
- replace controller mock data with database-driven activities response
- add coupon_center.sql with schema and seed inserts for deployment
- include existing repo-wide whitespace and line ending normalization changes
root 5 місяців тому
батько
коміт
3f0b0b7a79
100 змінених файлів з 19973 додано та 19563 видалено
  1. 29 29
      .dockerignore
  2. 231 231
      .editorconfig
  3. 29 29
      .gitignore
  4. 4 4
      Topsdk/Top/Defaultability/Domain/TaobaoKfcKeywordSearchKfcSearchResult.cs
  5. 5 5
      Topsdk/Top/Defaultability/Request/TaobaoKfcKeywordSearchRequest.cs
  6. 63 63
      Topsdk/Util/Constants.cs
  7. 271 271
      Topsdk/Util/TopFileItem.cs
  8. 226 226
      Topsdk/Util/TopUtils.cs
  9. 97 0
      coupon_center.sql
  10. 215 215
      molilian.api/Controllers/admin/AliyunController.cs
  11. 119 119
      molilian.api/Controllers/admin/ApiAccountController.cs
  12. 77 77
      molilian.api/Controllers/admin/ConfigController.cs
  13. 132 132
      molilian.api/Controllers/admin/CustomReportController.cs
  14. 243 243
      molilian.api/Controllers/admin/DataReportController.cs
  15. 395 395
      molilian.api/Controllers/admin/JdUnionController.cs
  16. 136 136
      molilian.api/Controllers/admin/MessageController.cs
  17. 130 130
      molilian.api/Controllers/admin/OverrideRulesController.cs
  18. 301 301
      molilian.api/Controllers/admin/PddUnionController.cs
  19. 103 103
      molilian.api/Controllers/admin/ProxyController.cs
  20. 135 135
      molilian.api/Controllers/admin/ReportController.cs
  21. 993 993
      molilian.api/Controllers/admin/TaobaoController.cs
  22. 99 99
      molilian.api/Controllers/admin/TaobaoOrderController.cs
  23. 73 73
      molilian.api/Controllers/admin/UserController.cs
  24. 162 162
      molilian.api/Controllers/public/ApiController.cs
  25. 28 0
      molilian.api/Controllers/public/CouponController.cs
  26. 93 93
      molilian.api/Controllers/public/CpsController.cs
  27. 185 185
      molilian.api/Controllers/public/H5Controller.cs
  28. 122 122
      molilian.api/Controllers/public/JDTaskController.cs
  29. 444 444
      molilian.api/Controllers/public/StressTestController.cs
  30. 1216 1216
      molilian.api/Controllers/public/TaskController.cs
  31. 508 508
      molilian.api/Controllers/public/TestController.cs
  32. 101 101
      molilian.api/Controllers/public/TkActivityController.cs
  33. 610 610
      molilian.api/Controllers/public/TkController.cs
  34. 71 71
      molilian.api/Controllers/public/TkEndpointController.cs
  35. 46 46
      molilian.api/Controllers/public/TracksController.cs
  36. 46 46
      molilian.api/Dockerfile
  37. 39 39
      molilian.api/Dockerfile.original
  38. 109 109
      molilian.api/Program.cs
  39. 17 17
      molilian.api/Properties/PublishProfiles/latest.pubxml
  40. 0 7
      molilian.api/Properties/PublishProfiles/latest.pubxml.user
  41. 14 14
      molilian.api/Properties/PublishProfiles/tester.pubxml
  42. 0 7
      molilian.api/Properties/PublishProfiles/tester.pubxml.user
  43. 80 80
      molilian.api/Properties/launchSettings.json
  44. 28 28
      molilian.api/docker-compose.yml
  45. 24 24
      molilian.api/molilian.api.csproj
  46. 9 9
      molilian.api/molilian.api.csproj.user
  47. 6 6
      molilian.api/molilian.api.http
  48. 30 30
      molilian.core/Authorization/AccessTokenCore.cs
  49. 19 19
      molilian.core/Authorization/AdminPermissionCore.cs
  50. 89 89
      molilian.core/Authorization/Provider/APIProvider.cs
  51. 142 142
      molilian.core/Authorization/Provider/AdminProvider.cs
  52. 140 140
      molilian.core/Authorization/Provider/UserProvider.cs
  53. 129 129
      molilian.core/Core/API/ApiAccountCore.cs
  54. 347 347
      molilian.core/Core/API/ApiReportCore.cs
  55. 83 83
      molilian.core/Core/ApiTokenCore.cs
  56. 43 43
      molilian.core/Core/CenterHub.cs
  57. 47 47
      molilian.core/Core/ConfigCore.cs
  58. 299 0
      molilian.core/Core/CouponCenterCore.cs
  59. 282 282
      molilian.core/Core/EndPointCore.cs
  60. 145 145
      molilian.core/Core/NotifyCore.cs
  61. 268 268
      molilian.core/Core/OverrideRuleCore.cs
  62. 191 191
      molilian.core/Core/ProxyNodesCore.cs
  63. 411 411
      molilian.core/Core/PushDataReport/Enum.cs
  64. 342 342
      molilian.core/Core/PushDataReport/PushDataReportCore.cs
  65. 187 187
      molilian.core/Core/PushDataReport/PushDataReportDTO.cs
  66. 102 102
      molilian.core/Core/PushDataReport/PushDataReportPush.cs
  67. 45 45
      molilian.core/Core/TracksCore.cs
  68. 407 407
      molilian.core/Core/admin/ChartDataCore.cs
  69. 49 49
      molilian.core/Core/admin/OperationLogCore.cs
  70. 505 505
      molilian.core/Core/aliyun/AliyunCore.cs
  71. 282 282
      molilian.core/Core/aliyun/AliyunPoolCore.cs
  72. 230 230
      molilian.core/Core/cps/CpsPoolCore.cs
  73. 105 105
      molilian.core/Core/cps/ElePoolCore.cs
  74. 101 101
      molilian.core/Core/cps/MeituanPoolCore.cs
  75. 509 509
      molilian.core/Core/cps/UnionCpsCore.cs
  76. 180 180
      molilian.core/Core/dataoke/DataokeCore.cs
  77. 76 76
      molilian.core/Core/douyin/PangolinPoolCore.cs
  78. 76 76
      molilian.core/Core/douyin/ReduPoolCore.cs
  79. 404 404
      molilian.core/Core/jd/JdPoolCore.cs
  80. 261 261
      molilian.core/Core/ks/KsPoolCore.cs
  81. 596 596
      molilian.core/Core/log/base.cs
  82. 204 204
      molilian.core/Core/log/coupon.cs
  83. 216 216
      molilian.core/Core/log/cps.cs
  84. 222 222
      molilian.core/Core/log/deeplink.cs
  85. 155 155
      molilian.core/Core/log/dy.cs
  86. 287 287
      molilian.core/Core/log/jd.cs
  87. 189 189
      molilian.core/Core/log/ks.cs
  88. 198 198
      molilian.core/Core/log/pdd.cs
  89. 198 198
      molilian.core/Core/log/promotion.cs
  90. 209 209
      molilian.core/Core/log/taobao.cs
  91. 183 183
      molilian.core/Core/log/tkActivity.cs
  92. 105 105
      molilian.core/Core/log/tool.cs
  93. 188 188
      molilian.core/Core/log/第三方旧接口.cs
  94. 933 933
      molilian.core/Core/pdd/PddPoolCore.cs
  95. 279 279
      molilian.core/Core/taoke/OrderTrackingCore.cs
  96. 236 236
      molilian.core/Core/taoke/RiskControlCore.cs
  97. 372 372
      molilian.core/Core/taoke/TaokeOpenCore.cs
  98. 148 148
      molilian.core/Core/taoke/TkActivityCore.cs
  99. 152 152
      molilian.core/Core/taoke/TkEndpointCore.cs
  100. 313 313
      molilian.core/Core/taoke/TkEndpointManager.cs

+ 29 - 29
.dockerignore

@@ -1,30 +1,30 @@
-**/.classpath
-**/.dockerignore
-**/.env
-**/.git
-**/.gitignore
-**/.project
-**/.settings
-**/.toolstarget
-**/.vs
-**/.vscode
-**/*.*proj.user
-**/*.dbmdl
-**/*.jfm
-**/azds.yaml
-**/bin
-**/charts
-**/docker-compose*
-**/Dockerfile*
-**/node_modules
-**/npm-debug.log
-**/obj
-**/secrets.dev.yaml
-**/values.dev.yaml
-LICENSE
-README.md
-!**/.gitignore
-!.git/HEAD
-!.git/config
-!.git/packed-refs
+**/.classpath
+**/.dockerignore
+**/.env
+**/.git
+**/.gitignore
+**/.project
+**/.settings
+**/.toolstarget
+**/.vs
+**/.vscode
+**/*.*proj.user
+**/*.dbmdl
+**/*.jfm
+**/azds.yaml
+**/bin
+**/charts
+**/docker-compose*
+**/Dockerfile*
+**/node_modules
+**/npm-debug.log
+**/obj
+**/secrets.dev.yaml
+**/values.dev.yaml
+LICENSE
+README.md
+!**/.gitignore
+!.git/HEAD
+!.git/config
+!.git/packed-refs
 !.git/refs/heads/**

+ 231 - 231
.editorconfig

@@ -1,231 +1,231 @@
-# 如果要从更高级别的目录继承 .editorconfig 设置,请删除以下行
-root = true
-
-# c# 文件
-[*.cs]
-
-#### Core EditorConfig 选项 ####
-
-# 缩进和间距
-indent_size = 4
-indent_style = space
-tab_width = 4
-
-# 新行首选项
-end_of_line = crlf
-insert_final_newline = false
-
-#### .NET 编码约定 ####
-
-# 组织 Using
-dotnet_separate_import_directive_groups = false
-dotnet_sort_system_directives_first = false
-file_header_template = unset
-
-# this. 和 Me. 首选项
-dotnet_style_qualification_for_event = false
-dotnet_style_qualification_for_field = false
-dotnet_style_qualification_for_method = false
-dotnet_style_qualification_for_property = false
-
-# 语言关键字与 bcl 类型首选项
-dotnet_style_predefined_type_for_locals_parameters_members = true
-dotnet_style_predefined_type_for_member_access = true
-
-# 括号首选项
-dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity
-dotnet_style_parentheses_in_other_binary_operators = always_for_clarity
-dotnet_style_parentheses_in_other_operators = never_if_unnecessary
-dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity
-
-# 修饰符首选项
-dotnet_style_require_accessibility_modifiers = for_non_interface_members
-
-# 表达式级首选项
-dotnet_style_coalesce_expression = true
-dotnet_style_collection_initializer = true
-dotnet_style_explicit_tuple_names = true
-dotnet_style_namespace_match_folder = true
-dotnet_style_null_propagation = true
-dotnet_style_object_initializer = true
-dotnet_style_operator_placement_when_wrapping = beginning_of_line
-dotnet_style_prefer_auto_properties = true
-dotnet_style_prefer_collection_expression = when_types_loosely_match
-dotnet_style_prefer_compound_assignment = true
-dotnet_style_prefer_conditional_expression_over_assignment = true
-dotnet_style_prefer_conditional_expression_over_return = true
-dotnet_style_prefer_foreach_explicit_cast_in_source = when_strongly_typed
-dotnet_style_prefer_inferred_anonymous_type_member_names = true
-dotnet_style_prefer_inferred_tuple_names = true
-dotnet_style_prefer_is_null_check_over_reference_equality_method = true
-dotnet_style_prefer_simplified_boolean_expressions = true
-dotnet_style_prefer_simplified_interpolation = true
-
-# 字段首选项
-dotnet_style_readonly_field = true
-
-# 参数首选项
-dotnet_code_quality_unused_parameters = all
-
-# 禁止显示首选项
-dotnet_remove_unnecessary_suppression_exclusions = none
-
-# 新行首选项
-dotnet_style_allow_multiple_blank_lines_experimental = true
-dotnet_style_allow_statement_immediately_after_block_experimental = true
-
-#### c# 编码约定 ####
-
-# var 首选项
-csharp_style_var_elsewhere = false
-csharp_style_var_for_built_in_types = false
-csharp_style_var_when_type_is_apparent = false
-
-# Expression-bodied 成员
-csharp_style_expression_bodied_accessors = true
-csharp_style_expression_bodied_constructors = false
-csharp_style_expression_bodied_indexers = true
-csharp_style_expression_bodied_lambdas = true
-csharp_style_expression_bodied_local_functions = false
-csharp_style_expression_bodied_methods = false
-csharp_style_expression_bodied_operators = false
-csharp_style_expression_bodied_properties = true
-
-# 模式匹配首选项
-csharp_style_pattern_matching_over_as_with_null_check = true
-csharp_style_pattern_matching_over_is_with_cast_check = true
-csharp_style_prefer_extended_property_pattern = true
-csharp_style_prefer_not_pattern = true
-csharp_style_prefer_pattern_matching = true
-csharp_style_prefer_switch_expression = true
-
-# Null 检查首选项
-csharp_style_conditional_delegate_call = true
-
-# 修饰符首选项
-csharp_prefer_static_local_function = true
-csharp_preferred_modifier_order = public,private,protected,internal,file,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,required,volatile,async
-csharp_style_prefer_readonly_struct = true
-csharp_style_prefer_readonly_struct_member = true
-
-# 代码块首选项
-csharp_prefer_braces = true
-csharp_prefer_simple_using_statement = true
-csharp_style_namespace_declarations = block_scoped
-csharp_style_prefer_method_group_conversion = true
-csharp_style_prefer_primary_constructors = true
-csharp_style_prefer_top_level_statements = true
-
-# 表达式级首选项
-csharp_prefer_simple_default_expression = true
-csharp_style_deconstructed_variable_declaration = true
-csharp_style_implicit_object_creation_when_type_is_apparent = true
-csharp_style_inlined_variable_declaration = true
-csharp_style_prefer_index_operator = true
-csharp_style_prefer_local_over_anonymous_function = true
-csharp_style_prefer_null_check_over_type_check = true
-csharp_style_prefer_range_operator = true
-csharp_style_prefer_tuple_swap = true
-csharp_style_prefer_utf8_string_literals = true
-csharp_style_throw_expression = true
-csharp_style_unused_value_assignment_preference = discard_variable
-csharp_style_unused_value_expression_statement_preference = discard_variable
-
-# "using" 指令首选项
-csharp_using_directive_placement = outside_namespace
-
-# 新行首选项
-csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true
-csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = true
-csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = true
-csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true
-csharp_style_allow_embedded_statements_on_same_line_experimental = true
-
-#### C# 格式规则 ####
-
-# 新行首选项
-csharp_new_line_before_catch = true
-csharp_new_line_before_else = true
-csharp_new_line_before_finally = true
-csharp_new_line_before_members_in_anonymous_types = true
-csharp_new_line_before_members_in_object_initializers = true
-csharp_new_line_before_open_brace = all
-csharp_new_line_between_query_expression_clauses = true
-
-# 缩进首选项
-csharp_indent_block_contents = true
-csharp_indent_braces = false
-csharp_indent_case_contents = true
-csharp_indent_case_contents_when_block = true
-csharp_indent_labels = no_change
-csharp_indent_switch_labels = true
-
-# 空格键首选项
-csharp_space_after_cast = false
-csharp_space_after_colon_in_inheritance_clause = true
-csharp_space_after_comma = true
-csharp_space_after_dot = false
-csharp_space_after_keywords_in_control_flow_statements = true
-csharp_space_after_semicolon_in_for_statement = true
-csharp_space_around_binary_operators = before_and_after
-csharp_space_around_declaration_statements = false
-csharp_space_before_colon_in_inheritance_clause = true
-csharp_space_before_comma = false
-csharp_space_before_dot = false
-csharp_space_before_open_square_brackets = false
-csharp_space_before_semicolon_in_for_statement = false
-csharp_space_between_empty_square_brackets = false
-csharp_space_between_method_call_empty_parameter_list_parentheses = false
-csharp_space_between_method_call_name_and_opening_parenthesis = false
-csharp_space_between_method_call_parameter_list_parentheses = false
-csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
-csharp_space_between_method_declaration_name_and_open_parenthesis = false
-csharp_space_between_method_declaration_parameter_list_parentheses = false
-csharp_space_between_parentheses = false
-csharp_space_between_square_brackets = false
-
-# 包装首选项
-csharp_preserve_single_line_blocks = true
-csharp_preserve_single_line_statements = true
-
-#### 命名样式 ####
-
-# 命名规则
-
-dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
-dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
-dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
-
-dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
-dotnet_naming_rule.types_should_be_pascal_case.symbols = types
-dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
-
-dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
-dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
-dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
-
-# 符号规范
-
-dotnet_naming_symbols.interface.applicable_kinds = interface
-dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
-dotnet_naming_symbols.interface.required_modifiers = 
-
-dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
-dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
-dotnet_naming_symbols.types.required_modifiers = 
-
-dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
-dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
-dotnet_naming_symbols.non_field_members.required_modifiers = 
-
-# 命名样式
-
-dotnet_naming_style.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.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
+# 如果要从更高级别的目录继承 .editorconfig 设置,请删除以下行
+root = true
+
+# c# 文件
+[*.cs]
+
+#### Core EditorConfig 选项 ####
+
+# 缩进和间距
+indent_size = 4
+indent_style = space
+tab_width = 4
+
+# 新行首选项
+end_of_line = crlf
+insert_final_newline = false
+
+#### .NET 编码约定 ####
+
+# 组织 Using
+dotnet_separate_import_directive_groups = false
+dotnet_sort_system_directives_first = false
+file_header_template = unset
+
+# this. 和 Me. 首选项
+dotnet_style_qualification_for_event = false
+dotnet_style_qualification_for_field = false
+dotnet_style_qualification_for_method = false
+dotnet_style_qualification_for_property = false
+
+# 语言关键字与 bcl 类型首选项
+dotnet_style_predefined_type_for_locals_parameters_members = true
+dotnet_style_predefined_type_for_member_access = true
+
+# 括号首选项
+dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity
+dotnet_style_parentheses_in_other_binary_operators = always_for_clarity
+dotnet_style_parentheses_in_other_operators = never_if_unnecessary
+dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity
+
+# 修饰符首选项
+dotnet_style_require_accessibility_modifiers = for_non_interface_members
+
+# 表达式级首选项
+dotnet_style_coalesce_expression = true
+dotnet_style_collection_initializer = true
+dotnet_style_explicit_tuple_names = true
+dotnet_style_namespace_match_folder = true
+dotnet_style_null_propagation = true
+dotnet_style_object_initializer = true
+dotnet_style_operator_placement_when_wrapping = beginning_of_line
+dotnet_style_prefer_auto_properties = true
+dotnet_style_prefer_collection_expression = when_types_loosely_match
+dotnet_style_prefer_compound_assignment = true
+dotnet_style_prefer_conditional_expression_over_assignment = true
+dotnet_style_prefer_conditional_expression_over_return = true
+dotnet_style_prefer_foreach_explicit_cast_in_source = when_strongly_typed
+dotnet_style_prefer_inferred_anonymous_type_member_names = true
+dotnet_style_prefer_inferred_tuple_names = true
+dotnet_style_prefer_is_null_check_over_reference_equality_method = true
+dotnet_style_prefer_simplified_boolean_expressions = true
+dotnet_style_prefer_simplified_interpolation = true
+
+# 字段首选项
+dotnet_style_readonly_field = true
+
+# 参数首选项
+dotnet_code_quality_unused_parameters = all
+
+# 禁止显示首选项
+dotnet_remove_unnecessary_suppression_exclusions = none
+
+# 新行首选项
+dotnet_style_allow_multiple_blank_lines_experimental = true
+dotnet_style_allow_statement_immediately_after_block_experimental = true
+
+#### c# 编码约定 ####
+
+# var 首选项
+csharp_style_var_elsewhere = false
+csharp_style_var_for_built_in_types = false
+csharp_style_var_when_type_is_apparent = false
+
+# Expression-bodied 成员
+csharp_style_expression_bodied_accessors = true
+csharp_style_expression_bodied_constructors = false
+csharp_style_expression_bodied_indexers = true
+csharp_style_expression_bodied_lambdas = true
+csharp_style_expression_bodied_local_functions = false
+csharp_style_expression_bodied_methods = false
+csharp_style_expression_bodied_operators = false
+csharp_style_expression_bodied_properties = true
+
+# 模式匹配首选项
+csharp_style_pattern_matching_over_as_with_null_check = true
+csharp_style_pattern_matching_over_is_with_cast_check = true
+csharp_style_prefer_extended_property_pattern = true
+csharp_style_prefer_not_pattern = true
+csharp_style_prefer_pattern_matching = true
+csharp_style_prefer_switch_expression = true
+
+# Null 检查首选项
+csharp_style_conditional_delegate_call = true
+
+# 修饰符首选项
+csharp_prefer_static_local_function = true
+csharp_preferred_modifier_order = public,private,protected,internal,file,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,required,volatile,async
+csharp_style_prefer_readonly_struct = true
+csharp_style_prefer_readonly_struct_member = true
+
+# 代码块首选项
+csharp_prefer_braces = true
+csharp_prefer_simple_using_statement = true
+csharp_style_namespace_declarations = block_scoped
+csharp_style_prefer_method_group_conversion = true
+csharp_style_prefer_primary_constructors = true
+csharp_style_prefer_top_level_statements = true
+
+# 表达式级首选项
+csharp_prefer_simple_default_expression = true
+csharp_style_deconstructed_variable_declaration = true
+csharp_style_implicit_object_creation_when_type_is_apparent = true
+csharp_style_inlined_variable_declaration = true
+csharp_style_prefer_index_operator = true
+csharp_style_prefer_local_over_anonymous_function = true
+csharp_style_prefer_null_check_over_type_check = true
+csharp_style_prefer_range_operator = true
+csharp_style_prefer_tuple_swap = true
+csharp_style_prefer_utf8_string_literals = true
+csharp_style_throw_expression = true
+csharp_style_unused_value_assignment_preference = discard_variable
+csharp_style_unused_value_expression_statement_preference = discard_variable
+
+# "using" 指令首选项
+csharp_using_directive_placement = outside_namespace
+
+# 新行首选项
+csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true
+csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = true
+csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = true
+csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true
+csharp_style_allow_embedded_statements_on_same_line_experimental = true
+
+#### C# 格式规则 ####
+
+# 新行首选项
+csharp_new_line_before_catch = true
+csharp_new_line_before_else = true
+csharp_new_line_before_finally = true
+csharp_new_line_before_members_in_anonymous_types = true
+csharp_new_line_before_members_in_object_initializers = true
+csharp_new_line_before_open_brace = all
+csharp_new_line_between_query_expression_clauses = true
+
+# 缩进首选项
+csharp_indent_block_contents = true
+csharp_indent_braces = false
+csharp_indent_case_contents = true
+csharp_indent_case_contents_when_block = true
+csharp_indent_labels = no_change
+csharp_indent_switch_labels = true
+
+# 空格键首选项
+csharp_space_after_cast = false
+csharp_space_after_colon_in_inheritance_clause = true
+csharp_space_after_comma = true
+csharp_space_after_dot = false
+csharp_space_after_keywords_in_control_flow_statements = true
+csharp_space_after_semicolon_in_for_statement = true
+csharp_space_around_binary_operators = before_and_after
+csharp_space_around_declaration_statements = false
+csharp_space_before_colon_in_inheritance_clause = true
+csharp_space_before_comma = false
+csharp_space_before_dot = false
+csharp_space_before_open_square_brackets = false
+csharp_space_before_semicolon_in_for_statement = false
+csharp_space_between_empty_square_brackets = false
+csharp_space_between_method_call_empty_parameter_list_parentheses = false
+csharp_space_between_method_call_name_and_opening_parenthesis = false
+csharp_space_between_method_call_parameter_list_parentheses = false
+csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
+csharp_space_between_method_declaration_name_and_open_parenthesis = false
+csharp_space_between_method_declaration_parameter_list_parentheses = false
+csharp_space_between_parentheses = false
+csharp_space_between_square_brackets = false
+
+# 包装首选项
+csharp_preserve_single_line_blocks = true
+csharp_preserve_single_line_statements = true
+
+#### 命名样式 ####
+
+# 命名规则
+
+dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
+dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
+dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
+
+dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
+dotnet_naming_rule.types_should_be_pascal_case.symbols = types
+dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
+
+dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
+dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
+dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
+
+# 符号规范
+
+dotnet_naming_symbols.interface.applicable_kinds = interface
+dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.interface.required_modifiers = 
+
+dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
+dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.types.required_modifiers = 
+
+dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
+dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.non_field_members.required_modifiers = 
+
+# 命名样式
+
+dotnet_naming_style.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.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

+ 29 - 29
.gitignore

@@ -1,31 +1,31 @@
-# Logs
-logs
-*.log
-npm-debug.log*
-yarn-debug.log*
-yarn-error.log*
-pnpm-debug.log*
-lerna-debug.log*
-
-node_modules
-dist
-dist-ssr
-*.local
-stats.html
-
-# Editor directories and files
-.idea
-*.suo
-*.ntvs*
-*.njsproj
-*.sln
-*.sw?
-
-# Misc
-.DS_Store
-.vs
-**/bin/
-**/obj/
-**/obj/Debug/
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+stats.html
+
+# Editor directories and files
+.idea
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
+
+# Misc
+.DS_Store
+.vs
+**/bin/
+**/obj/
+**/obj/Debug/
 .claude/*
 .ace-tool/

+ 4 - 4
Topsdk/Top/Defaultability/Domain/TaobaoKfcKeywordSearchKfcSearchResult.cs

@@ -14,16 +14,16 @@ public class TaobaoKfcKeywordSearchKfcSearchResult
 
 
     /*
-        匹配到的关键词的等级,值为null,或为A、B、C、D。
-当匹配不到关键词时,值为null,否则值为A、B、C、D中的一个。
+        匹配到的关键词的等级,值为null,或为A、B、C、D。
+当匹配不到关键词时,值为null,否则值为A、B、C、D中的一个。
 A、B、C、D等级按严重程度从高至低排列。
     */
     private string? level;
 
 
     /*
-        过滤后的文本:
-当匹配到B等级的词时,文本中的关键词被替换为*号,content即为关键词替换后的文本;
+        过滤后的文本:
+当匹配到B等级的词时,文本中的关键词被替换为*号,content即为关键词替换后的文本;
 其他情况,content始终为null
     */
     private string? content;

+ 5 - 5
Topsdk/Top/Defaultability/Request/TaobaoKfcKeywordSearchRequest.cs

@@ -14,11 +14,11 @@ public class TaobaoKfcKeywordSearchRequest : AbstractTopApiRequest
 
 
     /*
-        应用点,分为一级应用点、二级应用点。其中一级应用点通常是指某一个系统或产品,比如淘宝的商品应用(taobao_auction);二级应用点,是指一级应用点下的具体的分类,比如商品标题(title)、商品描述(content)。不同的二级应用可以设置不同关键词。
-
-这里的apply参数是由一级应用点与二级应用点合起来的字符(一级应用点+"."+二级应用点),如taobao_auction.title。
-
-
+        应用点,分为一级应用点、二级应用点。其中一级应用点通常是指某一个系统或产品,比如淘宝的商品应用(taobao_auction);二级应用点,是指一级应用点下的具体的分类,比如商品标题(title)、商品描述(content)。不同的二级应用可以设置不同关键词。
+
+这里的apply参数是由一级应用点与二级应用点合起来的字符(一级应用点+"."+二级应用点),如taobao_auction.title。
+
+
 通常apply参数是不需要传递的。如有特殊需求(比如特殊的过滤需求,需要自己维护一套自己词库),需传递此参数。
             */
     private string? apply;

+ 63 - 63
Topsdk/Util/Constants.cs

@@ -1,63 +1,63 @@
-namespace Topsdk.Util
-{
-    public sealed class Constants
-    {
-        public const string CHARSET_UTF8 = "utf-8";
-
-        public const string DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
-
-        public const string SIGN_METHOD_MD5 = "md5";
-        public const string SIGN_METHOD_HMAC = "hmac";
-        public const string SIGN_METHOD_HMAC_SHA256 = "hmac-sha256";
-
-        public const string LOG_SPLIT = "^_^";
-        public const string LOG_FILE_NAME = "topsdk.log";
-
-        public const string ACCEPT_ENCODING = "Accept-Encoding";
-        public const string CONTENT_ENCODING = "Content-Encoding";
-        public const string CONTENT_ENCODING_GZIP = "gzip";
-
-        public const string ERROR_RESPONSE = "error_response";
-        public const string ERROR_CODE = "code";
-        public const string ERROR_MSG = "msg";
-
-        public const string QIMEN_CLOUD_ERROR_RESPONSE = "response";
-        public const string QIMEN_CLOUD_ERROR_CODE = "code";
-        public const string QIMEN_CLOUD_ERROR_MSG = "message";
-
-        public const string SDK_VERSION = "new_net_sdk-20250815";
-        public const string SDK_VERSION_CLUSTER = "top-sdk-net-cluster-20250815";
-
-        public const string APP_KEY = "app_key";
-        public const string FORMAT = "format";
-        public const string METHOD = "method";
-        public const string TIMESTAMP = "timestamp";
-        public const string VERSION = "v";
-        public const string SIGN = "sign";
-        public const string SIGN_METHOD = "sign_method";
-        public const string PARTNER_ID = "partner_id";
-        public const string SESSION = "session";
-        public const string FORMAT_XML = "xml";
-        public const string FORMAT_JSON = "json";
-        public const string SIMPLIFY = "simplify";
-        public const string TARGET_APP_KEY = "target_app_key";
-        public const string BOUNDARY = "boundary";
-        public const string BOUNDARY_RETURN = "boundary_return";
-
-        public const string QM_ROOT_TAG_REQ = "request";
-        public const string QM_ROOT_TAG_RSP = "response";
-        public const string QM_CUSTOMER_ID = "customerId";
-        public const string QM_CONTENT_TYPE = "text/xml;charset=utf-8";
-        public const string QM_TARGETAPPKEY = "targetAppkey";
-
-        public const string CTYPE_DEFAULT = "application/octet-stream";
-        public const string CTYPE_FORM_DATA = "application/x-www-form-urlencoded";
-        public const string CTYPE_FILE_UPLOAD = "multipart/form-data";
-        public const string CTYPE_TEXT_XML = "text/xml";
-        public const string CTYPE_TEXT_PLAIN = "text/plain";
-        public const string CTYPE_APPLICATION_XML = "application/xml";
-        public const string CTYPE_APP_JSON = "application/json";
-        public const int READ_BUFFER_SIZE = 1024 * 4;
-    }
-}
-
+namespace Topsdk.Util
+{
+    public sealed class Constants
+    {
+        public const string CHARSET_UTF8 = "utf-8";
+
+        public const string DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
+
+        public const string SIGN_METHOD_MD5 = "md5";
+        public const string SIGN_METHOD_HMAC = "hmac";
+        public const string SIGN_METHOD_HMAC_SHA256 = "hmac-sha256";
+
+        public const string LOG_SPLIT = "^_^";
+        public const string LOG_FILE_NAME = "topsdk.log";
+
+        public const string ACCEPT_ENCODING = "Accept-Encoding";
+        public const string CONTENT_ENCODING = "Content-Encoding";
+        public const string CONTENT_ENCODING_GZIP = "gzip";
+
+        public const string ERROR_RESPONSE = "error_response";
+        public const string ERROR_CODE = "code";
+        public const string ERROR_MSG = "msg";
+
+        public const string QIMEN_CLOUD_ERROR_RESPONSE = "response";
+        public const string QIMEN_CLOUD_ERROR_CODE = "code";
+        public const string QIMEN_CLOUD_ERROR_MSG = "message";
+
+        public const string SDK_VERSION = "new_net_sdk-20250815";
+        public const string SDK_VERSION_CLUSTER = "top-sdk-net-cluster-20250815";
+
+        public const string APP_KEY = "app_key";
+        public const string FORMAT = "format";
+        public const string METHOD = "method";
+        public const string TIMESTAMP = "timestamp";
+        public const string VERSION = "v";
+        public const string SIGN = "sign";
+        public const string SIGN_METHOD = "sign_method";
+        public const string PARTNER_ID = "partner_id";
+        public const string SESSION = "session";
+        public const string FORMAT_XML = "xml";
+        public const string FORMAT_JSON = "json";
+        public const string SIMPLIFY = "simplify";
+        public const string TARGET_APP_KEY = "target_app_key";
+        public const string BOUNDARY = "boundary";
+        public const string BOUNDARY_RETURN = "boundary_return";
+
+        public const string QM_ROOT_TAG_REQ = "request";
+        public const string QM_ROOT_TAG_RSP = "response";
+        public const string QM_CUSTOMER_ID = "customerId";
+        public const string QM_CONTENT_TYPE = "text/xml;charset=utf-8";
+        public const string QM_TARGETAPPKEY = "targetAppkey";
+
+        public const string CTYPE_DEFAULT = "application/octet-stream";
+        public const string CTYPE_FORM_DATA = "application/x-www-form-urlencoded";
+        public const string CTYPE_FILE_UPLOAD = "multipart/form-data";
+        public const string CTYPE_TEXT_XML = "text/xml";
+        public const string CTYPE_TEXT_PLAIN = "text/plain";
+        public const string CTYPE_APPLICATION_XML = "application/xml";
+        public const string CTYPE_APP_JSON = "application/json";
+        public const int READ_BUFFER_SIZE = 1024 * 4;
+    }
+}
+

+ 271 - 271
Topsdk/Util/TopFileItem.cs

@@ -1,271 +1,271 @@
-namespace Topsdk.Util
-{
-    /// <summary>
-    /// 文件元数据。
-    /// 可以使用以下几种构造方法:
-    /// 本地路径:new FileItem("C:/temp.jpg");
-    /// 本地文件:new FileItem(new FileInfo("C:/temp.jpg"));
-    /// 字节数组:new FileItem("abc.jpg", bytes);
-    /// 输入流:new FileItem("abc.jpg", stream);
-    /// </summary>
-    public class TopFileItem
-    {
-        private Contract contract;
-
-        /// <summary>
-        /// 基于本地文件的构造器。
-        /// </summary>
-        /// <param name="fileInfo">本地文件</param>
-        public TopFileItem(FileInfo fileInfo)
-        {
-            this.contract = new LocalContract(fileInfo);
-        }
-
-        /// <summary>
-        /// 基于本地文件全路径的构造器。
-        /// </summary>
-        /// <param name="filePath">本地文件全路径</param>
-        public TopFileItem(string filePath) : this(new FileInfo(filePath))
-        {
-        }
-
-        /// <summary>
-        /// 基于文件名和字节数组的构造器。
-        /// </summary>
-        /// <param name="fileName">文件名称(服务端持久化字节数组到磁盘时的文件名)</param>
-        /// <param name="content">文件字节数组</param>
-        public TopFileItem(string fileName, byte[] content) : this(fileName, content, null)
-        {
-        }
-
-        /// <summary>
-        /// 基于文件名、字节数组和媒体类型的构造器。
-        /// </summary>
-        /// <param name="fileName">文件名(服务端持久化字节数组到磁盘时的文件名)</param>
-        /// <param name="content">文件字字节数组</param>
-        /// <param name="mimeType">媒体类型</param>
-        public TopFileItem(string fileName, byte[] content, string mimeType)
-        {
-            this.contract = new ByteArrayContract(fileName, content, mimeType);
-        }
-
-        /// <summary>
-        /// 基于文件名和输入流的构造器。
-        /// </summary>
-        /// <param name="fileName">文件名称(服务端持久化输入流到磁盘时的文件名)</param>
-        /// <param name="content">文件输入流</param>
-        public TopFileItem(string fileName, Stream stream) : this(fileName, stream, null)
-        {
-        }
-
-        /// <summary>
-        /// 基于文件名、输入流和媒体类型的构造器。
-        /// </summary>
-        /// <param name="fileName">文件名(服务端持久化输入流到磁盘时的文件名)</param>
-        /// <param name="content">文件输入流</param>
-        /// <param name="mimeType">媒体类型</param>
-        public TopFileItem(string fileName, Stream stream, string mimeType)
-        {
-            this.contract = new StreamContract(fileName, stream, mimeType);
-        }
-
-        public bool IsValid()
-        {
-            return this.contract.IsValid();
-        }
-
-        public long GetFileLength()
-        {
-            return this.contract.GetFileLength();
-        }
-
-        public string GetFileName()
-        {
-            return this.contract.GetFileName();
-        }
-
-        public string GetMimeType()
-        {
-            return this.contract.GetMimeType();
-        }
-
-        public void Write(Stream output)
-        {
-            this.contract.Write(output);
-        }
-
-        public Stream GetStream()
-        {
-            return this.contract.GetStream();
-        }
-    }
-
-    internal interface Contract
-    {
-        bool IsValid();
-        string GetFileName();
-        string GetMimeType();
-        long GetFileLength();
-        void Write(Stream output);
-
-        Stream GetStream();
-    }
-
-    internal class LocalContract : Contract
-    {
-        private FileInfo fileInfo;
-
-        public LocalContract(FileInfo fileInfo)
-        {
-            this.fileInfo = fileInfo;
-        }
-
-        public long GetFileLength()
-        {
-            return this.fileInfo.Length;
-        }
-
-        public string GetFileName()
-        {
-            return this.fileInfo.Name;
-        }
-
-        public string GetMimeType()
-        {
-            return Constants.CTYPE_DEFAULT;
-        }
-
-        public bool IsValid()
-        {
-            return this.fileInfo != null && this.fileInfo.Exists;
-        }
-
-        public void Write(Stream output)
-        {
-            using (BufferedStream bfs = new BufferedStream(this.fileInfo.OpenRead()))
-            {
-                int n = 0;
-                byte[] buffer = new byte[Constants.READ_BUFFER_SIZE];
-                while ((n = bfs.Read(buffer, 0, buffer.Length)) > 0)
-                {
-                    output.Write(buffer, 0, n);
-                }
-            }
-        }
-
-        public Stream GetStream()
-        {
-            return new BufferedStream(this.fileInfo.OpenRead());
-        }
-    }
-
-    internal class ByteArrayContract : Contract
-    {
-        private string fileName;
-        private byte[] content;
-        private string mimeType;
-
-        public ByteArrayContract(string fileName, byte[] content, string mimeType)
-        {
-            this.fileName = fileName;
-            this.content = content;
-            this.mimeType = mimeType;
-        }
-
-        public bool IsValid()
-        {
-            return this.content != null && this.fileName != null;
-        }
-
-        public long GetFileLength()
-        {
-            return this.content.Length;
-        }
-
-        public string GetFileName()
-        {
-            return this.fileName;
-        }
-
-        public string GetMimeType()
-        {
-            if (string.IsNullOrEmpty(this.mimeType))
-            {
-                return Constants.CTYPE_DEFAULT;
-            }
-            else
-            {
-                return this.mimeType;
-            }
-        }
-
-        public void Write(Stream output)
-        {
-            output.Write(this.content, 0, this.content.Length);
-        }
-
-        public Stream GetStream()
-        {
-            return new MemoryStream(content);
-        }
-    }
-
-    internal class StreamContract : Contract
-    {
-        private string fileName;
-        private Stream stream;
-        private string mimeType;
-
-        public StreamContract(string fileName, Stream stream, string mimeType)
-        {
-            this.fileName = fileName;
-            this.stream = stream;
-            this.mimeType = mimeType;
-        }
-
-        public long GetFileLength()
-        {
-            return 0L;
-        }
-
-        public string GetFileName()
-        {
-            return this.fileName;
-        }
-
-        public string GetMimeType()
-        {
-            if (string.IsNullOrEmpty(mimeType))
-            {
-                return Constants.CTYPE_DEFAULT;
-            }
-            else
-            {
-                return this.mimeType;
-            }
-        }
-
-        public bool IsValid()
-        {
-            return this.stream != null && this.fileName != null;
-        }
-
-        public void Write(Stream output)
-        {
-            using (this.stream)
-            {
-                int n = 0;
-                byte[] buffer = new byte[Constants.READ_BUFFER_SIZE];
-                while ((n = this.stream.Read(buffer, 0, buffer.Length)) > 0)
-                {
-                    output.Write(buffer, 0, n);
-                }
-            }
-        }
-
-        public Stream GetStream()
-        {
-            return stream;
-        }
-    }
-}
+namespace Topsdk.Util
+{
+    /// <summary>
+    /// 文件元数据。
+    /// 可以使用以下几种构造方法:
+    /// 本地路径:new FileItem("C:/temp.jpg");
+    /// 本地文件:new FileItem(new FileInfo("C:/temp.jpg"));
+    /// 字节数组:new FileItem("abc.jpg", bytes);
+    /// 输入流:new FileItem("abc.jpg", stream);
+    /// </summary>
+    public class TopFileItem
+    {
+        private Contract contract;
+
+        /// <summary>
+        /// 基于本地文件的构造器。
+        /// </summary>
+        /// <param name="fileInfo">本地文件</param>
+        public TopFileItem(FileInfo fileInfo)
+        {
+            this.contract = new LocalContract(fileInfo);
+        }
+
+        /// <summary>
+        /// 基于本地文件全路径的构造器。
+        /// </summary>
+        /// <param name="filePath">本地文件全路径</param>
+        public TopFileItem(string filePath) : this(new FileInfo(filePath))
+        {
+        }
+
+        /// <summary>
+        /// 基于文件名和字节数组的构造器。
+        /// </summary>
+        /// <param name="fileName">文件名称(服务端持久化字节数组到磁盘时的文件名)</param>
+        /// <param name="content">文件字节数组</param>
+        public TopFileItem(string fileName, byte[] content) : this(fileName, content, null)
+        {
+        }
+
+        /// <summary>
+        /// 基于文件名、字节数组和媒体类型的构造器。
+        /// </summary>
+        /// <param name="fileName">文件名(服务端持久化字节数组到磁盘时的文件名)</param>
+        /// <param name="content">文件字字节数组</param>
+        /// <param name="mimeType">媒体类型</param>
+        public TopFileItem(string fileName, byte[] content, string mimeType)
+        {
+            this.contract = new ByteArrayContract(fileName, content, mimeType);
+        }
+
+        /// <summary>
+        /// 基于文件名和输入流的构造器。
+        /// </summary>
+        /// <param name="fileName">文件名称(服务端持久化输入流到磁盘时的文件名)</param>
+        /// <param name="content">文件输入流</param>
+        public TopFileItem(string fileName, Stream stream) : this(fileName, stream, null)
+        {
+        }
+
+        /// <summary>
+        /// 基于文件名、输入流和媒体类型的构造器。
+        /// </summary>
+        /// <param name="fileName">文件名(服务端持久化输入流到磁盘时的文件名)</param>
+        /// <param name="content">文件输入流</param>
+        /// <param name="mimeType">媒体类型</param>
+        public TopFileItem(string fileName, Stream stream, string mimeType)
+        {
+            this.contract = new StreamContract(fileName, stream, mimeType);
+        }
+
+        public bool IsValid()
+        {
+            return this.contract.IsValid();
+        }
+
+        public long GetFileLength()
+        {
+            return this.contract.GetFileLength();
+        }
+
+        public string GetFileName()
+        {
+            return this.contract.GetFileName();
+        }
+
+        public string GetMimeType()
+        {
+            return this.contract.GetMimeType();
+        }
+
+        public void Write(Stream output)
+        {
+            this.contract.Write(output);
+        }
+
+        public Stream GetStream()
+        {
+            return this.contract.GetStream();
+        }
+    }
+
+    internal interface Contract
+    {
+        bool IsValid();
+        string GetFileName();
+        string GetMimeType();
+        long GetFileLength();
+        void Write(Stream output);
+
+        Stream GetStream();
+    }
+
+    internal class LocalContract : Contract
+    {
+        private FileInfo fileInfo;
+
+        public LocalContract(FileInfo fileInfo)
+        {
+            this.fileInfo = fileInfo;
+        }
+
+        public long GetFileLength()
+        {
+            return this.fileInfo.Length;
+        }
+
+        public string GetFileName()
+        {
+            return this.fileInfo.Name;
+        }
+
+        public string GetMimeType()
+        {
+            return Constants.CTYPE_DEFAULT;
+        }
+
+        public bool IsValid()
+        {
+            return this.fileInfo != null && this.fileInfo.Exists;
+        }
+
+        public void Write(Stream output)
+        {
+            using (BufferedStream bfs = new BufferedStream(this.fileInfo.OpenRead()))
+            {
+                int n = 0;
+                byte[] buffer = new byte[Constants.READ_BUFFER_SIZE];
+                while ((n = bfs.Read(buffer, 0, buffer.Length)) > 0)
+                {
+                    output.Write(buffer, 0, n);
+                }
+            }
+        }
+
+        public Stream GetStream()
+        {
+            return new BufferedStream(this.fileInfo.OpenRead());
+        }
+    }
+
+    internal class ByteArrayContract : Contract
+    {
+        private string fileName;
+        private byte[] content;
+        private string mimeType;
+
+        public ByteArrayContract(string fileName, byte[] content, string mimeType)
+        {
+            this.fileName = fileName;
+            this.content = content;
+            this.mimeType = mimeType;
+        }
+
+        public bool IsValid()
+        {
+            return this.content != null && this.fileName != null;
+        }
+
+        public long GetFileLength()
+        {
+            return this.content.Length;
+        }
+
+        public string GetFileName()
+        {
+            return this.fileName;
+        }
+
+        public string GetMimeType()
+        {
+            if (string.IsNullOrEmpty(this.mimeType))
+            {
+                return Constants.CTYPE_DEFAULT;
+            }
+            else
+            {
+                return this.mimeType;
+            }
+        }
+
+        public void Write(Stream output)
+        {
+            output.Write(this.content, 0, this.content.Length);
+        }
+
+        public Stream GetStream()
+        {
+            return new MemoryStream(content);
+        }
+    }
+
+    internal class StreamContract : Contract
+    {
+        private string fileName;
+        private Stream stream;
+        private string mimeType;
+
+        public StreamContract(string fileName, Stream stream, string mimeType)
+        {
+            this.fileName = fileName;
+            this.stream = stream;
+            this.mimeType = mimeType;
+        }
+
+        public long GetFileLength()
+        {
+            return 0L;
+        }
+
+        public string GetFileName()
+        {
+            return this.fileName;
+        }
+
+        public string GetMimeType()
+        {
+            if (string.IsNullOrEmpty(mimeType))
+            {
+                return Constants.CTYPE_DEFAULT;
+            }
+            else
+            {
+                return this.mimeType;
+            }
+        }
+
+        public bool IsValid()
+        {
+            return this.stream != null && this.fileName != null;
+        }
+
+        public void Write(Stream output)
+        {
+            using (this.stream)
+            {
+                int n = 0;
+                byte[] buffer = new byte[Constants.READ_BUFFER_SIZE];
+                while ((n = this.stream.Read(buffer, 0, buffer.Length)) > 0)
+                {
+                    output.Write(buffer, 0, n);
+                }
+            }
+        }
+
+        public Stream GetStream()
+        {
+            return stream;
+        }
+    }
+}

+ 226 - 226
Topsdk/Util/TopUtils.cs

@@ -1,226 +1,226 @@
-using System.Security.Cryptography;
-using System.Text;
-using System.Text.Json;
-using System.Text.Json.Nodes;
-using System.Text.Json.Serialization;
-
-namespace Topsdk.Util
-{
-    /// <summary>
-    /// TOP系统工具类。
-    /// </summary>
-    public abstract class TopUtils
-    {
-        private static JsonSerializerOptions options =  new JsonSerializerOptions()
-        {
-            WriteIndented = true,
-            NumberHandling = JsonNumberHandling.AllowReadingFromString, 
-            Converters = { new DateTimeJsonConverter("yyyy-MM-dd HH:mm:ss") }
-        };
-
-        /// <summary>
-        /// 给TOP请求签名。
-        /// </summary>
-        /// <param name="parameters">所有字符型的TOP请求参数</param>
-        /// <param name="secret">签名密钥</param>
-        /// <param name="signMethod">签名方法,可选值:md5, hmac</param>
-        /// <returns>签名</returns>
-        public static string SignTopRequest(IDictionary<string, string> parameters, string secret, string signMethod)
-        {
-            return SignTopRequestWithBody(parameters, null, secret, signMethod);
-        }
-        
-        /// <summary>
-        /// 给TOP请求签名。
-        /// </summary>
-        /// <param name="publicParam"></param>
-        /// <param name="requestParam"></param>
-        /// <param name="secret"></param>
-        /// <param name="signMethod"></param>
-        /// <returns></returns>
-        public static string SignTopRequest(IDictionary<string, string> publicParam,IDictionary<string,string> requestParam, string secret, string signMethod)
-        {
-            IDictionary<string, string> parameters = new Dictionary<string, string>(publicParam);
-            foreach (KeyValuePair<string,string> item in requestParam)
-            {
-                parameters.Add(item);
-            }
-            return SignTopRequest(parameters, secret, signMethod);
-        }
-
-        /// <summary>
-        /// 给TOP请求签名。
-        /// </summary>
-        /// <param name="parameters">所有字符型的TOP请求参数</param>
-        /// <param name="body">请求主体内容</param>
-        /// <param name="secret">签名密钥</param>
-        /// <param name="signMethod">签名方法,可选值:md5, hmac</param>
-        /// <returns>签名</returns>
-        public static string SignTopRequestWithBody(IDictionary<string, string> parameters, string body, string secret, string signMethod)
-        {
-            // 第一步:把字典按Key的字母顺序排序
-            IDictionary<string, string> sortedParams = new SortedDictionary<string, string>(parameters, StringComparer.Ordinal);
-
-            // 第二步:把所有参数名和参数值串在一起
-            StringBuilder query = new StringBuilder();
-            if(Constants.SIGN_METHOD_MD5.Equals(signMethod))
-            {
-                query.Append(secret);
-            }
-            foreach (KeyValuePair<string, string> kv in sortedParams)
-            {
-                if (!string.IsNullOrEmpty(kv.Key) && !string.IsNullOrEmpty(kv.Value))
-                {
-                    query.Append(kv.Key).Append(kv.Value);
-                }
-            }
-
-            // 第三步:把请求主体拼接在参数后面
-            if (!string.IsNullOrEmpty(body))
-            {
-                query.Append(body);
-            }
-
-            // 第四步:使用MD5/HMAC加密
-            byte[] bytes;
-            if (Constants.SIGN_METHOD_HMAC.Equals(signMethod))
-            {
-                HMACMD5 hmac = new HMACMD5(Encoding.UTF8.GetBytes(secret));
-                bytes = hmac.ComputeHash(Encoding.UTF8.GetBytes(query.ToString()));
-            }
-            else if (Constants.SIGN_METHOD_HMAC_SHA256.Equals(signMethod))
-            {
-                HMACSHA256 hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret));
-                bytes = hmac.ComputeHash(Encoding.UTF8.GetBytes(query.ToString()));
-            }
-            else
-            {
-                query.Append(secret);
-                MD5 md5 = MD5.Create();
-                bytes = md5.ComputeHash(Encoding.UTF8.GetBytes(query.ToString()));
-            }
-
-            // 第五步:把二进制转化为大写的十六进制
-            StringBuilder result = new StringBuilder();
-            for (int i = 0; i < bytes.Length; i++)
-            {
-                result.Append(bytes[i].ToString("X2"));
-            }
-
-            return result.ToString();
-        }
-
-        /// <summary>
-        /// 清除字典中值为空的项。
-        /// </summary>
-        /// <param name="dict">待清除的字典</param>
-        /// <returns>清除后的字典</returns>
-        public static IDictionary<string, T> CleanupDictionary<T>(IDictionary<string, T> dict)
-        {
-            IDictionary<string, T> newDict = new Dictionary<string, T>(dict.Count);
-
-            foreach (KeyValuePair<string, T> kv in dict)
-            {
-                if (kv.Value != null)
-                {
-                    newDict.Add(kv.Key, kv.Value);
-                }
-            }
-
-            return newDict;
-        }
-
-
-        /// <summary>
-        /// 获取从1970年1月1日到现在的毫秒总数。
-        /// </summary>
-        /// <returns>毫秒数</returns>
-        public static long GetCurrentTimeMillis()
-        {
-            return (long)DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1)).TotalMilliseconds;
-        }
-
-        /// <summary>
-        /// 复杂对象列表转json
-        /// </summary>
-        /// <param name="list"></param>
-        /// <returns></returns>
-        public static string ConvertStructList(object? list)
-        {
-            if (list == null)
-            {
-                return "[]";
-            }
-
-            if (list is string)
-            {
-                return (string)list;
-            }
-
-            return JsonSerializer.Serialize(list,options);
-
-        }
-        
-        public static string ConvertStruct(object? obj)
-        {
-            if (obj == null)
-            {
-                return "{}";
-            }
-
-            if (obj is string)
-            {
-                return (string)obj;
-            }
-
-            return JsonSerializer.Serialize(obj);
-        }
-
-        public static string ConvertBasicList<T>(IList<T> list)
-        {
-            if (list == null)
-            {
-                return "";
-            }
-            return string.Join(",",list);
-        }
-
-        public static string ConvertBasicType(object? obj)
-        {
-            if (obj == null)
-            {
-                return "";
-            }
-            if (obj is DateTime)
-            {
-                return ((DateTime)obj).ToString(Constants.DATE_TIME_FORMAT);
-            }
-
-            if (obj is bool)
-            {
-                return ((bool)obj).ToString().ToLower();
-            }
-
-            return obj.ToString();
-        }
-
-        public static T parseResultJson<T>(string jsonStr)
-        {
-            try
-            {
-                var jsonNode = JsonObject.Parse(jsonStr);
-                if (jsonNode[Constants.ERROR_RESPONSE] != null)
-                {
-                    return jsonNode[Constants.ERROR_RESPONSE].Deserialize<T>(options);
-                }
-                return jsonNode.Deserialize<T>(options);
-            }
-            catch (Exception e)
-            {
-                throw new TopException("ApiResult is not json", e);
-            }
-        }
-
-
-    }
-}
+using System.Security.Cryptography;
+using System.Text;
+using System.Text.Json;
+using System.Text.Json.Nodes;
+using System.Text.Json.Serialization;
+
+namespace Topsdk.Util
+{
+    /// <summary>
+    /// TOP系统工具类。
+    /// </summary>
+    public abstract class TopUtils
+    {
+        private static JsonSerializerOptions options =  new JsonSerializerOptions()
+        {
+            WriteIndented = true,
+            NumberHandling = JsonNumberHandling.AllowReadingFromString, 
+            Converters = { new DateTimeJsonConverter("yyyy-MM-dd HH:mm:ss") }
+        };
+
+        /// <summary>
+        /// 给TOP请求签名。
+        /// </summary>
+        /// <param name="parameters">所有字符型的TOP请求参数</param>
+        /// <param name="secret">签名密钥</param>
+        /// <param name="signMethod">签名方法,可选值:md5, hmac</param>
+        /// <returns>签名</returns>
+        public static string SignTopRequest(IDictionary<string, string> parameters, string secret, string signMethod)
+        {
+            return SignTopRequestWithBody(parameters, null, secret, signMethod);
+        }
+        
+        /// <summary>
+        /// 给TOP请求签名。
+        /// </summary>
+        /// <param name="publicParam"></param>
+        /// <param name="requestParam"></param>
+        /// <param name="secret"></param>
+        /// <param name="signMethod"></param>
+        /// <returns></returns>
+        public static string SignTopRequest(IDictionary<string, string> publicParam,IDictionary<string,string> requestParam, string secret, string signMethod)
+        {
+            IDictionary<string, string> parameters = new Dictionary<string, string>(publicParam);
+            foreach (KeyValuePair<string,string> item in requestParam)
+            {
+                parameters.Add(item);
+            }
+            return SignTopRequest(parameters, secret, signMethod);
+        }
+
+        /// <summary>
+        /// 给TOP请求签名。
+        /// </summary>
+        /// <param name="parameters">所有字符型的TOP请求参数</param>
+        /// <param name="body">请求主体内容</param>
+        /// <param name="secret">签名密钥</param>
+        /// <param name="signMethod">签名方法,可选值:md5, hmac</param>
+        /// <returns>签名</returns>
+        public static string SignTopRequestWithBody(IDictionary<string, string> parameters, string body, string secret, string signMethod)
+        {
+            // 第一步:把字典按Key的字母顺序排序
+            IDictionary<string, string> sortedParams = new SortedDictionary<string, string>(parameters, StringComparer.Ordinal);
+
+            // 第二步:把所有参数名和参数值串在一起
+            StringBuilder query = new StringBuilder();
+            if(Constants.SIGN_METHOD_MD5.Equals(signMethod))
+            {
+                query.Append(secret);
+            }
+            foreach (KeyValuePair<string, string> kv in sortedParams)
+            {
+                if (!string.IsNullOrEmpty(kv.Key) && !string.IsNullOrEmpty(kv.Value))
+                {
+                    query.Append(kv.Key).Append(kv.Value);
+                }
+            }
+
+            // 第三步:把请求主体拼接在参数后面
+            if (!string.IsNullOrEmpty(body))
+            {
+                query.Append(body);
+            }
+
+            // 第四步:使用MD5/HMAC加密
+            byte[] bytes;
+            if (Constants.SIGN_METHOD_HMAC.Equals(signMethod))
+            {
+                HMACMD5 hmac = new HMACMD5(Encoding.UTF8.GetBytes(secret));
+                bytes = hmac.ComputeHash(Encoding.UTF8.GetBytes(query.ToString()));
+            }
+            else if (Constants.SIGN_METHOD_HMAC_SHA256.Equals(signMethod))
+            {
+                HMACSHA256 hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret));
+                bytes = hmac.ComputeHash(Encoding.UTF8.GetBytes(query.ToString()));
+            }
+            else
+            {
+                query.Append(secret);
+                MD5 md5 = MD5.Create();
+                bytes = md5.ComputeHash(Encoding.UTF8.GetBytes(query.ToString()));
+            }
+
+            // 第五步:把二进制转化为大写的十六进制
+            StringBuilder result = new StringBuilder();
+            for (int i = 0; i < bytes.Length; i++)
+            {
+                result.Append(bytes[i].ToString("X2"));
+            }
+
+            return result.ToString();
+        }
+
+        /// <summary>
+        /// 清除字典中值为空的项。
+        /// </summary>
+        /// <param name="dict">待清除的字典</param>
+        /// <returns>清除后的字典</returns>
+        public static IDictionary<string, T> CleanupDictionary<T>(IDictionary<string, T> dict)
+        {
+            IDictionary<string, T> newDict = new Dictionary<string, T>(dict.Count);
+
+            foreach (KeyValuePair<string, T> kv in dict)
+            {
+                if (kv.Value != null)
+                {
+                    newDict.Add(kv.Key, kv.Value);
+                }
+            }
+
+            return newDict;
+        }
+
+
+        /// <summary>
+        /// 获取从1970年1月1日到现在的毫秒总数。
+        /// </summary>
+        /// <returns>毫秒数</returns>
+        public static long GetCurrentTimeMillis()
+        {
+            return (long)DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1)).TotalMilliseconds;
+        }
+
+        /// <summary>
+        /// 复杂对象列表转json
+        /// </summary>
+        /// <param name="list"></param>
+        /// <returns></returns>
+        public static string ConvertStructList(object? list)
+        {
+            if (list == null)
+            {
+                return "[]";
+            }
+
+            if (list is string)
+            {
+                return (string)list;
+            }
+
+            return JsonSerializer.Serialize(list,options);
+
+        }
+        
+        public static string ConvertStruct(object? obj)
+        {
+            if (obj == null)
+            {
+                return "{}";
+            }
+
+            if (obj is string)
+            {
+                return (string)obj;
+            }
+
+            return JsonSerializer.Serialize(obj);
+        }
+
+        public static string ConvertBasicList<T>(IList<T> list)
+        {
+            if (list == null)
+            {
+                return "";
+            }
+            return string.Join(",",list);
+        }
+
+        public static string ConvertBasicType(object? obj)
+        {
+            if (obj == null)
+            {
+                return "";
+            }
+            if (obj is DateTime)
+            {
+                return ((DateTime)obj).ToString(Constants.DATE_TIME_FORMAT);
+            }
+
+            if (obj is bool)
+            {
+                return ((bool)obj).ToString().ToLower();
+            }
+
+            return obj.ToString();
+        }
+
+        public static T parseResultJson<T>(string jsonStr)
+        {
+            try
+            {
+                var jsonNode = JsonObject.Parse(jsonStr);
+                if (jsonNode[Constants.ERROR_RESPONSE] != null)
+                {
+                    return jsonNode[Constants.ERROR_RESPONSE].Deserialize<T>(options);
+                }
+                return jsonNode.Deserialize<T>(options);
+            }
+            catch (Exception e)
+            {
+                throw new TopException("ApiResult is not json", e);
+            }
+        }
+
+
+    }
+}

+ 97 - 0
coupon_center.sql

@@ -0,0 +1,97 @@
+CREATE TABLE `coupon_center_platforms` (
+  `id` varchar(64) NOT NULL COMMENT '平台编码,如 tb/vipshop/tb_flash',
+  `name` varchar(64) NOT NULL DEFAULT '' COMMENT '平台名称',
+  `icon_url` varchar(512) NOT NULL DEFAULT '' COMMENT '平台图标',
+  `sort` int NOT NULL DEFAULT 0 COMMENT '排序,越小越靠前',
+  `status` tinyint(1) NOT NULL DEFAULT 1 COMMENT '是否启用:1启用,0停用',
+  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
+  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+  PRIMARY KEY (`id`),
+  KEY `idx_status_sort` (`status`, `sort`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='领券中心平台表';
+
+CREATE TABLE `coupon_center_activities` (
+  `id` varchar(64) NOT NULL COMMENT '活动编码,如 act-tb-all',
+  `platform_id` varchar(64) NOT NULL DEFAULT '' COMMENT '所属平台ID',
+  `name` varchar(128) NOT NULL DEFAULT '' COMMENT '活动名称',
+  `short_desc` varchar(255) NOT NULL DEFAULT '' COMMENT '活动短描述',
+  `deeplink` varchar(1024) NOT NULL DEFAULT '' COMMENT '活动跳转链接,留空则自动拼 magicchain 链接',
+  `sort` int NOT NULL DEFAULT 0 COMMENT '排序,越小越靠前',
+  `status` tinyint(1) NOT NULL DEFAULT 1 COMMENT '是否启用:1启用,0停用',
+  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
+  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+  PRIMARY KEY (`id`),
+  KEY `idx_platform_status_sort` (`platform_id`, `status`, `sort`),
+  KEY `idx_status_sort` (`status`, `sort`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='领券中心活动表';
+
+CREATE TABLE `coupon_center_coupons` (
+  `id` varchar(64) NOT NULL COMMENT '券编码,如 cp-tb-max-666',
+  `activity_id` varchar(64) NOT NULL DEFAULT '' COMMENT '所属活动ID',
+  `value` varchar(64) NOT NULL DEFAULT '' COMMENT '券面额展示值',
+  `expire_time` varchar(64) NOT NULL DEFAULT '' COMMENT '过期时间展示文案',
+  `category` varchar(128) NOT NULL DEFAULT '' COMMENT '分类',
+  `threshold` varchar(128) NOT NULL DEFAULT '' COMMENT '门槛说明',
+  `coupon_status` varchar(32) NOT NULL DEFAULT 'available' COMMENT '业务状态,如 available/soldout/expired',
+  `sort` int NOT NULL DEFAULT 0 COMMENT '排序,越小越靠前',
+  `status` tinyint(1) NOT NULL DEFAULT 1 COMMENT '是否启用:1启用,0停用',
+  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
+  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+  PRIMARY KEY (`id`),
+  KEY `idx_activity_status_sort` (`activity_id`, `status`, `sort`),
+  KEY `idx_status_sort` (`status`, `sort`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='领券中心券表';
+
+INSERT INTO `coupon_center_platforms` (`id`, `name`, `icon_url`, `sort`, `status`)
+VALUES
+  ('tb', '淘宝', 'http://doc.molilian.com/images/taobao.webp', 10, 1),
+  ('vipshop', '唯品会', 'http://doc.molilian.com/images/tmall.webp', 20, 1),
+  ('tb_flash', '淘宝闪购', 'http://doc.molilian.com/images/shangou.webp', 30, 1)
+ON DUPLICATE KEY UPDATE
+  `name` = VALUES(`name`),
+  `icon_url` = VALUES(`icon_url`),
+  `sort` = VALUES(`sort`),
+  `status` = VALUES(`status`),
+  `update_time` = CURRENT_TIMESTAMP;
+
+INSERT INTO `coupon_center_activities` (`id`, `platform_id`, `name`, `short_desc`, `deeplink`, `sort`, `status`)
+VALUES
+  ('act-tb-all', 'tb', '淘宝领券中心', '全平台', 'magicchain://coupon-center/activity?activityId=act-tb-all', 10, 1),
+  ('act-tb-home', 'tb', '淘宝居家券场', '居家日用', 'magicchain://coupon-center/activity?activityId=act-tb-home', 20, 1),
+  ('act-vipshop-all', 'vipshop', '唯品会领券中心', '全平台', 'magicchain://coupon-center/activity?activityId=act-vipshop-all', 10, 1),
+  ('act-vipshop-beauty', 'vipshop', '唯品会美妆券场', '美妆个护', 'magicchain://coupon-center/activity?activityId=act-vipshop-beauty', 20, 1),
+  ('act-tb-flash-medicine', 'tb_flash', '淘宝闪购领券中心', '医药', 'magicchain://coupon-center/activity?activityId=act-tb-flash-medicine', 10, 1),
+  ('act-tb-flash-food', 'tb_flash', '淘宝闪购夜宵券场', '美食', 'magicchain://coupon-center/activity?activityId=act-tb-flash-food', 20, 1)
+ON DUPLICATE KEY UPDATE
+  `platform_id` = VALUES(`platform_id`),
+  `name` = VALUES(`name`),
+  `short_desc` = VALUES(`short_desc`),
+  `deeplink` = VALUES(`deeplink`),
+  `sort` = VALUES(`sort`),
+  `status` = VALUES(`status`),
+  `update_time` = CURRENT_TIMESTAMP;
+
+INSERT INTO `coupon_center_coupons` (`id`, `activity_id`, `value`, `expire_time`, `category`, `threshold`, `coupon_status`, `sort`, `status`)
+VALUES
+  ('cp-tb-max-666', 'act-tb-all', '最高666元', '3月31日23:59', '全平台', '无门槛', 'available', 10, 1),
+  ('cp-tb-88', 'act-tb-all', '88元', '3月25日23:59', '数码家电', '满599元减88元', 'available', 20, 1),
+  ('cp-tb-home-30', 'act-tb-home', '30元', '3月29日23:59', '居家', '满199元减30元', 'available', 10, 1),
+  ('cp-tb-home-15', 'act-tb-home', '15元', '3月24日23:59', '日用百货', '满99元减15元', 'available', 20, 1),
+  ('cp-vipshop-max-30', 'act-vipshop-all', '最高减30', '3月28日23:59', '全平台', '无门槛', 'available', 10, 1),
+  ('cp-vipshop-80', 'act-vipshop-all', '80元', '3月26日23:59', '服饰', '满500元减80元', 'available', 20, 1),
+  ('cp-vipshop-beauty-50', 'act-vipshop-beauty', '50元', '3月30日23:59', '美妆', '满299元减50元', 'available', 10, 1),
+  ('cp-vipshop-beauty-20', 'act-vipshop-beauty', '20元', '3月27日23:59', '个护', '满159元减20元', 'available', 20, 1),
+  ('cp-tb-flash-8', 'act-tb-flash-medicine', '8元', '3月22日23:59', '医药', '满8元减8元', 'available', 10, 1),
+  ('cp-tb-flash-20', 'act-tb-flash-medicine', '20元', '3月23日23:59', '保健', '满99元减20元', 'available', 20, 1),
+  ('cp-tb-flash-food-10', 'act-tb-flash-food', '10元', '3月21日23:59', '美食', '满39元减10元', 'available', 10, 1),
+  ('cp-tb-flash-food-5', 'act-tb-flash-food', '5元', '3月20日23:59', '夜宵', '满20元减5元', 'available', 20, 1)
+ON DUPLICATE KEY UPDATE
+  `activity_id` = VALUES(`activity_id`),
+  `value` = VALUES(`value`),
+  `expire_time` = VALUES(`expire_time`),
+  `category` = VALUES(`category`),
+  `threshold` = VALUES(`threshold`),
+  `coupon_status` = VALUES(`coupon_status`),
+  `sort` = VALUES(`sort`),
+  `status` = VALUES(`status`),
+  `update_time` = CURRENT_TIMESTAMP;

+ 215 - 215
molilian.api/Controllers/admin/AliyunController.cs

@@ -1,216 +1,216 @@
-using molilian.core;
-using dodohold.core;
-using Microsoft.AspNetCore.Mvc;
-using System.Text.Json;
-using static molilian.core.ChartDataCore;
-using TencentCloud.Soe.V20180724.Models;
-namespace molilian.api.Controllers
-{
-    [ApiController]
-    [MyAuthorize("admin")]
-    [Route("api/[controller]/[action]")]
-    public class AliyunController : ControllerBase
-    {
-        readonly IAuthorizationProvider provider = new AdminProvider();
-        protected IHttpContextAccessor _accessor;
-        public AliyunController(IHttpContextAccessor accessor)
-        {
-            _accessor = accessor;
-        }
-
-        [HttpPost]
-        public ActionResult account_list([FromBody] JsonElement form)
-        {
-            var token = provider.Get(_accessor.HttpContext);
-
-            int page = form.Read("current", 1);
-            int size = form.Read("pageSize", 10);
-            bool getTotal = form.Read("getTotal", true);
-
-            string sort = form.Read<string>("sort");
-            string order = form.Read<string>("order");
-
-            string filter = string.Empty;
-
-            var keyword = form.Read("keyword", string.Empty);
-
-            if (!string.IsNullOrEmpty(keyword))
-            {
-                filter += $" AND (name like @keyword OR username like @keyword OR company like @keyword OR mobile like @keyword)";
-                keyword = $"%{keyword}%";
-            }
-            filter = filter.StringTrimStart(" AND ");
-
-            //排序
-            string orderBy = "id DESC";
-            if (!string.IsNullOrEmpty(order))
-            {
-                order = "descending".Equals(order) ? "DESC" : "ASC";
-                orderBy = sort switch
-                {
-                    _ => $"{sort} {order}",
-                };
-            }
-
-            var result = new DBContext.Table("aliyun_pool")
-               .Where(filter, new { keyword })
-               .Page(size, page)
-               .Order(orderBy)
-               .PageList<AliyunPoolDTO>(getTotal);
-
-            foreach (var item in result.List)
-            {
-                item.ecs_list = AliyunPoolCore.EcsList(item);
-                item.accessKeyId = string.Empty;
-                item.accessKeySecret = string.Empty;
-            }
-            return new APIResult(new { data = result });
-        }
-
-
-
-        [HttpPost]
-        public async Task<ActionResult> update([FromBody] JsonElement form)
-        {
-            var clientIp = _accessor.HttpContext.GetUserIp();
-            var token = provider.Get(_accessor.HttpContext);
-
-            int id = form.Read<int>("id", 0);
-            string name = form.Read<string>("name", string.Empty);
-            string val = form.Read<string>("val", string.Empty);
-
-            if (id == 0)
-            {
-                return new APIResult(new { data = new { success = false, msg = "更新失败,请检查输入参数" } });
-            }
-
-            int result = 0;
-            switch (name)
-            {
-                case "status":
-                    bool bVal = val.Equals("True");
-                    result = new DBContext.Table("aliyun_pool")
-                           .Add(name, bVal)
-                           .Add("last_time", DateTime.Now)
-                           .Where("id=@id", new { id })
-                           .Update();
-                    break;
-                case "balance":
-                    var item = new DBContext.Table("aliyun_pool").Get<dynamic>("id=@id", new { id });
-                    if (string.IsNullOrEmpty(item.accessKeyId)) result = 0;
-                    decimal amout = 0;
-                    try
-                    {
-                        AliyunCore core = new AliyunCore(item.id);
-                        var response = core.QueryAccountBalance();
-                        var balance = response.Body.Data.AvailableAmount;
-                        if (decimal.TryParse(balance, out amout))
-                        {
-                            result = 1;
-                            AliyunPoolCore.UpdateDrawBalance(item.name, amout);
-                            if (item.balance != amout && amout <= item.balance_alert_threshold)
-                            {
-                                string message = $"【{item.company}】阿里云当前余额:{amout} 元";
-                                _ = NotifyCore.QYWeixinPushNotifyAsync("余额预警", message);
-                                NotifyCore.Notify(new NifyMessage
-                                {
-                                    message = message,
-                                    priority = NifyMessagePriority.high,
-                                    tags = ["red_circle"]
-                                });
-                            }
-                        }
-                    }
-                    catch (Exception ex)
-                    {
-                        _ = new LoggerLibrary("aliyun", "balance_error")
-                          .Info($"Account:{item.id}\tbalance:{amout}")
-                          .Info(ex.Message, ex.StackTrace)
-                          .SaveAsync();
-                    }
-                    break;
-                default:
-                    return new APIResult(new { data = new { success = false, msg = "更新失败,未授权操作" } });
-            }
-
-
-            bool success = result > 0;
-            if (success)
-            {
-                OperationLogCore.LogOperation(token.AccessKey, clientIp, $"aliyun_pool:{id}:{name}", string.Empty, val);
-            }
-            return new APIResult(new
-            {
-                data = new { success, msg = success ? "更新成功" : "更新失败,请检查输入信息" },
-            });
-        }
-
-
-        [HttpPost]
-        public async Task<ActionResult> ecs_list([FromBody] JsonElement form)
-        {
-            var token = provider.Get(_accessor.HttpContext);
-
-            int page = form.Read("current", 1);
-            int size = form.Read("pageSize", 10);
-            bool getTotal = form.Read("getTotal", true);
-
-            string sort = form.Read<string>("sort");
-            string order = form.Read<string>("order");
-
-            string filter = string.Empty;
-
-            var log_date = form.PathReadArray<string>("query_date[]");
-            var accountName = form.Read("accountName", string.Empty);
-            int channel = form.Read("channel", -1);
-
-            if (!string.IsNullOrEmpty(accountName))
-            {
-                filter += $" AND accountName=@accountName";
-            }
-            else
-            {
-                filter += $" AND accountName!='all'";
-            }
-            DateTime stime = DateTime.MinValue, etime = DateTime.MinValue;
-            if (log_date.Count == 2)
-            {
-                if (DateTime.TryParse(log_date[0], out stime) && DateTime.TryParse(log_date[1], out etime))
-                {
-                    etime = etime.AddDays(1).AddSeconds(-1);
-                    if (etime > DateTime.Now) etime = DateTime.Now;
-                    filter += $" AND log_date BETWEEN @stime AND @etime";
-                }
-            }
-            if (channel != -1)
-            {
-                filter += $" AND channel=@channel";
-            }
-            filter = filter.StringTrimStart(" AND ");
-
-            //排序
-            string orderBy = "id DESC";
-            if (!string.IsNullOrEmpty(order))
-            {
-                order = "descending".Equals(order) ? "DESC" : "ASC";
-                orderBy = sort switch
-                {
-                    _ => $"{sort} {order}",
-                };
-            }
-
-            using var conn = CenterHub.GetOpenConnection();
-            var result = new DBContext.Table(conn, "aliyun_ecs")
-                .Where(filter, new { channel, accountName, stime, etime })
-                .Page(size, page)
-                .Order(orderBy)
-                .PageList<AliyunEcsDTO>(getTotal);
-
-
-            return new APIResult(new { data = result });
-        }
-
-
-
-    }
+using molilian.core;
+using dodohold.core;
+using Microsoft.AspNetCore.Mvc;
+using System.Text.Json;
+using static molilian.core.ChartDataCore;
+using TencentCloud.Soe.V20180724.Models;
+namespace molilian.api.Controllers
+{
+    [ApiController]
+    [MyAuthorize("admin")]
+    [Route("api/[controller]/[action]")]
+    public class AliyunController : ControllerBase
+    {
+        readonly IAuthorizationProvider provider = new AdminProvider();
+        protected IHttpContextAccessor _accessor;
+        public AliyunController(IHttpContextAccessor accessor)
+        {
+            _accessor = accessor;
+        }
+
+        [HttpPost]
+        public ActionResult account_list([FromBody] JsonElement form)
+        {
+            var token = provider.Get(_accessor.HttpContext);
+
+            int page = form.Read("current", 1);
+            int size = form.Read("pageSize", 10);
+            bool getTotal = form.Read("getTotal", true);
+
+            string sort = form.Read<string>("sort");
+            string order = form.Read<string>("order");
+
+            string filter = string.Empty;
+
+            var keyword = form.Read("keyword", string.Empty);
+
+            if (!string.IsNullOrEmpty(keyword))
+            {
+                filter += $" AND (name like @keyword OR username like @keyword OR company like @keyword OR mobile like @keyword)";
+                keyword = $"%{keyword}%";
+            }
+            filter = filter.StringTrimStart(" AND ");
+
+            //排序
+            string orderBy = "id DESC";
+            if (!string.IsNullOrEmpty(order))
+            {
+                order = "descending".Equals(order) ? "DESC" : "ASC";
+                orderBy = sort switch
+                {
+                    _ => $"{sort} {order}",
+                };
+            }
+
+            var result = new DBContext.Table("aliyun_pool")
+               .Where(filter, new { keyword })
+               .Page(size, page)
+               .Order(orderBy)
+               .PageList<AliyunPoolDTO>(getTotal);
+
+            foreach (var item in result.List)
+            {
+                item.ecs_list = AliyunPoolCore.EcsList(item);
+                item.accessKeyId = string.Empty;
+                item.accessKeySecret = string.Empty;
+            }
+            return new APIResult(new { data = result });
+        }
+
+
+
+        [HttpPost]
+        public async Task<ActionResult> update([FromBody] JsonElement form)
+        {
+            var clientIp = _accessor.HttpContext.GetUserIp();
+            var token = provider.Get(_accessor.HttpContext);
+
+            int id = form.Read<int>("id", 0);
+            string name = form.Read<string>("name", string.Empty);
+            string val = form.Read<string>("val", string.Empty);
+
+            if (id == 0)
+            {
+                return new APIResult(new { data = new { success = false, msg = "更新失败,请检查输入参数" } });
+            }
+
+            int result = 0;
+            switch (name)
+            {
+                case "status":
+                    bool bVal = val.Equals("True");
+                    result = new DBContext.Table("aliyun_pool")
+                           .Add(name, bVal)
+                           .Add("last_time", DateTime.Now)
+                           .Where("id=@id", new { id })
+                           .Update();
+                    break;
+                case "balance":
+                    var item = new DBContext.Table("aliyun_pool").Get<dynamic>("id=@id", new { id });
+                    if (string.IsNullOrEmpty(item.accessKeyId)) result = 0;
+                    decimal amout = 0;
+                    try
+                    {
+                        AliyunCore core = new AliyunCore(item.id);
+                        var response = core.QueryAccountBalance();
+                        var balance = response.Body.Data.AvailableAmount;
+                        if (decimal.TryParse(balance, out amout))
+                        {
+                            result = 1;
+                            AliyunPoolCore.UpdateDrawBalance(item.name, amout);
+                            if (item.balance != amout && amout <= item.balance_alert_threshold)
+                            {
+                                string message = $"【{item.company}】阿里云当前余额:{amout} 元";
+                                _ = NotifyCore.QYWeixinPushNotifyAsync("余额预警", message);
+                                NotifyCore.Notify(new NifyMessage
+                                {
+                                    message = message,
+                                    priority = NifyMessagePriority.high,
+                                    tags = ["red_circle"]
+                                });
+                            }
+                        }
+                    }
+                    catch (Exception ex)
+                    {
+                        _ = new LoggerLibrary("aliyun", "balance_error")
+                          .Info($"Account:{item.id}\tbalance:{amout}")
+                          .Info(ex.Message, ex.StackTrace)
+                          .SaveAsync();
+                    }
+                    break;
+                default:
+                    return new APIResult(new { data = new { success = false, msg = "更新失败,未授权操作" } });
+            }
+
+
+            bool success = result > 0;
+            if (success)
+            {
+                OperationLogCore.LogOperation(token.AccessKey, clientIp, $"aliyun_pool:{id}:{name}", string.Empty, val);
+            }
+            return new APIResult(new
+            {
+                data = new { success, msg = success ? "更新成功" : "更新失败,请检查输入信息" },
+            });
+        }
+
+
+        [HttpPost]
+        public async Task<ActionResult> ecs_list([FromBody] JsonElement form)
+        {
+            var token = provider.Get(_accessor.HttpContext);
+
+            int page = form.Read("current", 1);
+            int size = form.Read("pageSize", 10);
+            bool getTotal = form.Read("getTotal", true);
+
+            string sort = form.Read<string>("sort");
+            string order = form.Read<string>("order");
+
+            string filter = string.Empty;
+
+            var log_date = form.PathReadArray<string>("query_date[]");
+            var accountName = form.Read("accountName", string.Empty);
+            int channel = form.Read("channel", -1);
+
+            if (!string.IsNullOrEmpty(accountName))
+            {
+                filter += $" AND accountName=@accountName";
+            }
+            else
+            {
+                filter += $" AND accountName!='all'";
+            }
+            DateTime stime = DateTime.MinValue, etime = DateTime.MinValue;
+            if (log_date.Count == 2)
+            {
+                if (DateTime.TryParse(log_date[0], out stime) && DateTime.TryParse(log_date[1], out etime))
+                {
+                    etime = etime.AddDays(1).AddSeconds(-1);
+                    if (etime > DateTime.Now) etime = DateTime.Now;
+                    filter += $" AND log_date BETWEEN @stime AND @etime";
+                }
+            }
+            if (channel != -1)
+            {
+                filter += $" AND channel=@channel";
+            }
+            filter = filter.StringTrimStart(" AND ");
+
+            //排序
+            string orderBy = "id DESC";
+            if (!string.IsNullOrEmpty(order))
+            {
+                order = "descending".Equals(order) ? "DESC" : "ASC";
+                orderBy = sort switch
+                {
+                    _ => $"{sort} {order}",
+                };
+            }
+
+            using var conn = CenterHub.GetOpenConnection();
+            var result = new DBContext.Table(conn, "aliyun_ecs")
+                .Where(filter, new { channel, accountName, stime, etime })
+                .Page(size, page)
+                .Order(orderBy)
+                .PageList<AliyunEcsDTO>(getTotal);
+
+
+            return new APIResult(new { data = result });
+        }
+
+
+
+    }
 }

+ 119 - 119
molilian.api/Controllers/admin/ApiAccountController.cs

@@ -1,120 +1,120 @@
-using molilian.core;
-using dodohold.core;
-using Microsoft.AspNetCore.Mvc;
-using System.Text.Json;
-using static molilian.core.ChartDataCore;
-using TencentCloud.Soe.V20180724.Models;
-using TencentCloud.Mrs.V20200910.Models;
-using System.Xml.Linq;
-using System.Security.Cryptography;
-using TencentCloud.Bi.V20220105.Models;
-namespace molilian.api.Controllers
-{
-    [ApiController]
-    [MyAuthorize("admin")]
-    [Route("api/[controller]/[action]")]
-    public class ApiAccountController : ControllerBase
-    {
-        readonly IAuthorizationProvider provider = new AdminProvider();
-        protected IHttpContextAccessor _accessor;
-        public ApiAccountController(IHttpContextAccessor accessor)
-        {
-            _accessor = accessor;
-        }
-
-        [HttpPost]
-        public ActionResult list([FromBody] JsonElement form)
-        {
-            var token = provider.Get(_accessor.HttpContext);
-
-            int page = form.Read("current", 1);
-            int size = form.Read("pageSize", 10);
-            bool getTotal = form.Read("getTotal", true);
-
-            string sort = form.Read<string>("sort");
-            string order = form.Read<string>("order");
-
-            string filter = string.Empty;
-
-            var keyword = form.Read("keyword", string.Empty);
-
-            if (!string.IsNullOrEmpty(keyword))
-            {
-                filter += $" AND (name like @keyword OR description like @keyword OR api_key like @keyword)";
-                keyword = $"%{keyword}%";
-            }
-            filter = filter.StringTrimStart(" AND ");
-
-            string orderBy = "id DESC";
-            if (!string.IsNullOrEmpty(order))
-            {
-                order = "descending".Equals(order) ? "DESC" : "ASC";
-                orderBy = sort switch
-                {
-                    _ => $"{sort} {order}",
-                };
-            }
-
-            var result = new DBContext.Table("api_account")
-               .Where(filter, new { keyword })
-               .Page(size, page)
-               .Order(orderBy)
-               .PageList<dynamic>(getTotal);
-
-            return new APIResult(new { data = result });
-        }
-
-
-
-        [HttpPost]
-        public async Task<ActionResult> update([FromBody] ApiAccountDTO data)
-        {
-            var clientIp = _accessor.HttpContext.GetUserIp();
-            var token = provider.Get(_accessor.HttpContext);
-
-            using var conn = DBContext.GetOpenConnection();
-            if (data.id == 0) return new APIResult(new { data = new { success = false, msg = "更新失败,请检查输入参数" } });
-
-            var item = new DBContext.Table(conn, "api_account").Get<ApiAccountDTO>("id=@id", new { data.id });
-            if (item == null) return new APIResult(new { data = new { success = false, msg = "更新失败,记录不存在" } });
-
-            data.last_time = DateTime.Now;
-            var result = ApiAccountCore.Update(data, conn);
-            bool success = result > 0;
-            if (success)
-            {
-
-                string desc = ObjectComparer.PrintCompareToString(item, data).Trim();
-                OperationLogCore.LogOperation(token.AccessKey, clientIp, $"api_account:{data.name}", item.Convert2Json(), desc);
-            }
-            return new APIResult(new
-            {
-                data = new { success, msg = success ? "更新成功" : "更新失败,请检查输入信息" },
-            });
-        }
-
-        [HttpPost]
-        public async Task<ActionResult> create([FromBody] ApiAccountDTO data)
-        {
-            var clientIp = _accessor.HttpContext.GetUserIp();
-            var token = provider.Get(_accessor.HttpContext);
-
-            data.create_time = DateTime.Now;
-            data.last_time = DateTime.Now;
-
-            using var conn = DBContext.GetOpenConnection();
-            var result = ApiAccountCore.Create(data, conn);
-            bool success = result > 0;
-            if (success)
-            {
-                OperationLogCore.LogOperation(token.AccessKey, clientIp, $"api_account:{data.name}", string.Empty, data.Convert2Json());
-            }
-            return new APIResult(new
-            {
-                data = new { success, msg = success ? "更新成功" : "更新失败,请检查输入信息" },
-            });
-        }
-
-
-    }
+using molilian.core;
+using dodohold.core;
+using Microsoft.AspNetCore.Mvc;
+using System.Text.Json;
+using static molilian.core.ChartDataCore;
+using TencentCloud.Soe.V20180724.Models;
+using TencentCloud.Mrs.V20200910.Models;
+using System.Xml.Linq;
+using System.Security.Cryptography;
+using TencentCloud.Bi.V20220105.Models;
+namespace molilian.api.Controllers
+{
+    [ApiController]
+    [MyAuthorize("admin")]
+    [Route("api/[controller]/[action]")]
+    public class ApiAccountController : ControllerBase
+    {
+        readonly IAuthorizationProvider provider = new AdminProvider();
+        protected IHttpContextAccessor _accessor;
+        public ApiAccountController(IHttpContextAccessor accessor)
+        {
+            _accessor = accessor;
+        }
+
+        [HttpPost]
+        public ActionResult list([FromBody] JsonElement form)
+        {
+            var token = provider.Get(_accessor.HttpContext);
+
+            int page = form.Read("current", 1);
+            int size = form.Read("pageSize", 10);
+            bool getTotal = form.Read("getTotal", true);
+
+            string sort = form.Read<string>("sort");
+            string order = form.Read<string>("order");
+
+            string filter = string.Empty;
+
+            var keyword = form.Read("keyword", string.Empty);
+
+            if (!string.IsNullOrEmpty(keyword))
+            {
+                filter += $" AND (name like @keyword OR description like @keyword OR api_key like @keyword)";
+                keyword = $"%{keyword}%";
+            }
+            filter = filter.StringTrimStart(" AND ");
+
+            string orderBy = "id DESC";
+            if (!string.IsNullOrEmpty(order))
+            {
+                order = "descending".Equals(order) ? "DESC" : "ASC";
+                orderBy = sort switch
+                {
+                    _ => $"{sort} {order}",
+                };
+            }
+
+            var result = new DBContext.Table("api_account")
+               .Where(filter, new { keyword })
+               .Page(size, page)
+               .Order(orderBy)
+               .PageList<dynamic>(getTotal);
+
+            return new APIResult(new { data = result });
+        }
+
+
+
+        [HttpPost]
+        public async Task<ActionResult> update([FromBody] ApiAccountDTO data)
+        {
+            var clientIp = _accessor.HttpContext.GetUserIp();
+            var token = provider.Get(_accessor.HttpContext);
+
+            using var conn = DBContext.GetOpenConnection();
+            if (data.id == 0) return new APIResult(new { data = new { success = false, msg = "更新失败,请检查输入参数" } });
+
+            var item = new DBContext.Table(conn, "api_account").Get<ApiAccountDTO>("id=@id", new { data.id });
+            if (item == null) return new APIResult(new { data = new { success = false, msg = "更新失败,记录不存在" } });
+
+            data.last_time = DateTime.Now;
+            var result = ApiAccountCore.Update(data, conn);
+            bool success = result > 0;
+            if (success)
+            {
+
+                string desc = ObjectComparer.PrintCompareToString(item, data).Trim();
+                OperationLogCore.LogOperation(token.AccessKey, clientIp, $"api_account:{data.name}", item.Convert2Json(), desc);
+            }
+            return new APIResult(new
+            {
+                data = new { success, msg = success ? "更新成功" : "更新失败,请检查输入信息" },
+            });
+        }
+
+        [HttpPost]
+        public async Task<ActionResult> create([FromBody] ApiAccountDTO data)
+        {
+            var clientIp = _accessor.HttpContext.GetUserIp();
+            var token = provider.Get(_accessor.HttpContext);
+
+            data.create_time = DateTime.Now;
+            data.last_time = DateTime.Now;
+
+            using var conn = DBContext.GetOpenConnection();
+            var result = ApiAccountCore.Create(data, conn);
+            bool success = result > 0;
+            if (success)
+            {
+                OperationLogCore.LogOperation(token.AccessKey, clientIp, $"api_account:{data.name}", string.Empty, data.Convert2Json());
+            }
+            return new APIResult(new
+            {
+                data = new { success, msg = success ? "更新成功" : "更新失败,请检查输入信息" },
+            });
+        }
+
+
+    }
 }

+ 77 - 77
molilian.api/Controllers/admin/ConfigController.cs

@@ -1,78 +1,78 @@
-using molilian.core;
-using dodohold.core;
-using Microsoft.AspNetCore.Mvc;
-using Org.BouncyCastle.Ocsp;
-using System.Text.Json;
-using System.Runtime.InteropServices;
-using TencentCloud.Scf.V20180416.Models;
-using Quartz.Impl.AdoJobStore.Common;
-using TencentCloud.Gaap.V20180529.Models;
-using System.Net;
-using TencentCloud.Ie.V20200304.Models;
-using TencentCloud.Soe.V20180724.Models;
-
-namespace molilian.api.Controllers
-{
-    [ApiController]
-    [MyAuthorize("admin")]
-    [Route("api/[controller]/[action]")]
-    public class ConfigController : ControllerBase
-    {
-        readonly IAuthorizationProvider provider = new AdminProvider();
-        protected IHttpContextAccessor _accessor;
-        public ConfigController(IHttpContextAccessor accessor)
-        {
-            _accessor = accessor;
-        }
-
-        [HttpGet]
-        public ActionResult get()
-        {
-            var data = new DBContext.Table("tk_config").Get<TkConfigDTO>("id>0", new { });
-            return new APIResult(new { data });
-        }
-
-
-        [HttpPost]
-        public async Task<ActionResult> Update([FromBody] TkConfigDTO form)
-        {
-            var clientIp = _accessor.HttpContext.GetUserIp();
-            var token = provider.Get(_accessor.HttpContext);
-
-            string content = "【3瓶】恩威万蓝莓叶黄素酯软糖儿童成人上班族如酸涩肿胀可服用,<z5TYWAblvTw >:// HU7709,打開/";
-            try
-            {
-                var arr = form.tk_whitelist_regular.Split('\n')
-                            .Where(line => !string.IsNullOrWhiteSpace(line))
-                            .ToList();
-
-                var arr2 = form.tk_blacklist_regular.Split('\n')
-                            .Where(line => !string.IsNullOrWhiteSpace(line))
-                            .ToList();
-                bool is_tao_token = AlimamaPlus.MatchRegexes(content, arr);
-                bool is_other = AlimamaPlus.MatchOtherInfo(content, arr2);
-            }
-            catch (Exception ex)
-            {
-                return new APIResult(new { msg = $"正则异常${ex.Message}", success = false });
-            }
-            using var conn = DBContext.GetOpenConnection();
-            var old = new DBContext.Table(conn, "tk_config").Get<TkConfigDTO>("id>0", new { });
-
-            int result = await conn.UpdateAsync<TkConfigDTO>(form);
-
-            var endpoint = EndPointCore.GetCouponAdmin();
-            if (endpoint != null && !string.IsNullOrEmpty(endpoint.db_server))
-            {
-                using var conn2 = EndPointCore.GetDbConnection(endpoint.db_server);
-                _ = await conn2.UpdateAsync<TkConfigDTO>(form);
-            }
-
-            string desc = ObjectComparer.PrintCompareToString(old, form).Trim();
-            OperationLogCore.LogOperation(token.AccessKey, clientIp, "config", old.Convert2Json(), desc);
-            EndPointCore.NotifyReload();
-            return new APIResult(new { msg = "更新成功", success = result > 0 });
-        }
-
-    }
+using molilian.core;
+using dodohold.core;
+using Microsoft.AspNetCore.Mvc;
+using Org.BouncyCastle.Ocsp;
+using System.Text.Json;
+using System.Runtime.InteropServices;
+using TencentCloud.Scf.V20180416.Models;
+using Quartz.Impl.AdoJobStore.Common;
+using TencentCloud.Gaap.V20180529.Models;
+using System.Net;
+using TencentCloud.Ie.V20200304.Models;
+using TencentCloud.Soe.V20180724.Models;
+
+namespace molilian.api.Controllers
+{
+    [ApiController]
+    [MyAuthorize("admin")]
+    [Route("api/[controller]/[action]")]
+    public class ConfigController : ControllerBase
+    {
+        readonly IAuthorizationProvider provider = new AdminProvider();
+        protected IHttpContextAccessor _accessor;
+        public ConfigController(IHttpContextAccessor accessor)
+        {
+            _accessor = accessor;
+        }
+
+        [HttpGet]
+        public ActionResult get()
+        {
+            var data = new DBContext.Table("tk_config").Get<TkConfigDTO>("id>0", new { });
+            return new APIResult(new { data });
+        }
+
+
+        [HttpPost]
+        public async Task<ActionResult> Update([FromBody] TkConfigDTO form)
+        {
+            var clientIp = _accessor.HttpContext.GetUserIp();
+            var token = provider.Get(_accessor.HttpContext);
+
+            string content = "【3瓶】恩威万蓝莓叶黄素酯软糖儿童成人上班族如酸涩肿胀可服用,<z5TYWAblvTw >:// HU7709,打開/";
+            try
+            {
+                var arr = form.tk_whitelist_regular.Split('\n')
+                            .Where(line => !string.IsNullOrWhiteSpace(line))
+                            .ToList();
+
+                var arr2 = form.tk_blacklist_regular.Split('\n')
+                            .Where(line => !string.IsNullOrWhiteSpace(line))
+                            .ToList();
+                bool is_tao_token = AlimamaPlus.MatchRegexes(content, arr);
+                bool is_other = AlimamaPlus.MatchOtherInfo(content, arr2);
+            }
+            catch (Exception ex)
+            {
+                return new APIResult(new { msg = $"正则异常${ex.Message}", success = false });
+            }
+            using var conn = DBContext.GetOpenConnection();
+            var old = new DBContext.Table(conn, "tk_config").Get<TkConfigDTO>("id>0", new { });
+
+            int result = await conn.UpdateAsync<TkConfigDTO>(form);
+
+            var endpoint = EndPointCore.GetCouponAdmin();
+            if (endpoint != null && !string.IsNullOrEmpty(endpoint.db_server))
+            {
+                using var conn2 = EndPointCore.GetDbConnection(endpoint.db_server);
+                _ = await conn2.UpdateAsync<TkConfigDTO>(form);
+            }
+
+            string desc = ObjectComparer.PrintCompareToString(old, form).Trim();
+            OperationLogCore.LogOperation(token.AccessKey, clientIp, "config", old.Convert2Json(), desc);
+            EndPointCore.NotifyReload();
+            return new APIResult(new { msg = "更新成功", success = result > 0 });
+        }
+
+    }
 }

+ 132 - 132
molilian.api/Controllers/admin/CustomReportController.cs

@@ -1,133 +1,133 @@
-using molilian.core;
-using dodohold.core;
-using Microsoft.AspNetCore.Mvc;
-using System.Text.Json;
-using static molilian.core.ChartDataCore;
-using TencentCloud.Soe.V20180724.Models;
-using TencentCloud.Mrs.V20200910.Models;
-using System.Xml.Linq;
-using System.Security.Cryptography;
-using TencentCloud.Bi.V20220105.Models;
-using static Microsoft.Extensions.Logging.EventSource.LoggingEventSource;
-using molilian.core.PushDataReport;
-using System.Threading;
-using OfficeOpenXml.DataValidation.Exceptions;
-using System.Text.RegularExpressions;
-namespace molilian.api.Controllers
-{
-    [ApiController]
-    [MyAuthorize("admin")]
-    [Route("api/[controller]/[action]")]
-    public class CustomReportController : ControllerBase
-    {
-        readonly IAuthorizationProvider provider = new AdminProvider();
-        protected IHttpContextAccessor _accessor;
-        public CustomReportController(IHttpContextAccessor accessor)
-        {
-            _accessor = accessor;
-        }
-
-        [HttpPost]
-        public ActionResult list([FromBody] JsonElement form)
-        {
-            var token = provider.Get(_accessor.HttpContext);
-
-            int page = form.Read("current", 1);
-            int size = form.Read("pageSize", 10);
-            bool getTotal = form.Read("getTotal", true);
-
-            string sort = form.Read<string>("sort");
-            string order = form.Read<string>("order");
-
-            string filter = string.Empty;
-
-            // 临时锁定所有口令推送数据
-            //string filter = "type<>1";
-
-            int type = form.Read("type", 0);
-            if (type > 0) filter += $" AND type = @type";
-
-
-            int status = form.Read("status", -1);
-            if (status != -1) filter += $" AND status = @status";
-
-            int is_settlement = form.Read("is_settlement", -1);
-            if (is_settlement != -1) filter += $" AND is_settlement = @is_settlement";
-
-
-            int platform = form.Read("platform", 0);
-            if (platform > 0) filter += $" AND platform = @platform";
-
-            var report_date = form.Read<DateTime>("report_date", DateTime.MinValue);
-            if (report_date != DateTime.MinValue) filter += $" AND report_date = @report_date";
-
-
-            filter = filter.StringTrimStart(" AND ");
-
-            string orderBy = "id DESC";
-            if (!string.IsNullOrEmpty(order))
-            {
-                order = "descending".Equals(order) ? "DESC" : "ASC";
-                orderBy = sort switch
-                {
-                    _ => $"{sort} {order}",
-                };
-            }
-
-            var result = new DBContext.Table("push_data_report")
-               .Where(filter, new { type, report_date, is_settlement, status, platform })
-               .Page(size, page)
-               .Order(orderBy)
-               .PageList<dynamic>(getTotal);
-
-            return new APIResult(new { data = result });
-        }
-
-        [HttpPost]
-        public ActionResult GetSqlTemplates([FromBody] JsonElement form)
-        {
-            var token = provider.Get(_accessor.HttpContext);
-
-            int page = form.Read("current", 1);
-            int size = form.Read("pageSize", 10);
-            bool getTotal = form.Read("getTotal", true);
-
-            string sort = form.Read<string>("sort");
-            string order = form.Read<string>("order");
-            string category = form.Read<string>("category");
-            string keyword = form.Read<string>("keyword");
-
-            string filter = "is_active = 1";
-
-            if (!string.IsNullOrEmpty(category))
-                filter += " AND category = @category";
-
-            if (!string.IsNullOrEmpty(keyword))
-                filter += " AND (name LIKE @keyword OR description LIKE @keyword)";
-
-            string orderBy = "id DESC";
-            if (!string.IsNullOrEmpty(order))
-            {
-                order = "descending".Equals(order) ? "DESC" : "ASC";
-                orderBy = sort switch
-                {
-                    _ => $"{sort} {order}",
-                };
-            }
-
-            var keywordParam = !string.IsNullOrEmpty(keyword) ? $"%{keyword}%" : "";
-
-            var result = new DBContext.Table("sql_templates")
-               .Where(filter, new { category, keyword = keywordParam })
-               .Page(size, page)
-               .Order(orderBy)
-               .PageList<dynamic>(getTotal);
-
-            return new APIResult(new { data = result });
-        }
-
-         
-
-
-    }
+using molilian.core;
+using dodohold.core;
+using Microsoft.AspNetCore.Mvc;
+using System.Text.Json;
+using static molilian.core.ChartDataCore;
+using TencentCloud.Soe.V20180724.Models;
+using TencentCloud.Mrs.V20200910.Models;
+using System.Xml.Linq;
+using System.Security.Cryptography;
+using TencentCloud.Bi.V20220105.Models;
+using static Microsoft.Extensions.Logging.EventSource.LoggingEventSource;
+using molilian.core.PushDataReport;
+using System.Threading;
+using OfficeOpenXml.DataValidation.Exceptions;
+using System.Text.RegularExpressions;
+namespace molilian.api.Controllers
+{
+    [ApiController]
+    [MyAuthorize("admin")]
+    [Route("api/[controller]/[action]")]
+    public class CustomReportController : ControllerBase
+    {
+        readonly IAuthorizationProvider provider = new AdminProvider();
+        protected IHttpContextAccessor _accessor;
+        public CustomReportController(IHttpContextAccessor accessor)
+        {
+            _accessor = accessor;
+        }
+
+        [HttpPost]
+        public ActionResult list([FromBody] JsonElement form)
+        {
+            var token = provider.Get(_accessor.HttpContext);
+
+            int page = form.Read("current", 1);
+            int size = form.Read("pageSize", 10);
+            bool getTotal = form.Read("getTotal", true);
+
+            string sort = form.Read<string>("sort");
+            string order = form.Read<string>("order");
+
+            string filter = string.Empty;
+
+            // 临时锁定所有口令推送数据
+            //string filter = "type<>1";
+
+            int type = form.Read("type", 0);
+            if (type > 0) filter += $" AND type = @type";
+
+
+            int status = form.Read("status", -1);
+            if (status != -1) filter += $" AND status = @status";
+
+            int is_settlement = form.Read("is_settlement", -1);
+            if (is_settlement != -1) filter += $" AND is_settlement = @is_settlement";
+
+
+            int platform = form.Read("platform", 0);
+            if (platform > 0) filter += $" AND platform = @platform";
+
+            var report_date = form.Read<DateTime>("report_date", DateTime.MinValue);
+            if (report_date != DateTime.MinValue) filter += $" AND report_date = @report_date";
+
+
+            filter = filter.StringTrimStart(" AND ");
+
+            string orderBy = "id DESC";
+            if (!string.IsNullOrEmpty(order))
+            {
+                order = "descending".Equals(order) ? "DESC" : "ASC";
+                orderBy = sort switch
+                {
+                    _ => $"{sort} {order}",
+                };
+            }
+
+            var result = new DBContext.Table("push_data_report")
+               .Where(filter, new { type, report_date, is_settlement, status, platform })
+               .Page(size, page)
+               .Order(orderBy)
+               .PageList<dynamic>(getTotal);
+
+            return new APIResult(new { data = result });
+        }
+
+        [HttpPost]
+        public ActionResult GetSqlTemplates([FromBody] JsonElement form)
+        {
+            var token = provider.Get(_accessor.HttpContext);
+
+            int page = form.Read("current", 1);
+            int size = form.Read("pageSize", 10);
+            bool getTotal = form.Read("getTotal", true);
+
+            string sort = form.Read<string>("sort");
+            string order = form.Read<string>("order");
+            string category = form.Read<string>("category");
+            string keyword = form.Read<string>("keyword");
+
+            string filter = "is_active = 1";
+
+            if (!string.IsNullOrEmpty(category))
+                filter += " AND category = @category";
+
+            if (!string.IsNullOrEmpty(keyword))
+                filter += " AND (name LIKE @keyword OR description LIKE @keyword)";
+
+            string orderBy = "id DESC";
+            if (!string.IsNullOrEmpty(order))
+            {
+                order = "descending".Equals(order) ? "DESC" : "ASC";
+                orderBy = sort switch
+                {
+                    _ => $"{sort} {order}",
+                };
+            }
+
+            var keywordParam = !string.IsNullOrEmpty(keyword) ? $"%{keyword}%" : "";
+
+            var result = new DBContext.Table("sql_templates")
+               .Where(filter, new { category, keyword = keywordParam })
+               .Page(size, page)
+               .Order(orderBy)
+               .PageList<dynamic>(getTotal);
+
+            return new APIResult(new { data = result });
+        }
+
+         
+
+
+    }
 }

+ 243 - 243
molilian.api/Controllers/admin/DataReportController.cs

@@ -1,244 +1,244 @@
-using molilian.core;
-using dodohold.core;
-using Microsoft.AspNetCore.Mvc;
-using System.Text.Json;
-using static molilian.core.ChartDataCore;
-using TencentCloud.Soe.V20180724.Models;
-using TencentCloud.Mrs.V20200910.Models;
-using System.Xml.Linq;
-using System.Security.Cryptography;
-using TencentCloud.Bi.V20220105.Models;
-using static Microsoft.Extensions.Logging.EventSource.LoggingEventSource;
-using molilian.core.PushDataReport;
-using System.Threading;
-using OfficeOpenXml.DataValidation.Exceptions;
-namespace molilian.api.Controllers
-{
-    [ApiController]
-    [MyAuthorize("admin")]
-    [Route("api/[controller]/[action]")]
-    public class DataReportController : ControllerBase
-    {
-        readonly IAuthorizationProvider provider = new AdminProvider();
-        protected IHttpContextAccessor _accessor;
-        public DataReportController(IHttpContextAccessor accessor)
-        {
-            _accessor = accessor;
-        }
-
-        [HttpPost]
-        public ActionResult list([FromBody] JsonElement form)
-        {
-            var token = provider.Get(_accessor.HttpContext);
-
-            int page = form.Read("current", 1);
-            int size = form.Read("pageSize", 10);
-            bool getTotal = form.Read("getTotal", true);
-
-            string sort = form.Read<string>("sort");
-            string order = form.Read<string>("order");
-
-            string filter = string.Empty;
-
-            // 临时锁定所有口令推送数据
-            //string filter = "type<>1";
-
-            int type = form.Read("type", 0);
-            if (type > 0) filter += $" AND type = @type";
-
-
-            int status = form.Read("status", -1);
-            if (status != -1) filter += $" AND status = @status";
-
-            int is_settlement = form.Read("is_settlement", -1);
-            if (is_settlement != -1) filter += $" AND is_settlement = @is_settlement";
-
-
-            int platform = form.Read("platform", 0);
-            if (platform > 0) filter += $" AND platform = @platform";
-
-            var report_date = form.Read<DateTime>("report_date", DateTime.MinValue);
-            if (report_date != DateTime.MinValue) filter += $" AND report_date = @report_date";
-
-
-            filter = filter.StringTrimStart(" AND ");
-
-            string orderBy = "id DESC";
-            if (!string.IsNullOrEmpty(order))
-            {
-                order = "descending".Equals(order) ? "DESC" : "ASC";
-                orderBy = sort switch
-                {
-                    _ => $"{sort} {order}",
-                };
-            }
-
-            var result = new DBContext.Table("push_data_report")
-               .Where(filter, new { type, report_date, is_settlement, status, platform })
-               .Page(size, page)
-               .Order(orderBy)
-               .PageList<dynamic>(getTotal);
-
-            return new APIResult(new { data = result });
-        }
-
-
-
-        [HttpPost]
-        public async Task<ActionResult> push([FromBody] JsonElement form)
-        {
-            int id = form.Read<int>("id");
-
-            using var conn = DBContext.GetOpenConnection();
-            var item = new DBContext.Table(conn, "push_data_report").Get<PushDataReportDTO>("id=@id", new { id });
-            if (item == null) return new APIResult(new { data = new { success = false, msg = "PUSH失败,记录不存在" } });
-
-            // 临时锁定所有口令推送数据
-            //if (item.type == 1) return new APIResult(new { data = new { success = false, msg = "PUSH失败,类型错误" } });
-
-            PushDataReportPush plus = new PushDataReportPush();
-
-            item.request_id = Guid.NewGuid().ToString();
-            var response = await plus.PushReportData(item);
-
-            bool success = response.code == 200;
-            if (!success)
-            {
-                item.request_id = $"{item.request_id}:{response.message}";
-            }
-
-            new DBContext.Table("push_data_report")
-                        .Add("status", success)
-                        .Add("request_id", item.request_id)
-                        .Add("update_time", DateTime.Now)
-                        .Add("last_push_time", DateTime.Now)
-                        .Where("id=@id", new { id })
-                        .Update();
-
-            return new APIResult(new
-            {
-                data = new { success, msg = success ? "Push成功" : "Push失败" },
-            });
-        }
-
-
-        [HttpPost]
-        public async Task<ActionResult> delete([FromBody] JsonElement form)
-        {
-            int id = form.Read<int>("id");
-
-            using var conn = DBContext.GetOpenConnection();
-            var item = new DBContext.Table(conn, "push_data_report").Get<PushDataReportDTO>("id=@id", new { id });
-            if (item == null) return new APIResult(new { data = new { success = false, msg = "删除失败,记录不存在" } });
-
-            // 临时锁定所有口令推送数据
-            //if (item.type == 1) return new APIResult(new { data = new { success = false, msg = "删除失败,类型错误" } });
-
-            var result = new DBContext.Table("push_data_report").Delete("id=@id", new { id });
-            bool success = result > 0;
-            return new APIResult(new
-            {
-                data = new { success, msg = success ? "删除成功" : "删除失败" },
-            });
-        }
-
-
-        [HttpPost]
-        public async Task<ActionResult> save([FromBody] JsonElement from)
-        {
-            var clientIp = _accessor.HttpContext.GetUserIp();
-            var token = provider.Get(_accessor.HttpContext);
-
-            using var conn = DBContext.GetOpenConnection();
-            int result = 0;
-            bool success = false;
-
-            try
-            {
-                int id = from.Read("id", 0);
-                var report_date = from.Read<DateTime>("report_date", DateTime.Now).Date;
-                var platform = from.Read<int>("platform", 0);
-                var type = from.Read<int>("type", 0);
-
-                // 临时锁定所有口令推送数据
-                //if (type == 1) return new APIResult(new { data = new { success = false, msg = "更新失败,类型错误" } });
-
-                var update = new DBContext.Table("push_data_report");
-
-                //PushDataReportCore core = new();
-                //await core.FillCpsCouponData(report_date);
-
-                //return new APIResult(new
-                //{
-                //    data = new { success = false, msg = $"更新失败,test" },
-                //});
-
-                update.Fill(from);
-                update.Add("report_date", report_date);
-                update.Loader<bool>("is_settlement");
-                update.Loader<int>("status");
-                update.Loader<int>("type");
-                update.Loader<int>("sub_type");
-                update.Loader<int>("platform");
-                update.Loader<string>("platform_name");
-                update.Loader<string>("adId");
-                update.Loader<string>("adzoneId");
-                update.Loader<int>("distribution");
-                update.Loader<int>("req_count");
-                update.Loader<int>("dp_transition_succ_count");
-                update.Loader<int>("transition_succ_count");
-                update.Loader<int>("expose_count");
-                update.Loader<int>("expose_user_count");
-                update.Loader<int>("click_count");
-                update.Loader<int>("click_user_count");
-                update.Loader<int>("app_open_count");
-                update.Loader<int>("app_open_user_count");
-                update.Loader<int>("order_count");
-                update.Loader<int>("order_user_count");
-                update.Loader<decimal>("gmv");
-                update.Loader<decimal>("income");
-
-                if (id == 0)
-                {
-
-                    result = update.Create();
-                    success = result > 0;
-                    if (success)
-                    {
-                        OperationLogCore.LogOperation(token.AccessKey, clientIp, $"push_data_report:{report_date}:{platform}:{type}", from.Convert2Json(), "新增");
-                    }
-                }
-                else
-                {
-                    var item = new DBContext.Table(conn, "push_data_report").Get<PushDataReportDTO>("id=@id", new { id });
-                    if (item == null) return new APIResult(new { data = new { success = false, msg = "更新失败,记录不存在" } });
-
-                    result = update.Where("id=@id", new { id }).Update();
-
-                    success = result > 0;
-                    if (success)
-                    {
-                        var newItem = new DBContext.Table(conn, "push_data_report").Get<PushDataReportDTO>("id=@id", new { id });
-                        string desc = ObjectComparer.PrintCompareToString(item, newItem).Trim();
-                        OperationLogCore.LogOperation(token.AccessKey, clientIp, $"push_data_report:{report_date}:{platform}:{type}", item.Convert2Json(), desc);
-                    }
-                }
-            }
-            catch (Exception ex)
-            {
-                return new APIResult(new
-                {
-                    data = new { success = false, msg = $"更新失败,{ex.Message}" },
-                });
-            }
-            return new APIResult(new
-            {
-                data = new { success, msg = success ? "更新成功" : "更新失败,请检查输入信息" },
-            });
-        }
-
-
-
-
-    }
+using molilian.core;
+using dodohold.core;
+using Microsoft.AspNetCore.Mvc;
+using System.Text.Json;
+using static molilian.core.ChartDataCore;
+using TencentCloud.Soe.V20180724.Models;
+using TencentCloud.Mrs.V20200910.Models;
+using System.Xml.Linq;
+using System.Security.Cryptography;
+using TencentCloud.Bi.V20220105.Models;
+using static Microsoft.Extensions.Logging.EventSource.LoggingEventSource;
+using molilian.core.PushDataReport;
+using System.Threading;
+using OfficeOpenXml.DataValidation.Exceptions;
+namespace molilian.api.Controllers
+{
+    [ApiController]
+    [MyAuthorize("admin")]
+    [Route("api/[controller]/[action]")]
+    public class DataReportController : ControllerBase
+    {
+        readonly IAuthorizationProvider provider = new AdminProvider();
+        protected IHttpContextAccessor _accessor;
+        public DataReportController(IHttpContextAccessor accessor)
+        {
+            _accessor = accessor;
+        }
+
+        [HttpPost]
+        public ActionResult list([FromBody] JsonElement form)
+        {
+            var token = provider.Get(_accessor.HttpContext);
+
+            int page = form.Read("current", 1);
+            int size = form.Read("pageSize", 10);
+            bool getTotal = form.Read("getTotal", true);
+
+            string sort = form.Read<string>("sort");
+            string order = form.Read<string>("order");
+
+            string filter = string.Empty;
+
+            // 临时锁定所有口令推送数据
+            //string filter = "type<>1";
+
+            int type = form.Read("type", 0);
+            if (type > 0) filter += $" AND type = @type";
+
+
+            int status = form.Read("status", -1);
+            if (status != -1) filter += $" AND status = @status";
+
+            int is_settlement = form.Read("is_settlement", -1);
+            if (is_settlement != -1) filter += $" AND is_settlement = @is_settlement";
+
+
+            int platform = form.Read("platform", 0);
+            if (platform > 0) filter += $" AND platform = @platform";
+
+            var report_date = form.Read<DateTime>("report_date", DateTime.MinValue);
+            if (report_date != DateTime.MinValue) filter += $" AND report_date = @report_date";
+
+
+            filter = filter.StringTrimStart(" AND ");
+
+            string orderBy = "id DESC";
+            if (!string.IsNullOrEmpty(order))
+            {
+                order = "descending".Equals(order) ? "DESC" : "ASC";
+                orderBy = sort switch
+                {
+                    _ => $"{sort} {order}",
+                };
+            }
+
+            var result = new DBContext.Table("push_data_report")
+               .Where(filter, new { type, report_date, is_settlement, status, platform })
+               .Page(size, page)
+               .Order(orderBy)
+               .PageList<dynamic>(getTotal);
+
+            return new APIResult(new { data = result });
+        }
+
+
+
+        [HttpPost]
+        public async Task<ActionResult> push([FromBody] JsonElement form)
+        {
+            int id = form.Read<int>("id");
+
+            using var conn = DBContext.GetOpenConnection();
+            var item = new DBContext.Table(conn, "push_data_report").Get<PushDataReportDTO>("id=@id", new { id });
+            if (item == null) return new APIResult(new { data = new { success = false, msg = "PUSH失败,记录不存在" } });
+
+            // 临时锁定所有口令推送数据
+            //if (item.type == 1) return new APIResult(new { data = new { success = false, msg = "PUSH失败,类型错误" } });
+
+            PushDataReportPush plus = new PushDataReportPush();
+
+            item.request_id = Guid.NewGuid().ToString();
+            var response = await plus.PushReportData(item);
+
+            bool success = response.code == 200;
+            if (!success)
+            {
+                item.request_id = $"{item.request_id}:{response.message}";
+            }
+
+            new DBContext.Table("push_data_report")
+                        .Add("status", success)
+                        .Add("request_id", item.request_id)
+                        .Add("update_time", DateTime.Now)
+                        .Add("last_push_time", DateTime.Now)
+                        .Where("id=@id", new { id })
+                        .Update();
+
+            return new APIResult(new
+            {
+                data = new { success, msg = success ? "Push成功" : "Push失败" },
+            });
+        }
+
+
+        [HttpPost]
+        public async Task<ActionResult> delete([FromBody] JsonElement form)
+        {
+            int id = form.Read<int>("id");
+
+            using var conn = DBContext.GetOpenConnection();
+            var item = new DBContext.Table(conn, "push_data_report").Get<PushDataReportDTO>("id=@id", new { id });
+            if (item == null) return new APIResult(new { data = new { success = false, msg = "删除失败,记录不存在" } });
+
+            // 临时锁定所有口令推送数据
+            //if (item.type == 1) return new APIResult(new { data = new { success = false, msg = "删除失败,类型错误" } });
+
+            var result = new DBContext.Table("push_data_report").Delete("id=@id", new { id });
+            bool success = result > 0;
+            return new APIResult(new
+            {
+                data = new { success, msg = success ? "删除成功" : "删除失败" },
+            });
+        }
+
+
+        [HttpPost]
+        public async Task<ActionResult> save([FromBody] JsonElement from)
+        {
+            var clientIp = _accessor.HttpContext.GetUserIp();
+            var token = provider.Get(_accessor.HttpContext);
+
+            using var conn = DBContext.GetOpenConnection();
+            int result = 0;
+            bool success = false;
+
+            try
+            {
+                int id = from.Read("id", 0);
+                var report_date = from.Read<DateTime>("report_date", DateTime.Now).Date;
+                var platform = from.Read<int>("platform", 0);
+                var type = from.Read<int>("type", 0);
+
+                // 临时锁定所有口令推送数据
+                //if (type == 1) return new APIResult(new { data = new { success = false, msg = "更新失败,类型错误" } });
+
+                var update = new DBContext.Table("push_data_report");
+
+                //PushDataReportCore core = new();
+                //await core.FillCpsCouponData(report_date);
+
+                //return new APIResult(new
+                //{
+                //    data = new { success = false, msg = $"更新失败,test" },
+                //});
+
+                update.Fill(from);
+                update.Add("report_date", report_date);
+                update.Loader<bool>("is_settlement");
+                update.Loader<int>("status");
+                update.Loader<int>("type");
+                update.Loader<int>("sub_type");
+                update.Loader<int>("platform");
+                update.Loader<string>("platform_name");
+                update.Loader<string>("adId");
+                update.Loader<string>("adzoneId");
+                update.Loader<int>("distribution");
+                update.Loader<int>("req_count");
+                update.Loader<int>("dp_transition_succ_count");
+                update.Loader<int>("transition_succ_count");
+                update.Loader<int>("expose_count");
+                update.Loader<int>("expose_user_count");
+                update.Loader<int>("click_count");
+                update.Loader<int>("click_user_count");
+                update.Loader<int>("app_open_count");
+                update.Loader<int>("app_open_user_count");
+                update.Loader<int>("order_count");
+                update.Loader<int>("order_user_count");
+                update.Loader<decimal>("gmv");
+                update.Loader<decimal>("income");
+
+                if (id == 0)
+                {
+
+                    result = update.Create();
+                    success = result > 0;
+                    if (success)
+                    {
+                        OperationLogCore.LogOperation(token.AccessKey, clientIp, $"push_data_report:{report_date}:{platform}:{type}", from.Convert2Json(), "新增");
+                    }
+                }
+                else
+                {
+                    var item = new DBContext.Table(conn, "push_data_report").Get<PushDataReportDTO>("id=@id", new { id });
+                    if (item == null) return new APIResult(new { data = new { success = false, msg = "更新失败,记录不存在" } });
+
+                    result = update.Where("id=@id", new { id }).Update();
+
+                    success = result > 0;
+                    if (success)
+                    {
+                        var newItem = new DBContext.Table(conn, "push_data_report").Get<PushDataReportDTO>("id=@id", new { id });
+                        string desc = ObjectComparer.PrintCompareToString(item, newItem).Trim();
+                        OperationLogCore.LogOperation(token.AccessKey, clientIp, $"push_data_report:{report_date}:{platform}:{type}", item.Convert2Json(), desc);
+                    }
+                }
+            }
+            catch (Exception ex)
+            {
+                return new APIResult(new
+                {
+                    data = new { success = false, msg = $"更新失败,{ex.Message}" },
+                });
+            }
+            return new APIResult(new
+            {
+                data = new { success, msg = success ? "更新成功" : "更新失败,请检查输入信息" },
+            });
+        }
+
+
+
+
+    }
 }

+ 395 - 395
molilian.api/Controllers/admin/JdUnionController.cs

@@ -1,396 +1,396 @@
-using molilian.core;
-using dodohold.core;
-using Microsoft.AspNetCore.Mvc;
-using System.Text.Json;
-using System.Net;
-using static QRCoder.PayloadGenerator;
-using MySqlX.XDevAPI;
-using OfficeOpenXml.FormulaParsing.LexicalAnalysis;
-using WebSocketSharp.Net;
-namespace molilian.api.Controllers
-{
-
-
-    [ApiController]
-    [MyAuthorize("admin")]
-    [Route("api/[controller]/[action]")]
-    public class JdUnionController : ControllerBase
-    {
-        readonly IAuthorizationProvider provider = new AdminProvider();
-        protected IHttpContextAccessor _accessor;
-        public JdUnionController(IHttpContextAccessor accessor)
-        {
-            _accessor = accessor;
-        }
-
-
-
-        [HttpPost]
-        public ActionResult settle_bills_total([FromBody] JsonElement form)
-        {
-            int page = form.Read("current", 1);
-            int size = form.Read("pageSize", 10);
-            bool getTotal = form.Read("getTotal", true);
-            string sort = form.Read<string>("sort");
-            string order = form.Read<string>("order");
-
-            var report_date = form.PathReadArray<string>("query_date[]");
-
-            string filter = string.Empty;
-            DateTime stime = DateTime.MinValue, etime = DateTime.MinValue;
-            if (report_date.Count == 2)
-            {
-                if (DateTime.TryParse(report_date[0], out stime) && DateTime.TryParse(report_date[1], out etime))
-                {
-                    etime = etime.AddDays(1).AddSeconds(-1);
-                    filter += $" AND report_date BETWEEN @stime AND @etime";
-                }
-            }
-
-            filter = filter.StringTrimStart(" AND ");
-
-            //排序
-            string orderBy = "report_date DESC";
-            if (!string.IsNullOrEmpty(order))
-            {
-                order = "descending".Equals(order) ? "DESC" : "ASC";
-                orderBy = sort switch
-                {
-                    _ => $"{sort} {order}",
-                };
-            }
-            var result = new DBContext.Table("v_jd_spread_report")
-                .Where(filter, new { stime, etime })
-                .Page(size, page)
-                .Order(orderBy)
-                .PageList<JdSpreadReportDTO>(getTotal);
-
-            return new APIResult(new { data = result });
-        }
-
-
-        [HttpPost]
-        public ActionResult settle_bills([FromBody] JsonElement form)
-        {
-            int page = form.Read("current", 1);
-            int size = form.Read("pageSize", 10);
-            bool getTotal = form.Read("getTotal", true);
-            string sort = form.Read<string>("sort");
-            string order = form.Read<string>("order");
-            string name = form.Read<string>("keyword");
-
-            var report_date = form.PathReadArray<string>("query_date[]");
-
-            string filter = string.Empty;
-            if (!string.IsNullOrEmpty(name))
-            {
-                filter += $" AND accountId IN (SELECT ID FROM jd_pool WHERE company=@name)";
-            }
-            DateTime stime = DateTime.MinValue, etime = DateTime.MinValue;
-            if (report_date.Count == 2)
-            {
-                if (DateTime.TryParse(report_date[0], out stime) && DateTime.TryParse(report_date[1], out etime))
-                {
-                    etime = etime.AddDays(1).AddSeconds(-1);
-                    filter += $" AND report_date BETWEEN @stime AND @etime";
-                }
-            }
-
-            filter = filter.StringTrimStart(" AND ");
-
-            //排序
-            string orderBy = "report_date DESC";
-            if (!string.IsNullOrEmpty(order))
-            {
-                order = "descending".Equals(order) ? "DESC" : "ASC";
-                orderBy = sort switch
-                {
-                    _ => $"{sort} {order}",
-                };
-            }
-
-            var result = new DBContext.Table("jd_spread_report")
-                .Where(filter, new { name, stime, etime })
-                .Page(size, page)
-                .Order(orderBy)
-                .PageList<JdSpreadReportDTO>(getTotal);
-
-            return new APIResult(new { data = result });
-        }
-
-
-
-
-        [HttpPost]
-        public ActionResult estimate_comm_total([FromBody] JsonElement form)
-        {
-            int page = form.Read("current", 1);
-            int size = form.Read("pageSize", 10);
-            bool getTotal = form.Read("getTotal", true);
-            string sort = form.Read<string>("sort");
-            string order = form.Read<string>("order");
-
-            var report_date = form.PathReadArray<string>("query_date[]");
-
-            string filter = string.Empty;
-            DateTime stime = DateTime.MinValue, etime = DateTime.MinValue;
-            if (report_date.Count == 2)
-            {
-                if (DateTime.TryParse(report_date[0], out stime) && DateTime.TryParse(report_date[1], out etime))
-                {
-                    etime = etime.AddDays(1).AddSeconds(-1);
-                    filter += $" AND report_date BETWEEN @stime AND @etime";
-                }
-            }
-
-            filter = filter.StringTrimStart(" AND ");
-
-            //排序
-            string orderBy = "report_date DESC";
-            if (!string.IsNullOrEmpty(order))
-            {
-                order = "descending".Equals(order) ? "DESC" : "ASC";
-                orderBy = sort switch
-                {
-                    _ => $"{sort} {order}",
-                };
-            }
-            var result = new DBContext.Table("v_jd_estimate_comm")
-                .Where(filter, new { stime, etime })
-                .Page(size, page)
-                .Order(orderBy)
-                .PageList<JdEstimateCommDTO>(getTotal);
-
-            return new APIResult(new { data = result });
-        }
-
-
-        [HttpPost]
-        public ActionResult estimate_comm([FromBody] JsonElement form)
-        {
-            int page = form.Read("current", 1);
-            int size = form.Read("pageSize", 10);
-            bool getTotal = form.Read("getTotal", true);
-            string sort = form.Read<string>("sort");
-            string order = form.Read<string>("order");
-            string name = form.Read<string>("keyword");
-
-            var report_date = form.PathReadArray<string>("query_date[]");
-
-            string filter = string.Empty;
-            if (!string.IsNullOrEmpty(name))
-            {
-                filter += $" AND accountId IN (SELECT ID FROM jd_pool WHERE company=@name AND is_hide=0)";
-            }
-            else
-            {
-                filter += $" AND accountId IN (SELECT ID FROM jd_pool WHERE is_hide=0)";
-            }
-
-            DateTime stime = DateTime.MinValue, etime = DateTime.MinValue;
-            if (report_date.Count == 2)
-            {
-                if (DateTime.TryParse(report_date[0], out stime) && DateTime.TryParse(report_date[1], out etime))
-                {
-                    etime = etime.AddDays(1).AddSeconds(-1);
-                    filter += $" AND report_date BETWEEN @stime AND @etime";
-                }
-            }
-
-            filter = filter.StringTrimStart(" AND ");
-
-            //排序
-            string orderBy = "report_date DESC";
-            if (!string.IsNullOrEmpty(order))
-            {
-                order = "descending".Equals(order) ? "DESC" : "ASC";
-                orderBy = sort switch
-                {
-                    _ => $"{sort} {order}",
-                };
-            }
-
-            var result = new DBContext.Table("jd_estimate_comm")
-                .Where(filter, new { name, stime, etime })
-                .Page(size, page)
-                .Order(orderBy)
-                .PageList<JdEstimateCommDTO>(getTotal);
-
-            return new APIResult(new { data = result });
-        }
-
-
-
-        /// <summary>
-        /// 账号列表
-        /// </summary>
-        /// <param name="form"></param>
-        /// <returns></returns>
-        [HttpPost]
-        public ActionResult list([FromBody] JsonElement form)
-        {
-            var token = provider.Get(_accessor.HttpContext);
-
-            int page = form.Read("current", 1);
-            int size = form.Read("pageSize", 10);
-            string keyword = form.Read("name", string.Empty);
-            bool getTotal = form.Read("getTotal", true);
-            string _status = form.Read("status", string.Empty);
-            int status = _status switch
-            {
-                "online" => 1,
-                "offline" => 0,
-                _ => -1,
-            };
-            var lastTime = form.PathReadArray<string>("lastTime[]");
-
-            string sort = form.Read<string>("sort");
-            string order = form.Read<string>("order");
-
-            bool show_hide = form.Read("show_hide", false);
-            string filter = string.Empty;
-            if (!show_hide) filter = "is_hide=0";
-            if (!string.IsNullOrEmpty(keyword))
-            {
-                filter += $" AND (`name` LIKE @keyword OR `description` LIKE @keyword)";
-                keyword = $"%{keyword}%";
-            }
-            DateTime stime = DateTime.MinValue, etime = DateTime.MinValue;
-            if (lastTime.Count == 2)
-            {
-                if (DateTime.TryParse(lastTime[0], out stime) && DateTime.TryParse(lastTime[1], out etime))
-                {
-                    etime = etime.AddDays(1).AddSeconds(-1);
-                    filter += $" AND last_time BETWEEN @stime AND @etime";
-                }
-            }
-            if (status != -1)
-            {
-                filter += $" AND status=@status";
-
-            }
-
-            filter = filter.StringTrimStart(" AND ");
-
-            //排序
-            string orderBy = "id DESC";
-            if (!string.IsNullOrEmpty(order))
-            {
-                order = "descending".Equals(order) ? "DESC" : "ASC";
-                orderBy = sort switch
-                {
-                    //"num" => $"num {order}",
-                    _ => $"{sort} {order}",
-                };
-            }
-
-            using var conn = DBContext.GetOpenConnection();
-            var result = new DBContext.Table(conn, "jd_pool")
-                .Where(filter, new { keyword, status, stime, etime })
-                .Page(size, page)
-                .Order(orderBy)
-                .PageList<JdPoolDTO>(getTotal);
-
-            foreach (var item in result.List)
-            {
-                item.app_secret = string.Empty;
-                item.cookies = string.Empty;
-                item.riskCookie = RiskControlCore.GetRiskCookie($"jd:{item.id}");
-            }
-            return new APIResult(new { data = result });
-        }
-
-        [HttpPost]
-        public async Task<ActionResult> update([FromBody] JsonElement form)
-        {
-            var clientIp = _accessor.HttpContext.GetUserIp();
-            var token = provider.Get(_accessor.HttpContext);
-
-            int id = form.Read<int>("id", 0);
-            string name = form.Read<string>("name", string.Empty);
-            string val = form.Read<string>("val", string.Empty);
-
-            if (id == 0)
-            {
-                return new APIResult(new { data = new { success = false, msg = "更新失败,请检查输入的cookie" } });
-            }
-
-            int result;
-            switch (name)
-            {
-                case "enable_parse":
-                case "enable_coupon":
-                case "enable_sync_report":
-                case "enable_sync_order":
-                case "status":
-                    bool bVal = val.Equals("True");
-                    result = new DBContext.Table("jd_pool")
-                           .Add(name, bVal)
-                           .Add("last_time", DateTime.Now)
-                           .Where("id=@id", new { id })
-                           .Update();
-                    break;
-                case "time_range":
-                    int.TryParse(val, out int iVal);
-                    result = new DBContext.Table("jd_pool")
-                           .Add(name, iVal)
-                           .Add("last_time", DateTime.Now)
-                           .Where("id=@id", new { id })
-                           .Update();
-                    break;
-                default:
-                    return new APIResult(new { data = new { success = false, msg = "更新失败,未授权操作" } });
-            }
-            bool success = result > 0;
-            if (success)
-            {
-                OperationLogCore.LogOperation(token.AccessKey, clientIp, $"jd_pool:{id}:{name}", string.Empty, val);
-                await EndPointCore.NotifyReload(true);
-
-            }
-            return new APIResult(new
-            {
-                data = new { success, msg = success ? "更新成功" : "更新失败,请检查输入信息" },
-            });
-        }
-
-
-        [HttpPost]
-        public async Task<ActionResult> updateCookies([FromBody] JsonElement form)
-        {
-            var clientIp = _accessor.HttpContext.GetUserIp();
-            var token = provider.Get(_accessor.HttpContext);
-
-
-            string cookie = form.Read<string>("cookie", string.Empty);
-            string h5st = form.Read<string>("h5st", string.Empty);
-            string envStr = form.Read<string>("envStr", string.Empty);
-            string user_agent = form.Read<string>("user_agent", string.Empty);
-            cookie = cookie.Replace("\r", "").Replace("\n", "").Trim();
-            if (!cookie.EndsWith(";")) cookie += ";";
-
-            string union_cookies = form.Read<string>("union_cookies", string.Empty);
-            union_cookies = union_cookies.Replace("\r", "").Replace("\n", "").Trim();
-            if (!union_cookies.EndsWith(";")) union_cookies += ";";
-
-            string[] parts = h5st.Split(';');
-            h5st = string.Join(";", parts.Take(8));
-            h5st = h5st.Trim();
-
-
-            user_agent = user_agent.Trim();
-            var accountId = JdPoolCore.UpdateCookies(cookie, union_cookies, user_agent, h5st, envStr);
-            bool success = accountId > 0;
-            if (success)
-            {
-                //string desc = ObjectComparer.PrintCompareToString(old, form).Trim();
-                OperationLogCore.LogOperation(token.AccessKey, clientIp, $"jd_pool:{accountId}:cookie", string.Empty, cookie);
-                OperationLogCore.LogOperation(token.AccessKey, clientIp, $"jd_pool:{accountId}:union_cookies", string.Empty, union_cookies);
-            }
-            return new APIResult(new
-            {
-                data = new { success, msg = success ? "更新成功" : "更新失败,请检查输入的cookie" },
-            });
-        }
-    }
+using molilian.core;
+using dodohold.core;
+using Microsoft.AspNetCore.Mvc;
+using System.Text.Json;
+using System.Net;
+using static QRCoder.PayloadGenerator;
+using MySqlX.XDevAPI;
+using OfficeOpenXml.FormulaParsing.LexicalAnalysis;
+using WebSocketSharp.Net;
+namespace molilian.api.Controllers
+{
+
+
+    [ApiController]
+    [MyAuthorize("admin")]
+    [Route("api/[controller]/[action]")]
+    public class JdUnionController : ControllerBase
+    {
+        readonly IAuthorizationProvider provider = new AdminProvider();
+        protected IHttpContextAccessor _accessor;
+        public JdUnionController(IHttpContextAccessor accessor)
+        {
+            _accessor = accessor;
+        }
+
+
+
+        [HttpPost]
+        public ActionResult settle_bills_total([FromBody] JsonElement form)
+        {
+            int page = form.Read("current", 1);
+            int size = form.Read("pageSize", 10);
+            bool getTotal = form.Read("getTotal", true);
+            string sort = form.Read<string>("sort");
+            string order = form.Read<string>("order");
+
+            var report_date = form.PathReadArray<string>("query_date[]");
+
+            string filter = string.Empty;
+            DateTime stime = DateTime.MinValue, etime = DateTime.MinValue;
+            if (report_date.Count == 2)
+            {
+                if (DateTime.TryParse(report_date[0], out stime) && DateTime.TryParse(report_date[1], out etime))
+                {
+                    etime = etime.AddDays(1).AddSeconds(-1);
+                    filter += $" AND report_date BETWEEN @stime AND @etime";
+                }
+            }
+
+            filter = filter.StringTrimStart(" AND ");
+
+            //排序
+            string orderBy = "report_date DESC";
+            if (!string.IsNullOrEmpty(order))
+            {
+                order = "descending".Equals(order) ? "DESC" : "ASC";
+                orderBy = sort switch
+                {
+                    _ => $"{sort} {order}",
+                };
+            }
+            var result = new DBContext.Table("v_jd_spread_report")
+                .Where(filter, new { stime, etime })
+                .Page(size, page)
+                .Order(orderBy)
+                .PageList<JdSpreadReportDTO>(getTotal);
+
+            return new APIResult(new { data = result });
+        }
+
+
+        [HttpPost]
+        public ActionResult settle_bills([FromBody] JsonElement form)
+        {
+            int page = form.Read("current", 1);
+            int size = form.Read("pageSize", 10);
+            bool getTotal = form.Read("getTotal", true);
+            string sort = form.Read<string>("sort");
+            string order = form.Read<string>("order");
+            string name = form.Read<string>("keyword");
+
+            var report_date = form.PathReadArray<string>("query_date[]");
+
+            string filter = string.Empty;
+            if (!string.IsNullOrEmpty(name))
+            {
+                filter += $" AND accountId IN (SELECT ID FROM jd_pool WHERE company=@name)";
+            }
+            DateTime stime = DateTime.MinValue, etime = DateTime.MinValue;
+            if (report_date.Count == 2)
+            {
+                if (DateTime.TryParse(report_date[0], out stime) && DateTime.TryParse(report_date[1], out etime))
+                {
+                    etime = etime.AddDays(1).AddSeconds(-1);
+                    filter += $" AND report_date BETWEEN @stime AND @etime";
+                }
+            }
+
+            filter = filter.StringTrimStart(" AND ");
+
+            //排序
+            string orderBy = "report_date DESC";
+            if (!string.IsNullOrEmpty(order))
+            {
+                order = "descending".Equals(order) ? "DESC" : "ASC";
+                orderBy = sort switch
+                {
+                    _ => $"{sort} {order}",
+                };
+            }
+
+            var result = new DBContext.Table("jd_spread_report")
+                .Where(filter, new { name, stime, etime })
+                .Page(size, page)
+                .Order(orderBy)
+                .PageList<JdSpreadReportDTO>(getTotal);
+
+            return new APIResult(new { data = result });
+        }
+
+
+
+
+        [HttpPost]
+        public ActionResult estimate_comm_total([FromBody] JsonElement form)
+        {
+            int page = form.Read("current", 1);
+            int size = form.Read("pageSize", 10);
+            bool getTotal = form.Read("getTotal", true);
+            string sort = form.Read<string>("sort");
+            string order = form.Read<string>("order");
+
+            var report_date = form.PathReadArray<string>("query_date[]");
+
+            string filter = string.Empty;
+            DateTime stime = DateTime.MinValue, etime = DateTime.MinValue;
+            if (report_date.Count == 2)
+            {
+                if (DateTime.TryParse(report_date[0], out stime) && DateTime.TryParse(report_date[1], out etime))
+                {
+                    etime = etime.AddDays(1).AddSeconds(-1);
+                    filter += $" AND report_date BETWEEN @stime AND @etime";
+                }
+            }
+
+            filter = filter.StringTrimStart(" AND ");
+
+            //排序
+            string orderBy = "report_date DESC";
+            if (!string.IsNullOrEmpty(order))
+            {
+                order = "descending".Equals(order) ? "DESC" : "ASC";
+                orderBy = sort switch
+                {
+                    _ => $"{sort} {order}",
+                };
+            }
+            var result = new DBContext.Table("v_jd_estimate_comm")
+                .Where(filter, new { stime, etime })
+                .Page(size, page)
+                .Order(orderBy)
+                .PageList<JdEstimateCommDTO>(getTotal);
+
+            return new APIResult(new { data = result });
+        }
+
+
+        [HttpPost]
+        public ActionResult estimate_comm([FromBody] JsonElement form)
+        {
+            int page = form.Read("current", 1);
+            int size = form.Read("pageSize", 10);
+            bool getTotal = form.Read("getTotal", true);
+            string sort = form.Read<string>("sort");
+            string order = form.Read<string>("order");
+            string name = form.Read<string>("keyword");
+
+            var report_date = form.PathReadArray<string>("query_date[]");
+
+            string filter = string.Empty;
+            if (!string.IsNullOrEmpty(name))
+            {
+                filter += $" AND accountId IN (SELECT ID FROM jd_pool WHERE company=@name AND is_hide=0)";
+            }
+            else
+            {
+                filter += $" AND accountId IN (SELECT ID FROM jd_pool WHERE is_hide=0)";
+            }
+
+            DateTime stime = DateTime.MinValue, etime = DateTime.MinValue;
+            if (report_date.Count == 2)
+            {
+                if (DateTime.TryParse(report_date[0], out stime) && DateTime.TryParse(report_date[1], out etime))
+                {
+                    etime = etime.AddDays(1).AddSeconds(-1);
+                    filter += $" AND report_date BETWEEN @stime AND @etime";
+                }
+            }
+
+            filter = filter.StringTrimStart(" AND ");
+
+            //排序
+            string orderBy = "report_date DESC";
+            if (!string.IsNullOrEmpty(order))
+            {
+                order = "descending".Equals(order) ? "DESC" : "ASC";
+                orderBy = sort switch
+                {
+                    _ => $"{sort} {order}",
+                };
+            }
+
+            var result = new DBContext.Table("jd_estimate_comm")
+                .Where(filter, new { name, stime, etime })
+                .Page(size, page)
+                .Order(orderBy)
+                .PageList<JdEstimateCommDTO>(getTotal);
+
+            return new APIResult(new { data = result });
+        }
+
+
+
+        /// <summary>
+        /// 账号列表
+        /// </summary>
+        /// <param name="form"></param>
+        /// <returns></returns>
+        [HttpPost]
+        public ActionResult list([FromBody] JsonElement form)
+        {
+            var token = provider.Get(_accessor.HttpContext);
+
+            int page = form.Read("current", 1);
+            int size = form.Read("pageSize", 10);
+            string keyword = form.Read("name", string.Empty);
+            bool getTotal = form.Read("getTotal", true);
+            string _status = form.Read("status", string.Empty);
+            int status = _status switch
+            {
+                "online" => 1,
+                "offline" => 0,
+                _ => -1,
+            };
+            var lastTime = form.PathReadArray<string>("lastTime[]");
+
+            string sort = form.Read<string>("sort");
+            string order = form.Read<string>("order");
+
+            bool show_hide = form.Read("show_hide", false);
+            string filter = string.Empty;
+            if (!show_hide) filter = "is_hide=0";
+            if (!string.IsNullOrEmpty(keyword))
+            {
+                filter += $" AND (`name` LIKE @keyword OR `description` LIKE @keyword)";
+                keyword = $"%{keyword}%";
+            }
+            DateTime stime = DateTime.MinValue, etime = DateTime.MinValue;
+            if (lastTime.Count == 2)
+            {
+                if (DateTime.TryParse(lastTime[0], out stime) && DateTime.TryParse(lastTime[1], out etime))
+                {
+                    etime = etime.AddDays(1).AddSeconds(-1);
+                    filter += $" AND last_time BETWEEN @stime AND @etime";
+                }
+            }
+            if (status != -1)
+            {
+                filter += $" AND status=@status";
+
+            }
+
+            filter = filter.StringTrimStart(" AND ");
+
+            //排序
+            string orderBy = "id DESC";
+            if (!string.IsNullOrEmpty(order))
+            {
+                order = "descending".Equals(order) ? "DESC" : "ASC";
+                orderBy = sort switch
+                {
+                    //"num" => $"num {order}",
+                    _ => $"{sort} {order}",
+                };
+            }
+
+            using var conn = DBContext.GetOpenConnection();
+            var result = new DBContext.Table(conn, "jd_pool")
+                .Where(filter, new { keyword, status, stime, etime })
+                .Page(size, page)
+                .Order(orderBy)
+                .PageList<JdPoolDTO>(getTotal);
+
+            foreach (var item in result.List)
+            {
+                item.app_secret = string.Empty;
+                item.cookies = string.Empty;
+                item.riskCookie = RiskControlCore.GetRiskCookie($"jd:{item.id}");
+            }
+            return new APIResult(new { data = result });
+        }
+
+        [HttpPost]
+        public async Task<ActionResult> update([FromBody] JsonElement form)
+        {
+            var clientIp = _accessor.HttpContext.GetUserIp();
+            var token = provider.Get(_accessor.HttpContext);
+
+            int id = form.Read<int>("id", 0);
+            string name = form.Read<string>("name", string.Empty);
+            string val = form.Read<string>("val", string.Empty);
+
+            if (id == 0)
+            {
+                return new APIResult(new { data = new { success = false, msg = "更新失败,请检查输入的cookie" } });
+            }
+
+            int result;
+            switch (name)
+            {
+                case "enable_parse":
+                case "enable_coupon":
+                case "enable_sync_report":
+                case "enable_sync_order":
+                case "status":
+                    bool bVal = val.Equals("True");
+                    result = new DBContext.Table("jd_pool")
+                           .Add(name, bVal)
+                           .Add("last_time", DateTime.Now)
+                           .Where("id=@id", new { id })
+                           .Update();
+                    break;
+                case "time_range":
+                    int.TryParse(val, out int iVal);
+                    result = new DBContext.Table("jd_pool")
+                           .Add(name, iVal)
+                           .Add("last_time", DateTime.Now)
+                           .Where("id=@id", new { id })
+                           .Update();
+                    break;
+                default:
+                    return new APIResult(new { data = new { success = false, msg = "更新失败,未授权操作" } });
+            }
+            bool success = result > 0;
+            if (success)
+            {
+                OperationLogCore.LogOperation(token.AccessKey, clientIp, $"jd_pool:{id}:{name}", string.Empty, val);
+                await EndPointCore.NotifyReload(true);
+
+            }
+            return new APIResult(new
+            {
+                data = new { success, msg = success ? "更新成功" : "更新失败,请检查输入信息" },
+            });
+        }
+
+
+        [HttpPost]
+        public async Task<ActionResult> updateCookies([FromBody] JsonElement form)
+        {
+            var clientIp = _accessor.HttpContext.GetUserIp();
+            var token = provider.Get(_accessor.HttpContext);
+
+
+            string cookie = form.Read<string>("cookie", string.Empty);
+            string h5st = form.Read<string>("h5st", string.Empty);
+            string envStr = form.Read<string>("envStr", string.Empty);
+            string user_agent = form.Read<string>("user_agent", string.Empty);
+            cookie = cookie.Replace("\r", "").Replace("\n", "").Trim();
+            if (!cookie.EndsWith(";")) cookie += ";";
+
+            string union_cookies = form.Read<string>("union_cookies", string.Empty);
+            union_cookies = union_cookies.Replace("\r", "").Replace("\n", "").Trim();
+            if (!union_cookies.EndsWith(";")) union_cookies += ";";
+
+            string[] parts = h5st.Split(';');
+            h5st = string.Join(";", parts.Take(8));
+            h5st = h5st.Trim();
+
+
+            user_agent = user_agent.Trim();
+            var accountId = JdPoolCore.UpdateCookies(cookie, union_cookies, user_agent, h5st, envStr);
+            bool success = accountId > 0;
+            if (success)
+            {
+                //string desc = ObjectComparer.PrintCompareToString(old, form).Trim();
+                OperationLogCore.LogOperation(token.AccessKey, clientIp, $"jd_pool:{accountId}:cookie", string.Empty, cookie);
+                OperationLogCore.LogOperation(token.AccessKey, clientIp, $"jd_pool:{accountId}:union_cookies", string.Empty, union_cookies);
+            }
+            return new APIResult(new
+            {
+                data = new { success, msg = success ? "更新成功" : "更新失败,请检查输入的cookie" },
+            });
+        }
+    }
 }

+ 136 - 136
molilian.api/Controllers/admin/MessageController.cs

@@ -1,137 +1,137 @@
-using molilian.core;
-using dodohold.core;
-using Microsoft.AspNetCore.Mvc;
-using System.Text.Json;
-using static molilian.core.ChartDataCore;
-using Org.BouncyCastle.Asn1;
-namespace molilian.api.Controllers
-{
-    [ApiController]
-    [MyAuthorize("admin")]
-    [Route("api/[controller]/[action]")]
-    public class MessageController : ControllerBase
-    {
-        readonly IAuthorizationProvider provider = new AdminProvider();
-        protected IHttpContextAccessor _accessor;
-        public MessageController(IHttpContextAccessor accessor)
-        {
-            _accessor = accessor;
-        }
-        [HttpGet]
-        public async Task<ActionResult> AccountWarning()
-        {
-            IEnumerable<TkPoolDTO> tb_list = await TkPoolCore.AllListAsync();
-
-            List<string> typeKeys = new List<string>();
-            List<string> riskStrategyKeys = new List<string>();
-
-            Dictionary<string, List<int>> total = new();
-            foreach (TkPoolDTO tb in tb_list)
-            {
-                if (!string.IsNullOrEmpty(tb.parse_type) && !typeKeys.Contains(tb.parse_type)) typeKeys.Add(tb.parse_type);
-                if (!string.IsNullOrEmpty(tb.riskStrategy))
-                {
-                    string key = $"{tb.riskStrategy}-{tb.launchScene}";
-                    if (!riskStrategyKeys.Contains(key)) riskStrategyKeys.Add(key);
-                }
-            }
-            // 统计 typeKeys 的在线/离线状态
-            foreach (string typeKey in typeKeys)
-            {
-                var typeAccounts = tb_list.Where(tb => tb.parse_type == typeKey);
-                int totalCount = typeAccounts.Count();
-                int onlineCount = typeAccounts.Count(tb => tb.status);
-
-                total[typeKey] = new List<int> { totalCount, onlineCount };
-            }
-
-            // 统计 riskStrategyKeys 的在线/离线状态
-            foreach (string riskStrategyKey in riskStrategyKeys)
-            {
-                // riskStrategyKey 格式为 "riskStrategy-launchScene"
-                string[] parts = riskStrategyKey.Split('-');
-                if (parts.Length >= 2)
-                {
-                    string riskStrategy = parts[0];
-                    if (int.TryParse(parts[^1], out int launchScene))
-                    {
-                        var riskAccounts = tb_list.Where(tb =>
-                            tb.riskStrategy == riskStrategy &&
-                            tb.launchScene == launchScene);
-
-                        int totalCount = riskAccounts.Count();
-                        int onlineCount = riskAccounts.Count(tb => tb.status);
-
-                        total[riskStrategyKey] = [totalCount, onlineCount];
-                    }
-                }
-            }
-
-            IEnumerable<JdPoolDTO> jd_list = await JdPoolCore.AllListAsync();
-
-            List<string> jdTypeKeys = new List<string>();
-            List<string> jdRiskStrategyKeys = new List<string>();
-
-            Dictionary<string, List<int>> jdTotal = new();
-
-            // 收集京东的 typeKeys 和 riskStrategyKeys
-            foreach (JdPoolDTO jd in jd_list)
-            {
-                if (jd.id < 38) continue;
-                if (!string.IsNullOrEmpty(jd.parse_type) && !jdTypeKeys.Contains(jd.parse_type)) jdTypeKeys.Add(jd.parse_type);
-                if (!string.IsNullOrEmpty(jd.riskStrategy))
-                {
-                    string key = $"{jd.riskStrategy}-{jd.launchScene}";
-                    if (!jdRiskStrategyKeys.Contains(key)) jdRiskStrategyKeys.Add(key);
-                }
-            }
-
-            // 统计京东 typeKeys 的在线/离线状态
-            foreach (string typeKey in jdTypeKeys)
-            {
-                var typeAccounts = jd_list.Where(jd => jd.parse_type == typeKey);
-                int totalCount = typeAccounts.Count();
-                int onlineCount = typeAccounts.Count(jd => jd.status);
-
-                jdTotal[typeKey] = new List<int> { totalCount, onlineCount };
-            }
-
-            // 统计京东 riskStrategyKeys 的在线/离线状态
-            foreach (string riskStrategyKey in jdRiskStrategyKeys)
-            {
-                // riskStrategyKey 格式为 "riskStrategy-launchScene"
-                string[] parts = riskStrategyKey.Split('-');
-                if (parts.Length >= 2)
-                {
-                    string riskStrategy = parts[0];
-                    if (int.TryParse(parts[^1], out int launchScene))
-                    {
-                        var riskAccounts = jd_list.Where(jd =>
-                            jd.riskStrategy == riskStrategy &&
-                            jd.launchScene == launchScene);
-
-                        int totalCount = riskAccounts.Count();
-                        int onlineCount = riskAccounts.Count(jd => jd.status);
-                        jdTotal[riskStrategyKey] = [totalCount, onlineCount];
-                    }
-                }
-            }
-
-
-            return new APIResult(new
-            {
-                data = new
-                {
-                    tb = total,
-                    jd = jdTotal
-                }
-            });
-        }
-
-        [HttpPost]
-        public async Task<ActionResult> list([FromBody] JsonElement form)
-        {
-            return new APIResult(new { data = string.Empty });
-        }
-    }
+using molilian.core;
+using dodohold.core;
+using Microsoft.AspNetCore.Mvc;
+using System.Text.Json;
+using static molilian.core.ChartDataCore;
+using Org.BouncyCastle.Asn1;
+namespace molilian.api.Controllers
+{
+    [ApiController]
+    [MyAuthorize("admin")]
+    [Route("api/[controller]/[action]")]
+    public class MessageController : ControllerBase
+    {
+        readonly IAuthorizationProvider provider = new AdminProvider();
+        protected IHttpContextAccessor _accessor;
+        public MessageController(IHttpContextAccessor accessor)
+        {
+            _accessor = accessor;
+        }
+        [HttpGet]
+        public async Task<ActionResult> AccountWarning()
+        {
+            IEnumerable<TkPoolDTO> tb_list = await TkPoolCore.AllListAsync();
+
+            List<string> typeKeys = new List<string>();
+            List<string> riskStrategyKeys = new List<string>();
+
+            Dictionary<string, List<int>> total = new();
+            foreach (TkPoolDTO tb in tb_list)
+            {
+                if (!string.IsNullOrEmpty(tb.parse_type) && !typeKeys.Contains(tb.parse_type)) typeKeys.Add(tb.parse_type);
+                if (!string.IsNullOrEmpty(tb.riskStrategy))
+                {
+                    string key = $"{tb.riskStrategy}-{tb.launchScene}";
+                    if (!riskStrategyKeys.Contains(key)) riskStrategyKeys.Add(key);
+                }
+            }
+            // 统计 typeKeys 的在线/离线状态
+            foreach (string typeKey in typeKeys)
+            {
+                var typeAccounts = tb_list.Where(tb => tb.parse_type == typeKey);
+                int totalCount = typeAccounts.Count();
+                int onlineCount = typeAccounts.Count(tb => tb.status);
+
+                total[typeKey] = new List<int> { totalCount, onlineCount };
+            }
+
+            // 统计 riskStrategyKeys 的在线/离线状态
+            foreach (string riskStrategyKey in riskStrategyKeys)
+            {
+                // riskStrategyKey 格式为 "riskStrategy-launchScene"
+                string[] parts = riskStrategyKey.Split('-');
+                if (parts.Length >= 2)
+                {
+                    string riskStrategy = parts[0];
+                    if (int.TryParse(parts[^1], out int launchScene))
+                    {
+                        var riskAccounts = tb_list.Where(tb =>
+                            tb.riskStrategy == riskStrategy &&
+                            tb.launchScene == launchScene);
+
+                        int totalCount = riskAccounts.Count();
+                        int onlineCount = riskAccounts.Count(tb => tb.status);
+
+                        total[riskStrategyKey] = [totalCount, onlineCount];
+                    }
+                }
+            }
+
+            IEnumerable<JdPoolDTO> jd_list = await JdPoolCore.AllListAsync();
+
+            List<string> jdTypeKeys = new List<string>();
+            List<string> jdRiskStrategyKeys = new List<string>();
+
+            Dictionary<string, List<int>> jdTotal = new();
+
+            // 收集京东的 typeKeys 和 riskStrategyKeys
+            foreach (JdPoolDTO jd in jd_list)
+            {
+                if (jd.id < 38) continue;
+                if (!string.IsNullOrEmpty(jd.parse_type) && !jdTypeKeys.Contains(jd.parse_type)) jdTypeKeys.Add(jd.parse_type);
+                if (!string.IsNullOrEmpty(jd.riskStrategy))
+                {
+                    string key = $"{jd.riskStrategy}-{jd.launchScene}";
+                    if (!jdRiskStrategyKeys.Contains(key)) jdRiskStrategyKeys.Add(key);
+                }
+            }
+
+            // 统计京东 typeKeys 的在线/离线状态
+            foreach (string typeKey in jdTypeKeys)
+            {
+                var typeAccounts = jd_list.Where(jd => jd.parse_type == typeKey);
+                int totalCount = typeAccounts.Count();
+                int onlineCount = typeAccounts.Count(jd => jd.status);
+
+                jdTotal[typeKey] = new List<int> { totalCount, onlineCount };
+            }
+
+            // 统计京东 riskStrategyKeys 的在线/离线状态
+            foreach (string riskStrategyKey in jdRiskStrategyKeys)
+            {
+                // riskStrategyKey 格式为 "riskStrategy-launchScene"
+                string[] parts = riskStrategyKey.Split('-');
+                if (parts.Length >= 2)
+                {
+                    string riskStrategy = parts[0];
+                    if (int.TryParse(parts[^1], out int launchScene))
+                    {
+                        var riskAccounts = jd_list.Where(jd =>
+                            jd.riskStrategy == riskStrategy &&
+                            jd.launchScene == launchScene);
+
+                        int totalCount = riskAccounts.Count();
+                        int onlineCount = riskAccounts.Count(jd => jd.status);
+                        jdTotal[riskStrategyKey] = [totalCount, onlineCount];
+                    }
+                }
+            }
+
+
+            return new APIResult(new
+            {
+                data = new
+                {
+                    tb = total,
+                    jd = jdTotal
+                }
+            });
+        }
+
+        [HttpPost]
+        public async Task<ActionResult> list([FromBody] JsonElement form)
+        {
+            return new APIResult(new { data = string.Empty });
+        }
+    }
 }

+ 130 - 130
molilian.api/Controllers/admin/OverrideRulesController.cs

@@ -1,131 +1,131 @@
-using molilian.core;
-using dodohold.core;
-using Microsoft.AspNetCore.Mvc;
-using System.Text.Json;
-using System.Net;
-using static QRCoder.PayloadGenerator;
-using MySqlX.XDevAPI;
-using OfficeOpenXml.FormulaParsing.LexicalAnalysis;
-using StackExchange.Redis;
-using YunhuiKit;
-namespace molilian.api.Controllers
-{
-
-
-    [ApiController]
-    [MyAuthorize("admin")]
-    [Route("api/[controller]/[action]")]
-    public class OverrideRulesController : ControllerBase
-    {
-        readonly IAuthorizationProvider provider = new AdminProvider();
-        protected IHttpContextAccessor _accessor;
-        public OverrideRulesController(IHttpContextAccessor accessor)
-        {
-            _accessor = accessor;
-        }
-
-
-
-        [HttpPost]
-        public async Task<ActionResult> list([FromBody] JsonElement form)
-        {
-            var token = provider.Get(_accessor.HttpContext);
-
-            int page = form.Read("current", 1);
-            int size = form.Read("pageSize", 10);
-            bool getTotal = form.Read("getTotal", true);
-
-            string sort = form.Read<string>("sort");
-            string order = form.Read<string>("order");
-
-            string filter = string.Empty;
-
-            var keyword = form.Read("keyword", string.Empty);
-
-            if (!string.IsNullOrEmpty(keyword))
-            {
-                filter += $" AND (original_text like @keyword OR original_text like @keyword)";
-                keyword = $"%{keyword}%";
-            }
-            filter = filter.StringTrimStart(" AND ");
-
-            string orderBy = "sort DESC, id DESC";
-            if (!string.IsNullOrEmpty(order))
-            {
-                order = "descending".Equals(order) ? "DESC" : "ASC";
-                orderBy = sort switch
-                {
-                    _ => $"{sort} {order}",
-                };
-            }
-
-            var result = new DBContext.Table("tk_override_rules")
-               .Where(filter, new { keyword })
-               .Page(size, page)
-               .Order(orderBy)
-               .PageList<OverrideRuleDTO>(getTotal);
-
-            foreach (var item in result.List)
-            {
-                string hourlyKey = $":override_rule_calls:{item.id}:{DateTime.Now:yyyyMMddHH}";
-                string dailyKey = $":override_rule_calls:{item.id}:{DateTime.Now:yyyyMMdd}";
-
-                item.current_daily_calls = await RedisKit.GetAsync<int>(hourlyKey);
-                item.current_hourly_calls = await RedisKit.GetAsync<int>(hourlyKey);
-            }
-            return new APIResult(new { data = result });
-        }
-
-
-
-        [HttpPost]
-        public async Task<ActionResult> update([FromBody] OverrideRuleDTO data)
-        {
-            var clientIp = _accessor.HttpContext.GetUserIp();
-            var token = provider.Get(_accessor.HttpContext);
-
-            using var conn = DBContext.GetOpenConnection();
-            if (data.id == 0) return new APIResult(new { data = new { success = false, msg = "更新失败,请检查输入参数" } });
-
-            var item = new DBContext.Table(conn, "tk_override_rules").Get<OverrideRuleDTO>("id=@id", new { data.id });
-            if (item == null) return new APIResult(new { data = new { success = false, msg = "更新失败,记录不存在" } });
-
-            data.last_time = DateTime.Now;
-            var result = OverrideRuleCore.Update(data, conn);
-            bool success = result > 0;
-            if (success)
-            {
-                string desc = ObjectComparer.PrintCompareToString(item, data).Trim();
-                OperationLogCore.LogOperation(token.AccessKey, clientIp, $"tk_override_rules:{data.id}", item.Convert2Json(), desc);
-            }
-            return new APIResult(new
-            {
-                data = new { success, msg = success ? "更新成功" : "更新失败,请检查输入信息" },
-            });
-        }
-
-        [HttpPost]
-        public async Task<ActionResult> create([FromBody] OverrideRuleDTO data)
-        {
-            var clientIp = _accessor.HttpContext.GetUserIp();
-            var token = provider.Get(_accessor.HttpContext);
-
-            data.create_time = DateTime.Now;
-            data.last_time = DateTime.Now;
-
-            using var conn = DBContext.GetOpenConnection();
-            var result = OverrideRuleCore.Create(data, conn);
-            bool success = result > 0;
-            if (success)
-            {
-                OperationLogCore.LogOperation(token.AccessKey, clientIp, $"tk_override_rules:{result}", string.Empty, data.Convert2Json());
-            }
-            return new APIResult(new
-            {
-                data = new { success, msg = success ? "更新成功" : "更新失败,请检查输入信息" },
-            });
-        }
-
-
-    }
+using molilian.core;
+using dodohold.core;
+using Microsoft.AspNetCore.Mvc;
+using System.Text.Json;
+using System.Net;
+using static QRCoder.PayloadGenerator;
+using MySqlX.XDevAPI;
+using OfficeOpenXml.FormulaParsing.LexicalAnalysis;
+using StackExchange.Redis;
+using YunhuiKit;
+namespace molilian.api.Controllers
+{
+
+
+    [ApiController]
+    [MyAuthorize("admin")]
+    [Route("api/[controller]/[action]")]
+    public class OverrideRulesController : ControllerBase
+    {
+        readonly IAuthorizationProvider provider = new AdminProvider();
+        protected IHttpContextAccessor _accessor;
+        public OverrideRulesController(IHttpContextAccessor accessor)
+        {
+            _accessor = accessor;
+        }
+
+
+
+        [HttpPost]
+        public async Task<ActionResult> list([FromBody] JsonElement form)
+        {
+            var token = provider.Get(_accessor.HttpContext);
+
+            int page = form.Read("current", 1);
+            int size = form.Read("pageSize", 10);
+            bool getTotal = form.Read("getTotal", true);
+
+            string sort = form.Read<string>("sort");
+            string order = form.Read<string>("order");
+
+            string filter = string.Empty;
+
+            var keyword = form.Read("keyword", string.Empty);
+
+            if (!string.IsNullOrEmpty(keyword))
+            {
+                filter += $" AND (original_text like @keyword OR original_text like @keyword)";
+                keyword = $"%{keyword}%";
+            }
+            filter = filter.StringTrimStart(" AND ");
+
+            string orderBy = "sort DESC, id DESC";
+            if (!string.IsNullOrEmpty(order))
+            {
+                order = "descending".Equals(order) ? "DESC" : "ASC";
+                orderBy = sort switch
+                {
+                    _ => $"{sort} {order}",
+                };
+            }
+
+            var result = new DBContext.Table("tk_override_rules")
+               .Where(filter, new { keyword })
+               .Page(size, page)
+               .Order(orderBy)
+               .PageList<OverrideRuleDTO>(getTotal);
+
+            foreach (var item in result.List)
+            {
+                string hourlyKey = $":override_rule_calls:{item.id}:{DateTime.Now:yyyyMMddHH}";
+                string dailyKey = $":override_rule_calls:{item.id}:{DateTime.Now:yyyyMMdd}";
+
+                item.current_daily_calls = await RedisKit.GetAsync<int>(hourlyKey);
+                item.current_hourly_calls = await RedisKit.GetAsync<int>(hourlyKey);
+            }
+            return new APIResult(new { data = result });
+        }
+
+
+
+        [HttpPost]
+        public async Task<ActionResult> update([FromBody] OverrideRuleDTO data)
+        {
+            var clientIp = _accessor.HttpContext.GetUserIp();
+            var token = provider.Get(_accessor.HttpContext);
+
+            using var conn = DBContext.GetOpenConnection();
+            if (data.id == 0) return new APIResult(new { data = new { success = false, msg = "更新失败,请检查输入参数" } });
+
+            var item = new DBContext.Table(conn, "tk_override_rules").Get<OverrideRuleDTO>("id=@id", new { data.id });
+            if (item == null) return new APIResult(new { data = new { success = false, msg = "更新失败,记录不存在" } });
+
+            data.last_time = DateTime.Now;
+            var result = OverrideRuleCore.Update(data, conn);
+            bool success = result > 0;
+            if (success)
+            {
+                string desc = ObjectComparer.PrintCompareToString(item, data).Trim();
+                OperationLogCore.LogOperation(token.AccessKey, clientIp, $"tk_override_rules:{data.id}", item.Convert2Json(), desc);
+            }
+            return new APIResult(new
+            {
+                data = new { success, msg = success ? "更新成功" : "更新失败,请检查输入信息" },
+            });
+        }
+
+        [HttpPost]
+        public async Task<ActionResult> create([FromBody] OverrideRuleDTO data)
+        {
+            var clientIp = _accessor.HttpContext.GetUserIp();
+            var token = provider.Get(_accessor.HttpContext);
+
+            data.create_time = DateTime.Now;
+            data.last_time = DateTime.Now;
+
+            using var conn = DBContext.GetOpenConnection();
+            var result = OverrideRuleCore.Create(data, conn);
+            bool success = result > 0;
+            if (success)
+            {
+                OperationLogCore.LogOperation(token.AccessKey, clientIp, $"tk_override_rules:{result}", string.Empty, data.Convert2Json());
+            }
+            return new APIResult(new
+            {
+                data = new { success, msg = success ? "更新成功" : "更新失败,请检查输入信息" },
+            });
+        }
+
+
+    }
 }

+ 301 - 301
molilian.api/Controllers/admin/PddUnionController.cs

@@ -1,302 +1,302 @@
-using molilian.core;
-using dodohold.core;
-using Microsoft.AspNetCore.Mvc;
-using System.Text.Json;
-using System.Net;
-using static QRCoder.PayloadGenerator;
-using MySqlX.XDevAPI;
-using OfficeOpenXml.FormulaParsing.LexicalAnalysis;
-using WebSocketSharp.Net;
-namespace molilian.api.Controllers
-{
-
-
-    [ApiController]
-    [MyAuthorize("admin")]
-    [Route("api/[controller]/[action]")]
-    public class PddUnionController : ControllerBase
-    {
-        readonly IAuthorizationProvider provider = new AdminProvider();
-        protected IHttpContextAccessor _accessor;
-        public PddUnionController(IHttpContextAccessor accessor)
-        {
-            _accessor = accessor;
-        }
-
-        [HttpPost]
-        public async Task<ActionResult> updateCookies([FromBody] JsonElement form)
-        {
-            var clientIp = _accessor.HttpContext.GetUserIp();
-            var token = provider.Get(_accessor.HttpContext);
-            string user_agent = _accessor.HttpContext.Request.UserAgent();
-
-
-            string cookie = form.Read<string>("cookie", string.Empty);
-            int id = form.Read<int>("id", 0);
-            cookie = cookie.Replace("\r", "").Replace("\n", "").Trim();
-            if (!cookie.EndsWith(";")) cookie += ";";
-
-            var accountId = await PddPoolCore.UpdateCookies(cookie, user_agent, id);
-            bool success = accountId > 0;
-            if (success)
-            {
-                OperationLogCore.LogOperation(token.AccessKey, clientIp, $"pdd_pool:{accountId}:cookie", string.Empty, cookie);
-            }
-            return new APIResult(new
-            {
-                data = new { success, msg = success ? "更新成功" : "更新失败,请检查输入的cookie" },
-            });
-        }
-
-        /// <summary>
-        /// 账号列表
-        /// </summary>
-        /// <param name="form"></param>
-        /// <returns></returns>
-        [HttpPost]
-        public ActionResult list([FromBody] JsonElement form)
-        {
-            var token = provider.Get(_accessor.HttpContext);
-
-            int page = form.Read("current", 1);
-            int size = form.Read("pageSize", 10);
-            string keyword = form.Read("name", string.Empty);
-            bool getTotal = form.Read("getTotal", true);
-            string _status = form.Read("status", string.Empty);
-            int status = _status switch
-            {
-                "online" => 1,
-                "offline" => 0,
-                _ => -1,
-            };
-            var lastTime = form.PathReadArray<string>("lastTime[]");
-
-            string sort = form.Read<string>("sort");
-            string order = form.Read<string>("order");
-
-            bool show_hide = form.Read("show_hide", false);
-            string filter = string.Empty;
-            if (!show_hide) filter = "is_hide=0";
-            if (!string.IsNullOrEmpty(keyword))
-            {
-                filter += $" AND (`name` LIKE @keyword OR `description` LIKE @keyword)";
-                keyword = $"%{keyword}%";
-            }
-            DateTime stime = DateTime.MinValue, etime = DateTime.MinValue;
-            if (lastTime.Count == 2)
-            {
-                if (DateTime.TryParse(lastTime[0], out stime) && DateTime.TryParse(lastTime[1], out etime))
-                {
-                    etime = etime.AddDays(1).AddSeconds(-1);
-                    filter += $" AND last_time BETWEEN @stime AND @etime";
-                }
-            }
-            if (status != -1)
-            {
-                filter += $" AND status=@status";
-
-            }
-
-            filter = filter.StringTrimStart(" AND ");
-
-            //排序
-            string orderBy = "id DESC";
-            if (!string.IsNullOrEmpty(order))
-            {
-                order = "descending".Equals(order) ? "DESC" : "ASC";
-                orderBy = sort switch
-                {
-                    //"num" => $"num {order}",
-                    _ => $"{sort} {order}",
-                };
-            }
-
-            using var conn = DBContext.GetOpenConnection();
-            var result = new DBContext.Table(conn, "pdd_pool")
-                .Where(filter, new { keyword, status, stime, etime })
-                .Page(size, page)
-                .Order(orderBy)
-                .PageList<PddPoolDTO>(getTotal);
-
-            foreach (var item in result.List)
-            {
-                item.app_secret = string.Empty;
-                item.cookies = string.Empty;
-                item.riskCookie = RiskControlCore.GetRiskCookie($"pdd:{item.id}");
-            }
-            return new APIResult(new { data = result });
-        }
-
-        [HttpPost]
-        public async Task<ActionResult> update([FromBody] JsonElement form)
-        {
-            var clientIp = _accessor.HttpContext.GetUserIp();
-            var token = provider.Get(_accessor.HttpContext);
-
-            int id = form.Read<int>("id", 0);
-            string name = form.Read<string>("name", string.Empty);
-            string val = form.Read<string>("val", string.Empty);
-
-            if (id == 0)
-            {
-                return new APIResult(new { data = new { success = false, msg = "更新失败,请检查输入的cookie" } });
-            }
-
-            int result;
-            switch (name)
-            {
-                case "enable_parse":
-                case "enable_sync_revenue":
-                case "enable_sync_order":
-                case "enable_coupon":
-                case "cookie_status":
-                case "status":
-                    bool bVal = val.Equals("True");
-                    result = new DBContext.Table("pdd_pool")
-                           .Add(name, bVal)
-                           .Add("last_time", DateTime.Now)
-                           .Where("id=@id", new { id })
-                           .Update();
-
-                    if (bVal && ("status".Equals(name) || "enable_parse".Equals(name)))
-                    {
-                        PddPoolCore.InitializeNewAccountUsage(id);
-                    }
-                    break;
-                case "time_range":
-                    int.TryParse(val, out int iVal);
-                    result = new DBContext.Table("pdd_pool")
-                           .Add(name, iVal)
-                           .Add("last_time", DateTime.Now)
-                           .Where("id=@id", new { id })
-                           .Update();
-                    break;
-                case "balance":
-                    // todo 更新账户余额
-                    var account = new DBContext.Table("pdd_pool").Get<PddPoolDTO>("id=@id", new { id });
-                    var plus = new PddUnionPlus(account);
-                    result = await plus.queryMallBalance();
-                    break;
-                default:
-                    return new APIResult(new { data = new { success = false, msg = "更新失败,未授权操作" } });
-            }
-            bool success = result > 0;
-            if (success)
-            {
-                OperationLogCore.LogOperation(token.AccessKey, clientIp, $"pdd_pool:{id}:{name}", string.Empty, val);
-                await EndPointCore.NotifyReload(true);
-            }
-            return new APIResult(new
-            {
-                data = new { success, msg = success ? "更新成功" : "更新失败,请检查输入信息" },
-            });
-        }
-
-        [HttpPost]
-        public ActionResult estimate_revenue_total([FromBody] JsonElement form)
-        {
-            int page = form.Read("current", 1);
-            int size = form.Read("pageSize", 10);
-            bool getTotal = form.Read("getTotal", true);
-            string sort = form.Read<string>("sort");
-            string order = form.Read<string>("order");
-
-            var report_date = form.PathReadArray<string>("query_date[]");
-
-            string filter = string.Empty;
-            DateTime stime = DateTime.MinValue, etime = DateTime.MinValue;
-            if (report_date.Count == 2)
-            {
-                if (DateTime.TryParse(report_date[0], out stime) && DateTime.TryParse(report_date[1], out etime))
-                {
-                    etime = etime.AddDays(1).AddSeconds(-1);
-                    filter += $" AND report_date BETWEEN @stime AND @etime";
-                }
-            }
-
-            filter = filter.StringTrimStart(" AND ");
-
-            //排序
-            string orderBy = "report_date DESC";
-            if (!string.IsNullOrEmpty(order))
-            {
-                order = "descending".Equals(order) ? "DESC" : "ASC";
-                orderBy = sort switch
-                {
-                    _ => $"{sort} {order}",
-                };
-            }
-            var result = new DBContext.Table("v_pdd_estimate_revenue")
-                .Where(filter, new { stime, etime })
-                .Page(size, page)
-                .Order(orderBy)
-                .PageList<PddEstimateRevenueDTO>(getTotal);
-
-            return new APIResult(new { data = result });
-        }
-
-
-        [HttpGet]
-        public ActionResult RechargeAllOnlineAccountUsage()
-        {
-            int count = PddPoolCore.RechargeAllOnlineAccountUsage();
-            return new APIResult(new { msg = "ok", count });
-        }
-
-
-        [HttpPost]
-        public ActionResult estimate_revenue([FromBody] JsonElement form)
-        {
-            int page = form.Read("current", 1);
-            int size = form.Read("pageSize", 10);
-            bool getTotal = form.Read("getTotal", true);
-            string sort = form.Read<string>("sort");
-            string order = form.Read<string>("order");
-            string name = form.Read<string>("keyword");
-
-            var report_date = form.PathReadArray<string>("query_date[]");
-
-            string filter = string.Empty;
-            if (!string.IsNullOrEmpty(name))
-            {
-                filter += $" AND accountId IN (SELECT ID FROM pdd_pool WHERE company=@name AND is_hide=0)";
-            }
-            else
-            {
-                filter += $" AND accountId IN (SELECT ID FROM pdd_pool WHERE is_hide=0)";
-            }
-
-            DateTime stime = DateTime.MinValue, etime = DateTime.MinValue;
-            if (report_date.Count == 2)
-            {
-                if (DateTime.TryParse(report_date[0], out stime) && DateTime.TryParse(report_date[1], out etime))
-                {
-                    etime = etime.AddDays(1).AddSeconds(-1);
-                    filter += $" AND report_date BETWEEN @stime AND @etime";
-                }
-            }
-
-            filter = filter.StringTrimStart(" AND ");
-
-            //排序
-            string orderBy = "report_date DESC";
-            if (!string.IsNullOrEmpty(order))
-            {
-                order = "descending".Equals(order) ? "DESC" : "ASC";
-                orderBy = sort switch
-                {
-                    _ => $"{sort} {order}",
-                };
-            }
-
-            var result = new DBContext.Table("pdd_estimate_revenue")
-                .Where(filter, new { name, stime, etime })
-                .Page(size, page)
-                .Order(orderBy)
-                .PageList<PddEstimateRevenueDTO>(getTotal);
-
-            return new APIResult(new { data = result });
-        }
-
-    }
+using molilian.core;
+using dodohold.core;
+using Microsoft.AspNetCore.Mvc;
+using System.Text.Json;
+using System.Net;
+using static QRCoder.PayloadGenerator;
+using MySqlX.XDevAPI;
+using OfficeOpenXml.FormulaParsing.LexicalAnalysis;
+using WebSocketSharp.Net;
+namespace molilian.api.Controllers
+{
+
+
+    [ApiController]
+    [MyAuthorize("admin")]
+    [Route("api/[controller]/[action]")]
+    public class PddUnionController : ControllerBase
+    {
+        readonly IAuthorizationProvider provider = new AdminProvider();
+        protected IHttpContextAccessor _accessor;
+        public PddUnionController(IHttpContextAccessor accessor)
+        {
+            _accessor = accessor;
+        }
+
+        [HttpPost]
+        public async Task<ActionResult> updateCookies([FromBody] JsonElement form)
+        {
+            var clientIp = _accessor.HttpContext.GetUserIp();
+            var token = provider.Get(_accessor.HttpContext);
+            string user_agent = _accessor.HttpContext.Request.UserAgent();
+
+
+            string cookie = form.Read<string>("cookie", string.Empty);
+            int id = form.Read<int>("id", 0);
+            cookie = cookie.Replace("\r", "").Replace("\n", "").Trim();
+            if (!cookie.EndsWith(";")) cookie += ";";
+
+            var accountId = await PddPoolCore.UpdateCookies(cookie, user_agent, id);
+            bool success = accountId > 0;
+            if (success)
+            {
+                OperationLogCore.LogOperation(token.AccessKey, clientIp, $"pdd_pool:{accountId}:cookie", string.Empty, cookie);
+            }
+            return new APIResult(new
+            {
+                data = new { success, msg = success ? "更新成功" : "更新失败,请检查输入的cookie" },
+            });
+        }
+
+        /// <summary>
+        /// 账号列表
+        /// </summary>
+        /// <param name="form"></param>
+        /// <returns></returns>
+        [HttpPost]
+        public ActionResult list([FromBody] JsonElement form)
+        {
+            var token = provider.Get(_accessor.HttpContext);
+
+            int page = form.Read("current", 1);
+            int size = form.Read("pageSize", 10);
+            string keyword = form.Read("name", string.Empty);
+            bool getTotal = form.Read("getTotal", true);
+            string _status = form.Read("status", string.Empty);
+            int status = _status switch
+            {
+                "online" => 1,
+                "offline" => 0,
+                _ => -1,
+            };
+            var lastTime = form.PathReadArray<string>("lastTime[]");
+
+            string sort = form.Read<string>("sort");
+            string order = form.Read<string>("order");
+
+            bool show_hide = form.Read("show_hide", false);
+            string filter = string.Empty;
+            if (!show_hide) filter = "is_hide=0";
+            if (!string.IsNullOrEmpty(keyword))
+            {
+                filter += $" AND (`name` LIKE @keyword OR `description` LIKE @keyword)";
+                keyword = $"%{keyword}%";
+            }
+            DateTime stime = DateTime.MinValue, etime = DateTime.MinValue;
+            if (lastTime.Count == 2)
+            {
+                if (DateTime.TryParse(lastTime[0], out stime) && DateTime.TryParse(lastTime[1], out etime))
+                {
+                    etime = etime.AddDays(1).AddSeconds(-1);
+                    filter += $" AND last_time BETWEEN @stime AND @etime";
+                }
+            }
+            if (status != -1)
+            {
+                filter += $" AND status=@status";
+
+            }
+
+            filter = filter.StringTrimStart(" AND ");
+
+            //排序
+            string orderBy = "id DESC";
+            if (!string.IsNullOrEmpty(order))
+            {
+                order = "descending".Equals(order) ? "DESC" : "ASC";
+                orderBy = sort switch
+                {
+                    //"num" => $"num {order}",
+                    _ => $"{sort} {order}",
+                };
+            }
+
+            using var conn = DBContext.GetOpenConnection();
+            var result = new DBContext.Table(conn, "pdd_pool")
+                .Where(filter, new { keyword, status, stime, etime })
+                .Page(size, page)
+                .Order(orderBy)
+                .PageList<PddPoolDTO>(getTotal);
+
+            foreach (var item in result.List)
+            {
+                item.app_secret = string.Empty;
+                item.cookies = string.Empty;
+                item.riskCookie = RiskControlCore.GetRiskCookie($"pdd:{item.id}");
+            }
+            return new APIResult(new { data = result });
+        }
+
+        [HttpPost]
+        public async Task<ActionResult> update([FromBody] JsonElement form)
+        {
+            var clientIp = _accessor.HttpContext.GetUserIp();
+            var token = provider.Get(_accessor.HttpContext);
+
+            int id = form.Read<int>("id", 0);
+            string name = form.Read<string>("name", string.Empty);
+            string val = form.Read<string>("val", string.Empty);
+
+            if (id == 0)
+            {
+                return new APIResult(new { data = new { success = false, msg = "更新失败,请检查输入的cookie" } });
+            }
+
+            int result;
+            switch (name)
+            {
+                case "enable_parse":
+                case "enable_sync_revenue":
+                case "enable_sync_order":
+                case "enable_coupon":
+                case "cookie_status":
+                case "status":
+                    bool bVal = val.Equals("True");
+                    result = new DBContext.Table("pdd_pool")
+                           .Add(name, bVal)
+                           .Add("last_time", DateTime.Now)
+                           .Where("id=@id", new { id })
+                           .Update();
+
+                    if (bVal && ("status".Equals(name) || "enable_parse".Equals(name)))
+                    {
+                        PddPoolCore.InitializeNewAccountUsage(id);
+                    }
+                    break;
+                case "time_range":
+                    int.TryParse(val, out int iVal);
+                    result = new DBContext.Table("pdd_pool")
+                           .Add(name, iVal)
+                           .Add("last_time", DateTime.Now)
+                           .Where("id=@id", new { id })
+                           .Update();
+                    break;
+                case "balance":
+                    // todo 更新账户余额
+                    var account = new DBContext.Table("pdd_pool").Get<PddPoolDTO>("id=@id", new { id });
+                    var plus = new PddUnionPlus(account);
+                    result = await plus.queryMallBalance();
+                    break;
+                default:
+                    return new APIResult(new { data = new { success = false, msg = "更新失败,未授权操作" } });
+            }
+            bool success = result > 0;
+            if (success)
+            {
+                OperationLogCore.LogOperation(token.AccessKey, clientIp, $"pdd_pool:{id}:{name}", string.Empty, val);
+                await EndPointCore.NotifyReload(true);
+            }
+            return new APIResult(new
+            {
+                data = new { success, msg = success ? "更新成功" : "更新失败,请检查输入信息" },
+            });
+        }
+
+        [HttpPost]
+        public ActionResult estimate_revenue_total([FromBody] JsonElement form)
+        {
+            int page = form.Read("current", 1);
+            int size = form.Read("pageSize", 10);
+            bool getTotal = form.Read("getTotal", true);
+            string sort = form.Read<string>("sort");
+            string order = form.Read<string>("order");
+
+            var report_date = form.PathReadArray<string>("query_date[]");
+
+            string filter = string.Empty;
+            DateTime stime = DateTime.MinValue, etime = DateTime.MinValue;
+            if (report_date.Count == 2)
+            {
+                if (DateTime.TryParse(report_date[0], out stime) && DateTime.TryParse(report_date[1], out etime))
+                {
+                    etime = etime.AddDays(1).AddSeconds(-1);
+                    filter += $" AND report_date BETWEEN @stime AND @etime";
+                }
+            }
+
+            filter = filter.StringTrimStart(" AND ");
+
+            //排序
+            string orderBy = "report_date DESC";
+            if (!string.IsNullOrEmpty(order))
+            {
+                order = "descending".Equals(order) ? "DESC" : "ASC";
+                orderBy = sort switch
+                {
+                    _ => $"{sort} {order}",
+                };
+            }
+            var result = new DBContext.Table("v_pdd_estimate_revenue")
+                .Where(filter, new { stime, etime })
+                .Page(size, page)
+                .Order(orderBy)
+                .PageList<PddEstimateRevenueDTO>(getTotal);
+
+            return new APIResult(new { data = result });
+        }
+
+
+        [HttpGet]
+        public ActionResult RechargeAllOnlineAccountUsage()
+        {
+            int count = PddPoolCore.RechargeAllOnlineAccountUsage();
+            return new APIResult(new { msg = "ok", count });
+        }
+
+
+        [HttpPost]
+        public ActionResult estimate_revenue([FromBody] JsonElement form)
+        {
+            int page = form.Read("current", 1);
+            int size = form.Read("pageSize", 10);
+            bool getTotal = form.Read("getTotal", true);
+            string sort = form.Read<string>("sort");
+            string order = form.Read<string>("order");
+            string name = form.Read<string>("keyword");
+
+            var report_date = form.PathReadArray<string>("query_date[]");
+
+            string filter = string.Empty;
+            if (!string.IsNullOrEmpty(name))
+            {
+                filter += $" AND accountId IN (SELECT ID FROM pdd_pool WHERE company=@name AND is_hide=0)";
+            }
+            else
+            {
+                filter += $" AND accountId IN (SELECT ID FROM pdd_pool WHERE is_hide=0)";
+            }
+
+            DateTime stime = DateTime.MinValue, etime = DateTime.MinValue;
+            if (report_date.Count == 2)
+            {
+                if (DateTime.TryParse(report_date[0], out stime) && DateTime.TryParse(report_date[1], out etime))
+                {
+                    etime = etime.AddDays(1).AddSeconds(-1);
+                    filter += $" AND report_date BETWEEN @stime AND @etime";
+                }
+            }
+
+            filter = filter.StringTrimStart(" AND ");
+
+            //排序
+            string orderBy = "report_date DESC";
+            if (!string.IsNullOrEmpty(order))
+            {
+                order = "descending".Equals(order) ? "DESC" : "ASC";
+                orderBy = sort switch
+                {
+                    _ => $"{sort} {order}",
+                };
+            }
+
+            var result = new DBContext.Table("pdd_estimate_revenue")
+                .Where(filter, new { name, stime, etime })
+                .Page(size, page)
+                .Order(orderBy)
+                .PageList<PddEstimateRevenueDTO>(getTotal);
+
+            return new APIResult(new { data = result });
+        }
+
+    }
 }

+ 103 - 103
molilian.api/Controllers/admin/ProxyController.cs

@@ -1,104 +1,104 @@
-using molilian.core;
-using dodohold.core;
-using Microsoft.AspNetCore.Mvc;
-using Org.BouncyCastle.Ocsp;
-using System.Text.Json;
-using System.Runtime.InteropServices;
-using TencentCloud.Scf.V20180416.Models;
-using Quartz.Impl.AdoJobStore.Common;
-using TencentCloud.Gaap.V20180529.Models;
-using System.Net;
-using TencentCloud.Ie.V20200304.Models;
-using TencentCloud.Soe.V20180724.Models;
-
-namespace molilian.api.Controllers
-{
-    [ApiController]
-    [MyAuthorize("admin")]
-    [Route("api/[controller]/[action]")]
-    public class ProxyController : ControllerBase
-    {
-        readonly IAuthorizationProvider provider = new AdminProvider();
-        protected IHttpContextAccessor _accessor;
-        public ProxyController(IHttpContextAccessor accessor)
-        {
-            _accessor = accessor;
-        }
-
-        [HttpGet]
-        public ActionResult get()
-        {
-            var list = new DBContext.Table("proxy_nodes")
-                .Where("status=@status", new { status = 1 })
-                .Select<ProxyNodesDTO>();
-
-            // 参考数据
-            //61	1	6	品阅245	品阅辅助网卡	bj	http	http://172.21.127.245:33128	molilian	Bhquvmkn8zLO36lGjuqk	2024-04-09 14:25:53	2024-04-09 14:25:56
-            //59  1   6   品阅243 品阅辅助网卡  bj http    http://172.21.127.243:33128	molilian	Bhquvmkn8zLO36lGjuqk	2024-04-09 14:25:53	2024-04-09 14:25:56
-
-            foreach (var proxy in list)
-            {
-                //通过 curl myip.ipip.net检测proxy可访问行
-            }
-
-
-            var tasks = new List<Task<int>>
-                {
-                Task.Run(()=>{ })
-             };
-
-            // 等待所有任务完成
-            Task.WhenAll(tasks).Wait();
-
-
-
-            return new APIResult(new { list });
-        }
-
-
-        [HttpGet]
-        public async Task<ActionResult> ChangePublicIpByName(string nodeName)
-        {
-
-            var proxy_node = new DBContext.Table("proxy_nodes").Get<dynamic>("name=@nodeName", new { nodeName });
-            if (proxy_node == null) return new APIResult(new { success = false, msg = "没有匹配的 proxy_nodes 记录" });
-
-            int aliyun_id = proxy_node.aliyun_id;
-            string proxy_server = proxy_node.server;
-
-            var account = new DBContext.Table("aliyun_pool").Get<AliyunPoolDTO>(aliyun_id);
-            if (account == null) return new APIResult(new { success = false, msg = "没有匹配的 aliyun_pool 记录" });
-
-
-            var uri = new Uri(proxy_server);
-            string privateIp = uri.Host;
-            AliyunCore core = new AliyunCore(account);
-            var success = await core.ChangePublicIpAsync(privateIp);
-            return new APIResult(new { success = "ok" });
-        }
-
-        [HttpGet]
-        public async Task<ActionResult> ChangePublicIp(int id)
-        {
-            id = id >= 20000 ? id - 20000 : id;
-
-            var proxy_node = new DBContext.Table("proxy_nodes").Get<dynamic>(id);
-            if (proxy_node == null) return new APIResult(new { success = false, msg = "没有匹配的 proxy_nodes 记录" });
-
-            int aliyun_id = proxy_node.aliyun_id;
-            string proxy_server = proxy_node.server;
-
-            var account = new DBContext.Table("aliyun_pool").Get<AliyunPoolDTO>(aliyun_id);
-            if (account == null) return new APIResult(new { success = false, msg = "没有匹配的 aliyun_pool 记录" });
-
-
-
-            var uri = new Uri(proxy_server);
-            string privateIp = uri.Host;
-            AliyunCore core = new AliyunCore(account);
-            var success = await core.ChangePublicIpAsync(privateIp);
-            return new APIResult(new { success = "ok" });
-        }
-
-    }
+using molilian.core;
+using dodohold.core;
+using Microsoft.AspNetCore.Mvc;
+using Org.BouncyCastle.Ocsp;
+using System.Text.Json;
+using System.Runtime.InteropServices;
+using TencentCloud.Scf.V20180416.Models;
+using Quartz.Impl.AdoJobStore.Common;
+using TencentCloud.Gaap.V20180529.Models;
+using System.Net;
+using TencentCloud.Ie.V20200304.Models;
+using TencentCloud.Soe.V20180724.Models;
+
+namespace molilian.api.Controllers
+{
+    [ApiController]
+    [MyAuthorize("admin")]
+    [Route("api/[controller]/[action]")]
+    public class ProxyController : ControllerBase
+    {
+        readonly IAuthorizationProvider provider = new AdminProvider();
+        protected IHttpContextAccessor _accessor;
+        public ProxyController(IHttpContextAccessor accessor)
+        {
+            _accessor = accessor;
+        }
+
+        [HttpGet]
+        public ActionResult get()
+        {
+            var list = new DBContext.Table("proxy_nodes")
+                .Where("status=@status", new { status = 1 })
+                .Select<ProxyNodesDTO>();
+
+            // 参考数据
+            //61	1	6	品阅245	品阅辅助网卡	bj	http	http://172.21.127.245:33128	molilian	Bhquvmkn8zLO36lGjuqk	2024-04-09 14:25:53	2024-04-09 14:25:56
+            //59  1   6   品阅243 品阅辅助网卡  bj http    http://172.21.127.243:33128	molilian	Bhquvmkn8zLO36lGjuqk	2024-04-09 14:25:53	2024-04-09 14:25:56
+
+            foreach (var proxy in list)
+            {
+                //通过 curl myip.ipip.net检测proxy可访问行
+            }
+
+
+            var tasks = new List<Task<int>>
+                {
+                Task.Run(()=>{ })
+             };
+
+            // 等待所有任务完成
+            Task.WhenAll(tasks).Wait();
+
+
+
+            return new APIResult(new { list });
+        }
+
+
+        [HttpGet]
+        public async Task<ActionResult> ChangePublicIpByName(string nodeName)
+        {
+
+            var proxy_node = new DBContext.Table("proxy_nodes").Get<dynamic>("name=@nodeName", new { nodeName });
+            if (proxy_node == null) return new APIResult(new { success = false, msg = "没有匹配的 proxy_nodes 记录" });
+
+            int aliyun_id = proxy_node.aliyun_id;
+            string proxy_server = proxy_node.server;
+
+            var account = new DBContext.Table("aliyun_pool").Get<AliyunPoolDTO>(aliyun_id);
+            if (account == null) return new APIResult(new { success = false, msg = "没有匹配的 aliyun_pool 记录" });
+
+
+            var uri = new Uri(proxy_server);
+            string privateIp = uri.Host;
+            AliyunCore core = new AliyunCore(account);
+            var success = await core.ChangePublicIpAsync(privateIp);
+            return new APIResult(new { success = "ok" });
+        }
+
+        [HttpGet]
+        public async Task<ActionResult> ChangePublicIp(int id)
+        {
+            id = id >= 20000 ? id - 20000 : id;
+
+            var proxy_node = new DBContext.Table("proxy_nodes").Get<dynamic>(id);
+            if (proxy_node == null) return new APIResult(new { success = false, msg = "没有匹配的 proxy_nodes 记录" });
+
+            int aliyun_id = proxy_node.aliyun_id;
+            string proxy_server = proxy_node.server;
+
+            var account = new DBContext.Table("aliyun_pool").Get<AliyunPoolDTO>(aliyun_id);
+            if (account == null) return new APIResult(new { success = false, msg = "没有匹配的 aliyun_pool 记录" });
+
+
+
+            var uri = new Uri(proxy_server);
+            string privateIp = uri.Host;
+            AliyunCore core = new AliyunCore(account);
+            var success = await core.ChangePublicIpAsync(privateIp);
+            return new APIResult(new { success = "ok" });
+        }
+
+    }
 }

+ 135 - 135
molilian.api/Controllers/admin/ReportController.cs

@@ -1,136 +1,136 @@
-using molilian.core;
-using dodohold.core;
-using Microsoft.AspNetCore.Mvc;
-using System.Text.Json;
-using static molilian.core.ChartDataCore;
-using Org.BouncyCastle.Asn1;
-namespace molilian.api.Controllers
-{
-    [ApiController]
-    [MyAuthorize("admin")]
-    [Route("api/[controller]/[action]")]
-    public class ReportController : ControllerBase
-    {
-        readonly IAuthorizationProvider provider = new AdminProvider();
-        protected IHttpContextAccessor _accessor;
-        public ReportController(IHttpContextAccessor accessor)
-        {
-            _accessor = accessor;
-        }
-
-        [HttpPost]
-        public async Task<ActionResult> daily_list([FromBody] JsonElement form)
-        {
-            var token = provider.Get(_accessor.HttpContext);
-
-            int page = form.Read("current", 1);
-            int size = form.Read("pageSize", 10);
-            bool getTotal = form.Read("getTotal", true);
-
-            string sort = form.Read<string>("sort");
-            string order = form.Read<string>("order");
-            string filter = string.Empty;
-
-            var log_date = form.PathReadArray<string>("query_date[]");
-            var accountName = form.Read("accountName", string.Empty);
-            int channel = form.Read("channel", -1);
-
-            if (!string.IsNullOrEmpty(accountName))
-            {
-                filter += $" AND accountName=@accountName";
-            }
-            else
-            {
-                filter += $" AND accountName!='all'";
-            }
-            DateTime stime = DateTime.MinValue, etime = DateTime.MinValue;
-            if (log_date.Count == 2)
-            {
-                if (DateTime.TryParse(log_date[0], out stime) && DateTime.TryParse(log_date[1], out etime))
-                {
-                    etime = etime.AddDays(1).AddSeconds(-1);
-                    if (etime > DateTime.Now) etime = DateTime.Now;
-                    filter += $" AND log_date BETWEEN @stime AND @etime";
-                }
-            }
-            if (channel != -1)
-            {
-                filter += $" AND channel=@channel";
-            }
-            filter = filter.StringTrimStart(" AND ");
-
-            //排序
-            string orderBy = "id DESC";
-            if (!string.IsNullOrEmpty(order))
-            {
-                order = "descending".Equals(order) ? "DESC" : "ASC";
-                orderBy = sort switch
-                {
-                    _ => $"{sort} {order}",
-                };
-            }
-
-            using var conn = CenterHub.GetOpenConnection();
-            var result = new DBContext.Table(conn, "center_daily_logs")
-                .Where(filter, new { channel, accountName, stime, etime })
-                .Page(size, page)
-                .Order(orderBy)
-                .PageList<DailyLogsDTO>(getTotal);
-
-            var jd_accounts = new DBContext.Table("jd_pool").Select<JdPoolDTO>();
-            var jd_hide_ids = jd_accounts.Where(e => e.is_hide).Select(e => e.id).ToList();
-            result.List = result.List.Where(e => e.channel != 1 || !jd_hide_ids.Contains(e.accountId));
-
-
-            var tb_accounts = new DBContext.Table("tk_pool").Select<TkPoolDTO>();
-            var tb_hide_ids = tb_accounts.Where(e => e.is_hide).Select(e => e.id).ToList();
-            result.List = result.List.Where(e => e.channel != 0 || !tb_hide_ids.Contains(e.accountId));
-
-            var pdd_accounts = new DBContext.Table("pdd_pool").Select<PddPoolDTO>();
-            var pdd_hide_ids = pdd_accounts.Where(e => e.is_hide).Select(e => e.id).ToList();
-            result.List = result.List.Where(e => e.channel != 3 || !pdd_hide_ids.Contains(e.accountId));
-
-            return new APIResult(new { data = result });
-        }
-
-        [HttpPost]
-        public async Task<ActionResult> chartData([FromBody] JsonElement form)
-        {
-            var result = new ChartDataRes();
-
-            var report_date = form.PathReadArray<string>("query_date[]");
-            var accountName = form.Read("accountName", string.Empty);
-            var isHourtrend = form.Read<bool>("isLeaf", false);
-            var total_key = form.Read("total_key", "total");
-
-            DateTime stime = DateTime.MinValue;
-            if (!DateTime.TryParse(report_date[0], out stime)) return new APIResult(result);
-
-            string _report_date = stime.ToString("yyyyMMdd");
-            if (isHourtrend) _report_date = stime.ToString("yyyyMMddHH");
-
-
-
-            if (string.IsNullOrEmpty(accountName)) accountName = "tb";
-
-            switch (total_key)
-            {
-                case "tool_total":
-                    result = await GetToolDataAsync(total_key, accountName, _report_date);
-                    break;
-                case "total":
-                case "parse_total":
-                case "coupon_total":
-                    result = await GetTaobaoDataAsync(total_key, accountName, _report_date);
-                    break;
-                case "jd_parse_total":
-                    result = await GetJdDataAsync(total_key, accountName, _report_date);
-
-                    break;
-            }
-            return new APIResult(result);
-        }
-
-
-    }
+using molilian.core;
+using dodohold.core;
+using Microsoft.AspNetCore.Mvc;
+using System.Text.Json;
+using static molilian.core.ChartDataCore;
+using Org.BouncyCastle.Asn1;
+namespace molilian.api.Controllers
+{
+    [ApiController]
+    [MyAuthorize("admin")]
+    [Route("api/[controller]/[action]")]
+    public class ReportController : ControllerBase
+    {
+        readonly IAuthorizationProvider provider = new AdminProvider();
+        protected IHttpContextAccessor _accessor;
+        public ReportController(IHttpContextAccessor accessor)
+        {
+            _accessor = accessor;
+        }
+
+        [HttpPost]
+        public async Task<ActionResult> daily_list([FromBody] JsonElement form)
+        {
+            var token = provider.Get(_accessor.HttpContext);
+
+            int page = form.Read("current", 1);
+            int size = form.Read("pageSize", 10);
+            bool getTotal = form.Read("getTotal", true);
+
+            string sort = form.Read<string>("sort");
+            string order = form.Read<string>("order");
+            string filter = string.Empty;
+
+            var log_date = form.PathReadArray<string>("query_date[]");
+            var accountName = form.Read("accountName", string.Empty);
+            int channel = form.Read("channel", -1);
+
+            if (!string.IsNullOrEmpty(accountName))
+            {
+                filter += $" AND accountName=@accountName";
+            }
+            else
+            {
+                filter += $" AND accountName!='all'";
+            }
+            DateTime stime = DateTime.MinValue, etime = DateTime.MinValue;
+            if (log_date.Count == 2)
+            {
+                if (DateTime.TryParse(log_date[0], out stime) && DateTime.TryParse(log_date[1], out etime))
+                {
+                    etime = etime.AddDays(1).AddSeconds(-1);
+                    if (etime > DateTime.Now) etime = DateTime.Now;
+                    filter += $" AND log_date BETWEEN @stime AND @etime";
+                }
+            }
+            if (channel != -1)
+            {
+                filter += $" AND channel=@channel";
+            }
+            filter = filter.StringTrimStart(" AND ");
+
+            //排序
+            string orderBy = "id DESC";
+            if (!string.IsNullOrEmpty(order))
+            {
+                order = "descending".Equals(order) ? "DESC" : "ASC";
+                orderBy = sort switch
+                {
+                    _ => $"{sort} {order}",
+                };
+            }
+
+            using var conn = CenterHub.GetOpenConnection();
+            var result = new DBContext.Table(conn, "center_daily_logs")
+                .Where(filter, new { channel, accountName, stime, etime })
+                .Page(size, page)
+                .Order(orderBy)
+                .PageList<DailyLogsDTO>(getTotal);
+
+            var jd_accounts = new DBContext.Table("jd_pool").Select<JdPoolDTO>();
+            var jd_hide_ids = jd_accounts.Where(e => e.is_hide).Select(e => e.id).ToList();
+            result.List = result.List.Where(e => e.channel != 1 || !jd_hide_ids.Contains(e.accountId));
+
+
+            var tb_accounts = new DBContext.Table("tk_pool").Select<TkPoolDTO>();
+            var tb_hide_ids = tb_accounts.Where(e => e.is_hide).Select(e => e.id).ToList();
+            result.List = result.List.Where(e => e.channel != 0 || !tb_hide_ids.Contains(e.accountId));
+
+            var pdd_accounts = new DBContext.Table("pdd_pool").Select<PddPoolDTO>();
+            var pdd_hide_ids = pdd_accounts.Where(e => e.is_hide).Select(e => e.id).ToList();
+            result.List = result.List.Where(e => e.channel != 3 || !pdd_hide_ids.Contains(e.accountId));
+
+            return new APIResult(new { data = result });
+        }
+
+        [HttpPost]
+        public async Task<ActionResult> chartData([FromBody] JsonElement form)
+        {
+            var result = new ChartDataRes();
+
+            var report_date = form.PathReadArray<string>("query_date[]");
+            var accountName = form.Read("accountName", string.Empty);
+            var isHourtrend = form.Read<bool>("isLeaf", false);
+            var total_key = form.Read("total_key", "total");
+
+            DateTime stime = DateTime.MinValue;
+            if (!DateTime.TryParse(report_date[0], out stime)) return new APIResult(result);
+
+            string _report_date = stime.ToString("yyyyMMdd");
+            if (isHourtrend) _report_date = stime.ToString("yyyyMMddHH");
+
+
+
+            if (string.IsNullOrEmpty(accountName)) accountName = "tb";
+
+            switch (total_key)
+            {
+                case "tool_total":
+                    result = await GetToolDataAsync(total_key, accountName, _report_date);
+                    break;
+                case "total":
+                case "parse_total":
+                case "coupon_total":
+                    result = await GetTaobaoDataAsync(total_key, accountName, _report_date);
+                    break;
+                case "jd_parse_total":
+                    result = await GetJdDataAsync(total_key, accountName, _report_date);
+
+                    break;
+            }
+            return new APIResult(result);
+        }
+
+
+    }
 }

+ 993 - 993
molilian.api/Controllers/admin/TaobaoController.cs

@@ -1,994 +1,994 @@
-using molilian.core;
-using dodohold.core;
-using Microsoft.AspNetCore.Mvc;
-using System.Text.Json;
-using System.Net;
-using static QRCoder.PayloadGenerator;
-using MySqlX.XDevAPI;
-using OfficeOpenXml.FormulaParsing.LexicalAnalysis;
-using StackExchange.Redis;
-namespace molilian.api.Controllers
-{
-
-
-    [ApiController]
-    [MyAuthorize("admin")]
-    [Route("api/[controller]/[action]")]
-    [Route("coupon_api/[controller]/[action]")]
-    public class TaobaoController : ControllerBase
-    {
-        readonly IAuthorizationProvider provider = new AdminProvider();
-        protected IHttpContextAccessor _accessor;
-        public TaobaoController(IHttpContextAccessor accessor)
-        {
-            _accessor = accessor;
-        }
-
-        [HttpPost]
-        public async Task<ActionResult> updateCookies([FromBody] JsonElement form)
-        {
-            var clientIp = _accessor.HttpContext.GetUserIp();
-            var token = provider.Get(_accessor.HttpContext);
-            string user_agent = _accessor.HttpContext.Request.UserAgent();
-
-
-            string cookie = form.Read<string>("cookie", string.Empty);
-            var accountId = await TkPoolCore.UpdateCookies(cookie, user_agent);
-            bool success = accountId > 0;
-            if (success)
-            {
-                //string desc = ObjectComparer.PrintCompareToString(old, form).Trim();
-                OperationLogCore.LogOperation(token.AccessKey, clientIp, $"tk_pool:{accountId}:cookie", string.Empty, cookie);
-            }
-            return new APIResult(new
-            {
-                data = new { success, msg = success ? "更新成功" : "更新失败,请检查输入的cookie" },
-            });
-        }
-
-
-        [HttpPost]
-        public async Task<ActionResult> update([FromBody] JsonElement form)
-        {
-            var clientIp = _accessor.HttpContext.GetUserIp();
-            var token = provider.Get(_accessor.HttpContext);
-
-            int id = form.Read<int>("id", 0);
-            string name = form.Read<string>("name", string.Empty);
-            string val = form.Read<string>("val", string.Empty);
-
-            if (id == 0)
-            {
-                return new APIResult(new { data = new { success = false, msg = "更新失败,请检查输入的cookie" } });
-            }
-
-            int result;
-            switch (name)
-            {
-                case "enable_fake_click":
-                case "enable_parse":
-                case "enable_coupon":
-                case "enable_sync_order":
-                case "enable_promotionQuery":
-                case "enable_deep_url":
-                case "status":
-                    bool bVal = val.Equals("True");
-                    result = new DBContext.Table("tk_pool")
-                           .Add(name, bVal)
-                           .Add("last_time", DateTime.Now)
-                           .Where("id=@id", new { id })
-                           .Update();
-                    break;
-                case "check_status":
-                    //检查状态
-                    var arr = EndPointCore.NotifyCheckDeepUrl(id);
-                    string suspended_endpoint = string.Empty;
-                    var log = new LoggerLibrary("check_suspended_endpoint");
-
-                    foreach ((string node, string message) in arr)
-                    {
-                        log.Info(node, message);
-                        if (message.Contains("霸下验证码"))
-                        {
-                            suspended_endpoint += $"{node},";
-                        }
-                    }
-                    result = new DBContext.Table("tk_pool")
-                           .Add("suspended_endpoint", suspended_endpoint)
-                           .Add("last_time", DateTime.Now)
-                           .Where("id=@id", new { id })
-                           .Update();
-                    log.SaveAsync();
-                    break;
-                case "time_range":
-                    int.TryParse(val, out int iVal);
-                    result = new DBContext.Table("tk_pool")
-                           .Add(name, iVal)
-                           .Add("last_time", DateTime.Now)
-                           .Where("id=@id", new { id })
-                           .Update();
-                    break;
-                default:
-                    return new APIResult(new { data = new { success = false, msg = "更新失败,未授权操作" } });
-            }
-            bool success = result > 0;
-            if (success)
-            {
-                //string desc = ObjectComparer.PrintCompareToString(old, form).Trim();
-                OperationLogCore.LogOperation(token.AccessKey, clientIp, $"tk_pool:{id}:{name}", string.Empty, val);
-                await EndPointCore.NotifyReload(true);
-
-            }
-            return new APIResult(new
-            {
-                data = new { success, msg = success ? "更新成功" : "更新失败,请检查输入信息" },
-            });
-        }
-
-
-        /// <summary>
-        /// 账号列表
-        /// </summary>
-        /// <param name="form"></param>
-        /// <returns></returns>
-        [HttpPost]
-        public async Task<ActionResult> list([FromBody] JsonElement form)
-        {
-            var token = provider.Get(_accessor.HttpContext);
-
-            int page = form.Read("current", 1);
-            int size = form.Read("pageSize", 10);
-            string keyword = form.Read("name", string.Empty);
-            bool getTotal = form.Read("getTotal", true);
-            string _status = form.Read("status", string.Empty);
-            int status = _status switch
-            {
-                "online" => 1,
-                "offline" => 0,
-                _ => -1,
-            };
-            var lastTime = form.PathReadArray<string>("lastTime[]");
-
-            string sort = form.Read<string>("sort");
-            string order = form.Read<string>("order");
-
-            bool show_hide = form.Read("show_hide", false);
-            string filter = string.Empty;
-            if (!show_hide) filter = "is_hide=0";
-
-            if (!string.IsNullOrEmpty(keyword))
-            {
-                filter += $" AND (`name` LIKE @keyword OR `description` LIKE @keyword)";
-                keyword = $"%{keyword}%";
-            }
-            DateTime stime = DateTime.MinValue, etime = DateTime.MinValue;
-            if (lastTime.Count == 2)
-            {
-                if (DateTime.TryParse(lastTime[0], out stime) && DateTime.TryParse(lastTime[1], out etime))
-                {
-                    etime = etime.AddDays(1).AddSeconds(-1);
-                    filter += $" AND last_time BETWEEN @stime AND @etime";
-                }
-            }
-            if (status != -1)
-            {
-                filter += $" AND status=@status";
-
-            }
-
-            filter = filter.StringTrimStart(" AND ");
-
-            //排序
-            string orderBy = "id DESC";
-            if (!string.IsNullOrEmpty(order))
-            {
-                order = "descending".Equals(order) ? "DESC" : "ASC";
-                orderBy = sort switch
-                {
-                    //"num" => $"num {order}",
-                    _ => $"{sort} {order},ID DESC",
-                };
-            }
-
-            using var conn = DBContext.GetOpenConnection();
-            var result = new DBContext.Table(conn, "tk_pool")
-                .Where(filter, new { keyword, status, stime, etime })
-                .Page(size, page)
-                .Order(orderBy)
-                .PageList<TkPoolDTO>(getTotal);
-
-            foreach (var item in result.List)
-            {
-                item.current_amt = Math.Round(RiskControlCore.GetIncomeAmt(TkChannelEnum.tb, $"{item.id}"), 2);
-                item.cookies = string.Empty;
-                //await RiskControlCore.SetRiskCookie($"tb:{item.id}", "TEST");
-                item.riskCookie = RiskControlCore.GetRiskCookie($"tb:{item.id}");
-            }
-            return new APIResult(new { data = result });
-        }
-
-
-        [HttpPost]
-        public async Task<ActionResult> report_dailys([FromBody] JsonElement form)
-        {
-            int page = form.Read("current", 1);
-            int size = form.Read("pageSize", 10);
-            bool getTotal = form.Read("getTotal", true);
-            string sort = form.Read<string>("sort");
-            string order = form.Read<string>("order");
-
-            var report_date = form.PathReadArray<string>("query_date[]");
-            string dailys_accountName = "all";
-            int dailys_accountId = 0;
-
-            string filter = string.Empty;
-            DateTime stime = DateTime.MinValue, etime = DateTime.MinValue;
-            if (report_date.Count == 2)
-            {
-                if (DateTime.TryParse(report_date[0], out stime) && DateTime.TryParse(report_date[1], out etime))
-                {
-                    etime = etime.AddDays(1).AddSeconds(-1);
-                    filter += $" AND A.report_date BETWEEN @stime AND @etime";
-                }
-            }
-
-            filter = filter.StringTrimStart(" AND ");
-
-            //排序
-            string orderBy = "A.report_date DESC";
-            if (!string.IsNullOrEmpty(order))
-            {
-                order = "descending".Equals(order) ? "DESC" : "ASC";
-                orderBy = sort switch
-                {
-                    _ => $"A.{sort} {order}",
-                };
-            }
-
-            var result = new DBContext.Table("v_tk_report_dailys")
-                .LeftJoin("daily_logs",
-                "B.log_date = A.report_date AND B.channel=0 AND B.accountId=@dailys_accountId",
-                "total_count, B.abandon_count, B.success_count, B.abandon_percentage, B.success_percentage," +
-                "B.parse_total_count, B.parse_abandon_count, B.parse_success_count, B.parse_abandon_percentage, B.parse_success_percentage," +
-                "B.coupon_total_count, B.coupon_abandon_count, B.coupon_success_count, B.coupon_abandon_percentage, B.coupon_success_percentage," +
-                "B.tool_total_count, B.tool_abandon_count, B.tool_success_count, B.tool_abandon_percentage, B.tool_success_percentage")
-                .Where(filter, new { dailys_accountId, dailys_accountName, stime, etime })
-                .Page(size, page)
-                .Order(orderBy)
-                .PageList<TkReportDTO>(getTotal);
-
-            foreach (var item in result.List)
-            {
-                if (item.report_date.Date < DateTime.Now.Date) continue;
-                if (item.total_count == 0)
-                {
-                    item.total_count = await TkLogCore.GetTotalAsync($":total:tb:{item.report_date:yyyyMMdd}");
-                    if (item.total_count > 0)
-                    {
-                        item.success_count = await TkLogCore.GetTotalAsync($":total:tb:success:{item.report_date:yyyyMMdd}");
-                        item.abandon_count = await TkLogCore.GetTotalAsync($":total:tb:放弃转链:{item.report_date:yyyyMMdd}");
-                        if (item.total_count > 0)
-                        {
-                            item.success_percentage = $"{item.success_count / (double)item.total_count * 100:f2}%";
-                            item.abandon_percentage = $"{item.abandon_count / (double)item.total_count * 100:f2}%";
-                        }
-                    }
-                }
-
-                if (item.parse_total_count == 0)
-                {
-                    item.parse_total_count = await TkLogCore.GetTotalAsync($":parse_total:tb:{item.report_date:yyyyMMdd}");
-                    if (item.parse_total_count > 0)
-                    {
-                        item.parse_success_count = await TkLogCore.GetTotalAsync($":parse_total:tb:success:{item.report_date:yyyyMMdd}");
-                        item.parse_abandon_count = await TkLogCore.GetTotalAsync($":parse_total:tb:放弃转链:{item.report_date:yyyyMMdd}");
-                        if (item.parse_total_count > 0)
-                        {
-                            item.parse_success_percentage = $"{item.parse_success_count / (double)item.parse_total_count * 100:f2}%";
-                            item.parse_abandon_percentage = $"{item.parse_abandon_count / (double)item.parse_total_count * 100:f2}%";
-                        }
-                    }
-                }
-
-                if (item.coupon_total_count == 0)
-                {
-                    item.coupon_total_count = await TkLogCore.GetTotalAsync($":coupon_total:tb:{item.report_date:yyyyMMdd}", false);
-                    if (item.coupon_total_count > 0)
-                    {
-                        item.coupon_success_count = await TkLogCore.GetTotalAsync($":coupon_total:tb:success:{item.report_date:yyyyMMdd}", false);
-                        item.coupon_abandon_count = await TkLogCore.GetTotalAsync($":coupon_total:tb:放弃转链:{item.report_date:yyyyMMdd}", false);
-                        if (item.coupon_total_count > 0)
-                        {
-                            item.coupon_success_percentage = $"{item.coupon_success_count / (double)item.coupon_total_count * 100:f2}%";
-                            item.coupon_abandon_percentage = $"{item.coupon_abandon_count / (double)item.coupon_total_count * 100:f2}%";
-                        }
-                    }
-                }
-
-                if (item.tool_total_count == 0)
-                {
-                    item.tool_total_count = await TkLogCore.GetTotalAsync($":parse_total:tool:{item.report_date:yyyyMMdd}");
-                    if (item.tool_total_count > 0)
-                    {
-                        item.tool_success_count = await TkLogCore.GetTotalAsync($":parse_total:tool:success:{item.report_date:yyyyMMdd}");
-                        item.tool_abandon_count = await TkLogCore.GetTotalAsync($":parse_total:tool:放弃转链:{item.report_date:yyyyMMdd}");
-                        if (item.tool_total_count > 0)
-                        {
-                            item.tool_success_percentage = $"{item.tool_success_count / (double)item.tool_total_count * 100:f2}%";
-                            item.tool_abandon_percentage = $"{item.tool_abandon_count / (double)item.tool_total_count * 100:f2}%";
-                        }
-
-                    }
-                }
-            }
-            return new APIResult(new
-            {
-                data = result
-            });
-        }
-
-
-        [HttpPost]
-        public async Task<ActionResult> report_hourlys([FromBody] JsonElement form)
-        {
-            int page = form.Read("current", 1);
-            int size = form.Read("pageSize", 10);
-            bool getTotal = form.Read("getTotal", true);
-            string sort = form.Read<string>("sort");
-            string order = form.Read<string>("order");
-
-            var report_date = form.PathReadArray<string>("query_date[]");
-            var filterHours = form.Read<int?>("filterHours", null);
-            string dailys_accountName = "all";
-            int dailys_accountId = 0;
-
-            string filter = string.Empty;
-            DateTime stime = DateTime.MinValue, etime = DateTime.MinValue;
-            if (report_date.Count == 2)
-            {
-                if (DateTime.TryParse(report_date[0], out stime) && DateTime.TryParse(report_date[1], out etime))
-                {
-                    etime = etime.AddDays(1).AddSeconds(-1);
-                    filter += $" AND A.report_date BETWEEN @stime AND @etime";
-                }
-            }
-
-            if (filterHours != null)
-            {
-                filterHours--;
-                filter += $" AND HOUR(report_date) = @filterHours";
-            }
-
-            filter = filter.StringTrimStart(" AND ");
-
-            //排序
-            string orderBy = "A.report_date DESC";
-            if (!string.IsNullOrEmpty(order))
-            {
-                order = "descending".Equals(order) ? "DESC" : "ASC";
-                orderBy = sort switch
-                {
-                    _ => $"A.{sort} {order}",
-                };
-            }
-
-            var result = new DBContext.Table("v_tk_report_hourlys")
-                .LeftJoin("daily_logs",
-                "B.log_date = A.report_date AND B.channel=0 AND B.accountId=@dailys_accountId",
-                "total_count, B.abandon_count, B.success_count, B.abandon_percentage, B.success_percentage," +
-                "B.parse_total_count, B.parse_abandon_count, B.parse_success_count, B.parse_abandon_percentage, B.parse_success_percentage," +
-                "B.coupon_total_count, B.coupon_abandon_count, B.coupon_success_count, B.coupon_abandon_percentage, B.coupon_success_percentage," +
-                "B.tool_total_count, B.tool_abandon_count, B.tool_success_count, B.tool_abandon_percentage, B.tool_success_percentage")
-                .Where(filter, new { dailys_accountId, dailys_accountName, filterHours, stime, etime })
-                .Page(size, page)
-                .Order(orderBy)
-                .PageList<TkReportDTO>(getTotal);
-
-            foreach (var item in result.List)
-            {
-                if (item.total_count == 0)
-                {
-                    item.total_count = await TkLogCore.GetTotalAsync($":total:tb:{item.report_date:yyyyMMddHH}");
-                    if (item.total_count > 0)
-                    {
-                        item.success_count = await TkLogCore.GetTotalAsync($":total:tb:success:{item.report_date:yyyyMMddHH}");
-                        item.abandon_count = await TkLogCore.GetTotalAsync($":total:tb:放弃转链:{item.report_date:yyyyMMddHH}");
-                        if (item.total_count > 0)
-                        {
-                            item.success_percentage = $"{item.success_count / (double)item.total_count * 100:f2}%";
-                            item.abandon_percentage = $"{item.abandon_count / (double)item.total_count * 100:f2}%";
-                        }
-                    }
-                }
-
-                if (item.parse_total_count == 0)
-                {
-                    item.parse_total_count = await TkLogCore.GetTotalAsync($":parse_total:tb:{item.report_date:yyyyMMddHH}");
-                    if (item.parse_total_count > 0)
-                    {
-                        item.parse_success_count = await TkLogCore.GetTotalAsync($":parse_total:tb:success:{item.report_date:yyyyMMddHH}");
-                        item.parse_abandon_count = await TkLogCore.GetTotalAsync($":parse_total:tb:放弃转链:{item.report_date:yyyyMMddHH}");
-                        if (item.parse_total_count > 0)
-                        {
-                            item.parse_success_percentage = $"{item.parse_success_count / (double)item.parse_total_count * 100:f2}%";
-                            item.parse_abandon_percentage = $"{item.parse_abandon_count / (double)item.parse_total_count * 100:f2}%";
-                        }
-                    }
-                }
-
-                if (item.coupon_total_count == 0)
-                {
-                    item.coupon_total_count = await TkLogCore.GetTotalAsync($":coupon_total:tb:{item.report_date:yyyyMMddHH}", false);
-                    if (item.coupon_total_count > 0)
-                    {
-                        item.coupon_success_count = await TkLogCore.GetTotalAsync($":coupon_total:tb:success:{item.report_date:yyyyMMddHH}", false);
-                        item.coupon_abandon_count = await TkLogCore.GetTotalAsync($":coupon_total:tb:放弃转链:{item.report_date:yyyyMMddHH}", false);
-                        if (item.coupon_total_count > 0)
-                        {
-                            item.coupon_success_percentage = $"{item.coupon_success_count / (double)item.coupon_total_count * 100:f2}%";
-                            item.coupon_abandon_percentage = $"{item.coupon_abandon_count / (double)item.coupon_total_count * 100:f2}%";
-                        }
-                    }
-                }
-
-                if (item.tool_total_count == 0)
-                {
-                    item.tool_total_count = await TkLogCore.GetTotalAsync($":parse_total:tool:{item.report_date:yyyyMMddHH}");
-                    if (item.tool_total_count > 0)
-                    {
-                        item.tool_success_count = await TkLogCore.GetTotalAsync($":parse_total:tool:success:{item.report_date:yyyyMMddHH}");
-                        item.tool_abandon_count = await TkLogCore.GetTotalAsync($":parse_total:tool:放弃转链:{item.report_date:yyyyMMddHH}");
-                        if (item.tool_total_count > 0)
-                        {
-                            item.tool_success_percentage = $"{item.tool_success_count / (double)item.tool_total_count * 100:f2}%";
-                            item.tool_abandon_percentage = $"{item.tool_abandon_count / (double)item.tool_total_count * 100:f2}%";
-                        }
-
-                    }
-                }
-            }
-            return new APIResult(new
-            {
-                data = result
-            });
-        }
-
-        [HttpPost]
-        public async Task<ActionResult> report_list([FromBody] JsonElement form)
-        {
-            int page = form.Read("current", 1);
-            int size = form.Read("pageSize", 10);
-            bool getTotal = form.Read("getTotal", true);
-            string sort = form.Read<string>("sort");
-            string order = form.Read<string>("order");
-
-            var report_date = form.PathReadArray<string>("query_date[]");
-            string accountName = form.Read<string>("accountName");
-
-            string filter = string.Empty;
-            if (!string.IsNullOrEmpty(accountName))
-            {
-                filter += $" AND accountName=@accountName";
-            }
-
-            DateTime stime = DateTime.MinValue, etime = DateTime.MinValue;
-            if (report_date.Count == 2)
-            {
-                if (DateTime.TryParse(report_date[0], out stime) && DateTime.TryParse(report_date[1], out etime))
-                {
-                    etime = etime.AddDays(1).AddSeconds(-1);
-                    filter += $" AND report_date BETWEEN @stime AND @etime";
-                }
-            }
-
-            filter = filter.StringTrimStart(" AND ");
-
-            //排序
-            string orderBy = "id DESC";
-            if (!string.IsNullOrEmpty(order))
-            {
-                order = "descending".Equals(order) ? "DESC" : "ASC";
-                orderBy = sort switch
-                {
-                    _ => $"{sort} {order}",
-                };
-            }
-
-            var result = new DBContext.Table("tk_report")
-                .Where(filter, new { accountName, stime, etime })
-                .Page(size, page)
-                .Order(orderBy)
-                .PageList<TkReportDTO>(getTotal);
-
-
-            var tb_accounts = await TkPoolCore.AllListAsync();
-            var tb_hide_ids = tb_accounts.Where(e => e.is_hide).Select(e => e.id).ToList();
-            result.List = result.List.Where(e => !tb_hide_ids.Contains(e.accountId));
-
-
-
-            foreach (var item in result.List)
-            {
-#if DEBUG
-#else
-                if (item.report_date.Date < DateTime.Now.Date) continue;
-#endif
-                //var tk = TkPoolCore.GetOne(item.accountId);
-                //if (tk != null) item.accountName = tk.company;
-                accountName = $"{TkChannelEnum.tb}_{item.accountId}";
-
-                if (item.total_count == 0)
-                {
-                    item.total_count = await TkLogCore.GetTotalAsync($":total:{accountName}:{item.report_date:yyyyMMdd}");
-                    if (item.total_count > 0)
-                    {
-                        //todo  过度用, 0613 跑两天就可以删掉
-                        accountName = item.accountName;
-                        item.total_count = await TkLogCore.GetTotalAsync($":total:{accountName}:{item.report_date:yyyyMMdd}");
-                    }
-                    if (item.total_count > 0)
-                    {
-                        item.success_count = await TkLogCore.GetTotalAsync($":total:{accountName}:success:{item.report_date:yyyyMMdd}");
-                        item.abandon_count = await TkLogCore.GetTotalAsync($":total:{accountName}:放弃转链:{item.report_date:yyyyMMdd}");
-                        if (item.total_count > 0)
-                        {
-                            item.success_percentage = $"{item.success_count / (double)item.total_count * 100:f2}%";
-                            item.abandon_percentage = $"{item.abandon_count / (double)item.total_count * 100:f2}%";
-                        }
-                    }
-                }
-
-                if (item.parse_total_count == 0)
-                {
-                    item.parse_total_count = await TkLogCore.GetTotalAsync($":parse_total:{accountName}:{item.report_date:yyyyMMdd}");
-                    if (item.parse_total_count > 0)
-                    {
-                        item.parse_success_count = await TkLogCore.GetTotalAsync($":parse_total:{accountName}:success:{item.report_date:yyyyMMdd}");
-                        item.parse_abandon_count = await TkLogCore.GetTotalAsync($":parse_total:{accountName}:放弃转链:{item.report_date:yyyyMMdd}");
-                        if (item.parse_total_count > 0)
-                        {
-                            item.parse_success_percentage = $"{item.parse_success_count / (double)item.parse_total_count * 100:f2}%";
-                            item.parse_abandon_percentage = $"{item.parse_abandon_count / (double)item.parse_total_count * 100:f2}%";
-                        }
-                    }
-                }
-
-                if (item.coupon_total_count == 0)
-                {
-                    item.coupon_total_count = await TkLogCore.GetTotalAsync($":coupon_total:{accountName}:{item.report_date:yyyyMMdd}", false);
-                    if (item.coupon_total_count > 0)
-                    {
-                        item.coupon_success_count = await TkLogCore.GetTotalAsync($":coupon_total:{accountName}:success:{item.report_date:yyyyMMdd}", false);
-                        item.coupon_abandon_count = await TkLogCore.GetTotalAsync($":coupon_total:{accountName}:放弃转链:{item.report_date:yyyyMMdd}", false);
-                        if (item.coupon_total_count > 0)
-                        {
-                            item.coupon_success_percentage = $"{item.coupon_success_count / (double)item.coupon_total_count * 100:f2}%";
-                            item.coupon_abandon_percentage = $"{item.coupon_abandon_count / (double)item.coupon_total_count * 100:f2}%";
-                        }
-                    }
-                }
-
-                if (item.tool_total_count == 0)
-                {
-                    item.tool_total_count = await TkLogCore.GetTotalAsync($":parse_total:tool:{item.report_date:yyyyMMdd}");
-                    if (item.tool_total_count > 0)
-                    {
-                        item.tool_success_count = await TkLogCore.GetTotalAsync($":parse_total:tool:success:{item.report_date:yyyyMMdd}");
-                        item.tool_abandon_count = await TkLogCore.GetTotalAsync($":parse_total:tool:放弃转链:{item.report_date:yyyyMMdd}");
-                        if (item.tool_total_count > 0)
-                        {
-                            item.tool_success_percentage = $"{item.tool_success_count / (double)item.tool_total_count * 100:f2}%";
-                            item.tool_abandon_percentage = $"{item.tool_abandon_count / (double)item.tool_total_count * 100:f2}%";
-                        }
-                    }
-                }
-            }
-            return new APIResult(new { data = result });
-        }
-
-        [HttpPost]
-        public async Task<ActionResult> report_hourtrend([FromBody] JsonElement form)
-        {
-            int page = form.Read("current", 1);
-            int size = form.Read("pageSize", 10);
-            bool getTotal = form.Read("getTotal", true);
-            string sort = form.Read<string>("sort");
-            string order = form.Read<string>("order");
-            var filterHours = form.Read<int?>("filterHours", null);
-            string accountName = form.Read<string>("accountName");
-
-            var report_date = form.PathReadArray<string>("query_date[]");
-            int accountId = form.Read("accountId", 0);
-
-            string filter = string.Empty;
-            if (!string.IsNullOrEmpty(accountName))
-            {
-                filter += $" AND accountName=@accountName";
-            }
-
-            if (accountId != 0)
-            {
-                filter += $" AND accountId=@accountId";
-            }
-
-            DateTime stime = DateTime.MinValue, etime = DateTime.MinValue;
-            if (report_date.Count == 2)
-            {
-                if (DateTime.TryParse(report_date[0], out stime) && DateTime.TryParse(report_date[1], out etime))
-                {
-                    etime = etime.AddDays(1).AddSeconds(-1);
-                    if (etime > DateTime.Now) etime = DateTime.Now;
-                    filter += $" AND report_date BETWEEN @stime AND @etime";
-                }
-            }
-            if (filterHours != null)
-            {
-                filterHours--;
-                filter += $" AND HOUR(report_date) = @filterHours";
-            }
-
-            filter = filter.StringTrimStart(" AND ");
-
-            //排序
-            string orderBy = "id ASC";
-            if (!string.IsNullOrEmpty(order))
-            {
-                order = "descending".Equals(order) ? "DESC" : "ASC";
-                orderBy = sort switch
-                {
-                    _ => $"{sort} {order}",
-                };
-            }
-
-            using var conn = DBContext.GetOpenConnection();
-            var result = new DBContext.Table(conn, "tk_report_hourtrend")
-
-                .Where(filter, new { accountId, stime, etime, accountName, filterHours })
-                .Page(size, page)
-                .Order(orderBy)
-                .PageList<TkReportDTO>(getTotal);
-
-            foreach (var item in result.List)
-            {
-
-#if DEBUG
-#else
-                if (item.report_date.Date < DateTime.Now.Date) continue;
-#endif
-                accountName = $"{TkChannelEnum.tb}_{item.accountId}";
-                if (item.total_count == 0)
-                {
-                    item.total_count = await TkLogCore.GetTotalAsync($":total:{accountName}:{item.report_date:yyyyMMddHH}");
-                    if (item.total_count == 0)
-                    {
-                        //todo  过度用, 0613 跑两天就可以删掉
-                        accountName = item.accountName;
-                        item.total_count = await TkLogCore.GetTotalAsync($":total:{accountName}:{item.report_date:yyyyMMddHH}");
-                    }
-                    if (item.total_count == 0) continue;
-                    item.success_count = await TkLogCore.GetTotalAsync($":total:{item.accountName}:success:{item.report_date:yyyyMMddHH}");
-                    item.abandon_count = await TkLogCore.GetTotalAsync($":total:{item.accountName}:放弃转链:{item.report_date:yyyyMMddHH}");
-                    if (item.total_count > 0)
-                    {
-                        item.success_percentage = $"{item.success_count / (double)item.total_count * 100:f2}%";
-                        item.abandon_percentage = $"{item.abandon_count / (double)item.total_count * 100:f2}%";
-                    }
-                }
-
-                if (item.parse_total_count == 0)
-                {
-                    item.parse_total_count = await TkLogCore.GetTotalAsync($":parse_total:{accountName}:{item.report_date:yyyyMMddHH}");
-                    if (item.parse_total_count > 0)
-                    {
-                        item.parse_success_count = await TkLogCore.GetTotalAsync($":parse_total:{item.accountName}:success:{item.report_date:yyyyMMddHH}");
-                        item.parse_abandon_count = await TkLogCore.GetTotalAsync($":parse_total:{item.accountName}:放弃转链:{item.report_date:yyyyMMddHH}");
-                        if (item.parse_total_count > 0)
-                        {
-                            item.parse_success_percentage = $"{item.parse_success_count / (double)item.parse_total_count * 100:f2}%";
-                            item.parse_abandon_percentage = $"{item.parse_abandon_count / (double)item.parse_total_count * 100:f2}%";
-                        }
-                    }
-                }
-
-                if (item.coupon_total_count == 0)
-                {
-                    item.coupon_total_count = await TkLogCore.GetTotalAsync($":coupon_total:{accountName}:{item.report_date:yyyyMMddHH}", false);
-                    if (item.coupon_total_count > 0)
-                    {
-                        item.coupon_success_count = await TkLogCore.GetTotalAsync($":coupon_total:{item.accountName}:success:{item.report_date:yyyyMMddHH}", false);
-                        item.coupon_abandon_count = await TkLogCore.GetTotalAsync($":coupon_total:{item.accountName}:放弃转链:{item.report_date:yyyyMMddHH}", false);
-                        if (item.coupon_total_count > 0)
-                        {
-                            item.coupon_success_percentage = $"{item.coupon_success_count / (double)item.coupon_total_count * 100:f2}%";
-                            item.coupon_abandon_percentage = $"{item.coupon_abandon_count / (double)item.coupon_total_count * 100:f2}%";
-                        }
-                    }
-                }
-
-                if (item.tool_total_count == 0)
-                {
-                    item.tool_total_count = await TkLogCore.GetTotalAsync($":tool_total:{accountName}:{item.report_date:yyyyMMddHH}");
-                    if (item.tool_total_count > 0)
-                    {
-                        item.tool_success_count = await TkLogCore.GetTotalAsync($":tool_total:{item.accountName}:success:{item.report_date:yyyyMMddHH}");
-                        item.tool_abandon_count = await TkLogCore.GetTotalAsync($":tool_total:{item.accountName}:放弃转链:{item.report_date:yyyyMMddHH}");
-                        if (item.tool_total_count > 0)
-                        {
-                            item.tool_success_percentage = $"{item.tool_success_count / (double)item.tool_total_count * 100:f2}%";
-                            item.tool_abandon_percentage = $"{item.tool_abandon_count / (double)item.tool_total_count * 100:f2}%";
-                        }
-                    }
-                }
-            }
-            return new APIResult(new { data = result });
-        }
-
-
-        [HttpPost]
-        public async Task<ActionResult> chartData([FromBody] JsonElement form)
-        {
-            var result = new List<dynamic>();
-            var reason_result = new List<dynamic>();
-
-            var report_date = form.PathReadArray<string>("query_date[]");
-            var accountName = form.Read("accountName", string.Empty);
-            var isHourtrend = form.Read<bool>("isLeaf", false);
-            var total_key = form.Read("total_key", "total");
-
-            DateTime stime = DateTime.MinValue;
-            if (!DateTime.TryParse(report_date[0], out stime)) return new APIResult(new { data = result, data2 = reason_result });
-
-            string _report_date = stime.ToString("yyyyMMdd");
-            if (isHourtrend) _report_date = stime.ToString("yyyyMMddHH");
-
-
-
-            if (string.IsNullOrEmpty(accountName)) accountName = "tb";
-            if (total_key == "tool_total")
-            {
-                accountName = "tool";
-                total_key = "parse_total";
-
-                string cacheKey = $":{total_key}:{accountName}:{_report_date}";
-                double total = await TkLogCore.GetTotalAsync(cacheKey);
-                if (total > 0)
-                {
-                    string[] keys = ["bdpan", "wemeet"];
-
-                    foreach (var keyname in keys)
-                    {
-                        //                    string[] keys = ["bdpan:success", "bdpan:fail", "wemeet:success", "wemeet:fail"];
-
-                        cacheKey = $":{total_key}:{keyname}:{_report_date}";
-                        total = await TkLogCore.GetTotalAsync(cacheKey);
-
-                        cacheKey = $":{total_key}:{keyname}:success:{_report_date}";
-                        int value = await TkLogCore.GetTotalAsync(cacheKey);
-                        if (value == 0) continue;
-                        result.Add(new { name = $"{keyname}-成功", value, increases = Math.Round(value / total * 100, 2) });
-
-
-                        cacheKey = $":{total_key}:{keyname}:fail:{_report_date}";
-                        value = await TkLogCore.GetTotalAsync(cacheKey);
-                        if (value == 0) continue;
-                        result.Add(new { name = $"{keyname}-失败", value, increases = Math.Round(value / total * 100, 2) });
-                    }
-
-                    cacheKey = $":{total_key}:{accountName}:reason:{_report_date}";
-                    string[] reason_keys = await TkLogCore.GetTotalKeysAsync(cacheKey);
-
-                    foreach (var keyname in reason_keys)
-                    {
-                        cacheKey = $":{total_key}:{accountName}:{keyname}:{_report_date}";
-                        int value = await TkLogCore.GetTotalAsync(cacheKey);
-                        if (value == 0) continue;
-                        reason_result.Add(new { name = keyname, value, increases = Math.Round(value / total * 100, 2) });
-                    }
-                    reason_result = reason_result.OrderByDescending(item => item.value).ToList();
-                }
-                return new APIResult(new { total, data = result, data2 = reason_result });
-            }
-            else
-            {
-                string cacheKey = $":{total_key}:{accountName}:{_report_date}";
-                double total = await TkLogCore.GetTotalAsync(cacheKey, false);
-                if (total > 0)
-                {
-                    string[] keys = ["success"];
-
-                    cacheKey = $":{total_key}:{accountName}:message:{_report_date}";
-                    string[] message_keys = await TkLogCore.GetTotalKeysAsync(cacheKey, false);
-
-                    var combinedKeys = keys.Union(message_keys).ToList();
-                    combinedKeys.RemoveAll(item => item == "fail");
-                    combinedKeys.RemoveAll(item => item == "OK");
-
-                    foreach (var keyname in combinedKeys)
-                    {
-                        cacheKey = $":{total_key}:{accountName}:{keyname}:{_report_date}";
-                        int value = await TkLogCore.GetTotalAsync(cacheKey, false);
-                        if (value == 0) continue;
-                        result.Add(new { name = keyname, value, increases = Math.Round(value / total * 100, 2) });
-                    }
-                    result = result.OrderByDescending(item => item.value).ToList();
-
-                    cacheKey = $":{total_key}:{accountName}:reason:{_report_date}";
-                    string[] reason_keys = await TkLogCore.GetTotalKeysAsync(cacheKey, false);
-
-                    foreach (var keyname in reason_keys)
-                    {
-                        if ("没有优惠券".Equals(keyname)) continue;
-                        cacheKey = $":{total_key}:{accountName}:{keyname}:{_report_date}";
-                        int value = await TkLogCore.GetTotalAsync(cacheKey, false);
-                        if (value < 50) continue;
-                        reason_result.Add(new { name = keyname, value, increases = Math.Round(value / total * 100, 2) });
-                    }
-                    reason_result = reason_result.OrderByDescending(item => item.value).ToList();
-                }
-                return new APIResult(new { total, data = result, data2 = reason_result });
-            }
-        }
-
-        [HttpPost]
-        public async Task<ActionResult> chartData2([FromBody] JsonElement form)
-        {
-            var result = new List<dynamic>();
-            var reason_result = new List<dynamic>();
-
-            var report_date = form.PathReadArray<string>("query_date[]");
-            var accountName = form.Read("accountName", string.Empty);
-            var isHourtrend = form.Read<bool>("isLeaf", false);
-            var total_key = form.Read("total_key", "total");
-
-            if (string.IsNullOrEmpty(accountName)) accountName = "tb";
-
-            DateTime stime = DateTime.MinValue;
-            if (!DateTime.TryParse(report_date[0], out stime)) return new APIResult(new { data = result, data2 = reason_result });
-
-            string _report_date = stime.ToString("yyyyMMdd");
-            if (isHourtrend) _report_date = stime.ToString("yyyyMMddHH");
-
-            string cacheKey = $":{total_key}:{accountName}:{_report_date}";
-            double total = await TkLogCore.GetTotalAsync(cacheKey);
-            if (total > 0)
-            {
-                string[] keys = ["success"];
-
-                cacheKey = $":{total_key}:{accountName}:message:{_report_date}";
-                string[] message_keys = await TkLogCore.GetTotalKeysAsync(cacheKey);
-
-                var combinedKeys = keys.Union(message_keys).ToList();
-                combinedKeys.RemoveAll(item => item == "fail");
-                combinedKeys.RemoveAll(item => item == "OK");
-
-
-                foreach (var keyname in combinedKeys)
-                {
-                    cacheKey = $":{total_key}:{accountName}:{keyname}:{_report_date}";
-                    int value = await TkLogCore.GetTotalAsync(cacheKey);
-                    if (value == 0) continue;
-                    result.Add(new { name = keyname, value, increases = Math.Round(value / total * 100, 2) });
-                }
-                result = result.OrderByDescending(item => item.value).ToList();
-
-                cacheKey = $":{total_key}:{accountName}:reason:{_report_date}";
-                string[] reason_keys = await TkLogCore.GetTotalKeysAsync(cacheKey);
-
-                foreach (var keyname in reason_keys)
-                {
-                    if ("没有优惠券".Equals(keyname)) continue;
-                    cacheKey = $":{total_key}:{accountName}:{keyname}:{_report_date}";
-                    int value = await TkLogCore.GetTotalAsync(cacheKey);
-                    if (value == 0) continue;
-                    reason_result.Add(new { name = keyname, value, increases = Math.Round(value / total * 100, 2) });
-                }
-                reason_result = reason_result.OrderByDescending(item => item.value).ToList();
-            }
-            return new APIResult(new { total, data = result, data2 = reason_result });
-        }
-
-
-
-        [HttpPost]
-        public ActionResult settle_bills_total([FromBody] JsonElement form)
-        {
-            int page = form.Read("current", 1);
-            int size = form.Read("pageSize", 10);
-            bool getTotal = form.Read("getTotal", true);
-            string sort = form.Read<string>("sort");
-            string order = form.Read<string>("order");
-
-            var belongTime = form.PathReadArray<string>("query_date[]");
-
-            string filter = string.Empty;
-            DateTime stime = DateTime.MinValue, etime = DateTime.MinValue;
-            if (belongTime.Count == 2)
-            {
-                if (DateTime.TryParse(belongTime[0], out stime) && DateTime.TryParse(belongTime[1], out etime))
-                {
-                    etime = etime.AddDays(1).AddSeconds(-1);
-                    filter += $" AND belongTime BETWEEN @stime AND @etime";
-                }
-            }
-
-            filter = filter.StringTrimStart(" AND ");
-
-            //排序
-            string orderBy = "belongTime DESC";
-            if (!string.IsNullOrEmpty(order))
-            {
-                order = "descending".Equals(order) ? "DESC" : "ASC";
-                orderBy = sort switch
-                {
-                    _ => $"{sort} {order}",
-                };
-            }
-            var result = new DBContext.Table("v_tk_report_bills")
-                .Where(filter, new { stime, etime })
-                .Page(size, page)
-                .Order(orderBy)
-                .PageList<TkSettleBillDTO>(getTotal);
-
-            return new APIResult(new { data = result });
-        }
-
-
-        [HttpPost]
-        public ActionResult settle_bills([FromBody] JsonElement form)
-        {
-            int page = form.Read("current", 1);
-            int size = form.Read("pageSize", 10);
-            bool getTotal = form.Read("getTotal", true);
-            string sort = form.Read<string>("sort");
-            string order = form.Read<string>("order");
-            string name = form.Read<string>("keyword");
-
-            var belongTime = form.PathReadArray<string>("query_date[]");
-
-            string filter = string.Empty;
-            if (!string.IsNullOrEmpty(name))
-            {
-                filter += $" AND accountId IN (SELECT ID FROM tk_pool WHERE company=@name)";
-            }
-            DateTime stime = DateTime.MinValue, etime = DateTime.MinValue;
-            if (belongTime.Count == 2)
-            {
-                if (DateTime.TryParse(belongTime[0], out stime) && DateTime.TryParse(belongTime[1], out etime))
-                {
-                    etime = etime.AddDays(1).AddSeconds(-1);
-                    filter += $" AND belongTime BETWEEN @stime AND @etime";
-                }
-            }
-
-            filter = filter.StringTrimStart(" AND ");
-
-            //排序
-            string orderBy = "belongTime DESC";
-            if (!string.IsNullOrEmpty(order))
-            {
-                order = "descending".Equals(order) ? "DESC" : "ASC";
-                orderBy = sort switch
-                {
-                    _ => $"{sort} {order}",
-                };
-            }
-
-            var result = new DBContext.Table("tk_settle_bill")
-                .Where(filter, new { name, stime, etime })
-                .Page(size, page)
-                .Order(orderBy)
-                .PageList<TkSettleBillDTO>(getTotal);
-
-
-            //foreach (var item in result.List)
-            //{
-            //    if (string.IsNullOrEmpty(name))
-            //    {
-            //        item.accountName = "-";
-            //    }
-            //}
-            return new APIResult(new { data = result });
-        }
-
-    }
+using molilian.core;
+using dodohold.core;
+using Microsoft.AspNetCore.Mvc;
+using System.Text.Json;
+using System.Net;
+using static QRCoder.PayloadGenerator;
+using MySqlX.XDevAPI;
+using OfficeOpenXml.FormulaParsing.LexicalAnalysis;
+using StackExchange.Redis;
+namespace molilian.api.Controllers
+{
+
+
+    [ApiController]
+    [MyAuthorize("admin")]
+    [Route("api/[controller]/[action]")]
+    [Route("coupon_api/[controller]/[action]")]
+    public class TaobaoController : ControllerBase
+    {
+        readonly IAuthorizationProvider provider = new AdminProvider();
+        protected IHttpContextAccessor _accessor;
+        public TaobaoController(IHttpContextAccessor accessor)
+        {
+            _accessor = accessor;
+        }
+
+        [HttpPost]
+        public async Task<ActionResult> updateCookies([FromBody] JsonElement form)
+        {
+            var clientIp = _accessor.HttpContext.GetUserIp();
+            var token = provider.Get(_accessor.HttpContext);
+            string user_agent = _accessor.HttpContext.Request.UserAgent();
+
+
+            string cookie = form.Read<string>("cookie", string.Empty);
+            var accountId = await TkPoolCore.UpdateCookies(cookie, user_agent);
+            bool success = accountId > 0;
+            if (success)
+            {
+                //string desc = ObjectComparer.PrintCompareToString(old, form).Trim();
+                OperationLogCore.LogOperation(token.AccessKey, clientIp, $"tk_pool:{accountId}:cookie", string.Empty, cookie);
+            }
+            return new APIResult(new
+            {
+                data = new { success, msg = success ? "更新成功" : "更新失败,请检查输入的cookie" },
+            });
+        }
+
+
+        [HttpPost]
+        public async Task<ActionResult> update([FromBody] JsonElement form)
+        {
+            var clientIp = _accessor.HttpContext.GetUserIp();
+            var token = provider.Get(_accessor.HttpContext);
+
+            int id = form.Read<int>("id", 0);
+            string name = form.Read<string>("name", string.Empty);
+            string val = form.Read<string>("val", string.Empty);
+
+            if (id == 0)
+            {
+                return new APIResult(new { data = new { success = false, msg = "更新失败,请检查输入的cookie" } });
+            }
+
+            int result;
+            switch (name)
+            {
+                case "enable_fake_click":
+                case "enable_parse":
+                case "enable_coupon":
+                case "enable_sync_order":
+                case "enable_promotionQuery":
+                case "enable_deep_url":
+                case "status":
+                    bool bVal = val.Equals("True");
+                    result = new DBContext.Table("tk_pool")
+                           .Add(name, bVal)
+                           .Add("last_time", DateTime.Now)
+                           .Where("id=@id", new { id })
+                           .Update();
+                    break;
+                case "check_status":
+                    //检查状态
+                    var arr = EndPointCore.NotifyCheckDeepUrl(id);
+                    string suspended_endpoint = string.Empty;
+                    var log = new LoggerLibrary("check_suspended_endpoint");
+
+                    foreach ((string node, string message) in arr)
+                    {
+                        log.Info(node, message);
+                        if (message.Contains("霸下验证码"))
+                        {
+                            suspended_endpoint += $"{node},";
+                        }
+                    }
+                    result = new DBContext.Table("tk_pool")
+                           .Add("suspended_endpoint", suspended_endpoint)
+                           .Add("last_time", DateTime.Now)
+                           .Where("id=@id", new { id })
+                           .Update();
+                    log.SaveAsync();
+                    break;
+                case "time_range":
+                    int.TryParse(val, out int iVal);
+                    result = new DBContext.Table("tk_pool")
+                           .Add(name, iVal)
+                           .Add("last_time", DateTime.Now)
+                           .Where("id=@id", new { id })
+                           .Update();
+                    break;
+                default:
+                    return new APIResult(new { data = new { success = false, msg = "更新失败,未授权操作" } });
+            }
+            bool success = result > 0;
+            if (success)
+            {
+                //string desc = ObjectComparer.PrintCompareToString(old, form).Trim();
+                OperationLogCore.LogOperation(token.AccessKey, clientIp, $"tk_pool:{id}:{name}", string.Empty, val);
+                await EndPointCore.NotifyReload(true);
+
+            }
+            return new APIResult(new
+            {
+                data = new { success, msg = success ? "更新成功" : "更新失败,请检查输入信息" },
+            });
+        }
+
+
+        /// <summary>
+        /// 账号列表
+        /// </summary>
+        /// <param name="form"></param>
+        /// <returns></returns>
+        [HttpPost]
+        public async Task<ActionResult> list([FromBody] JsonElement form)
+        {
+            var token = provider.Get(_accessor.HttpContext);
+
+            int page = form.Read("current", 1);
+            int size = form.Read("pageSize", 10);
+            string keyword = form.Read("name", string.Empty);
+            bool getTotal = form.Read("getTotal", true);
+            string _status = form.Read("status", string.Empty);
+            int status = _status switch
+            {
+                "online" => 1,
+                "offline" => 0,
+                _ => -1,
+            };
+            var lastTime = form.PathReadArray<string>("lastTime[]");
+
+            string sort = form.Read<string>("sort");
+            string order = form.Read<string>("order");
+
+            bool show_hide = form.Read("show_hide", false);
+            string filter = string.Empty;
+            if (!show_hide) filter = "is_hide=0";
+
+            if (!string.IsNullOrEmpty(keyword))
+            {
+                filter += $" AND (`name` LIKE @keyword OR `description` LIKE @keyword)";
+                keyword = $"%{keyword}%";
+            }
+            DateTime stime = DateTime.MinValue, etime = DateTime.MinValue;
+            if (lastTime.Count == 2)
+            {
+                if (DateTime.TryParse(lastTime[0], out stime) && DateTime.TryParse(lastTime[1], out etime))
+                {
+                    etime = etime.AddDays(1).AddSeconds(-1);
+                    filter += $" AND last_time BETWEEN @stime AND @etime";
+                }
+            }
+            if (status != -1)
+            {
+                filter += $" AND status=@status";
+
+            }
+
+            filter = filter.StringTrimStart(" AND ");
+
+            //排序
+            string orderBy = "id DESC";
+            if (!string.IsNullOrEmpty(order))
+            {
+                order = "descending".Equals(order) ? "DESC" : "ASC";
+                orderBy = sort switch
+                {
+                    //"num" => $"num {order}",
+                    _ => $"{sort} {order},ID DESC",
+                };
+            }
+
+            using var conn = DBContext.GetOpenConnection();
+            var result = new DBContext.Table(conn, "tk_pool")
+                .Where(filter, new { keyword, status, stime, etime })
+                .Page(size, page)
+                .Order(orderBy)
+                .PageList<TkPoolDTO>(getTotal);
+
+            foreach (var item in result.List)
+            {
+                item.current_amt = Math.Round(RiskControlCore.GetIncomeAmt(TkChannelEnum.tb, $"{item.id}"), 2);
+                item.cookies = string.Empty;
+                //await RiskControlCore.SetRiskCookie($"tb:{item.id}", "TEST");
+                item.riskCookie = RiskControlCore.GetRiskCookie($"tb:{item.id}");
+            }
+            return new APIResult(new { data = result });
+        }
+
+
+        [HttpPost]
+        public async Task<ActionResult> report_dailys([FromBody] JsonElement form)
+        {
+            int page = form.Read("current", 1);
+            int size = form.Read("pageSize", 10);
+            bool getTotal = form.Read("getTotal", true);
+            string sort = form.Read<string>("sort");
+            string order = form.Read<string>("order");
+
+            var report_date = form.PathReadArray<string>("query_date[]");
+            string dailys_accountName = "all";
+            int dailys_accountId = 0;
+
+            string filter = string.Empty;
+            DateTime stime = DateTime.MinValue, etime = DateTime.MinValue;
+            if (report_date.Count == 2)
+            {
+                if (DateTime.TryParse(report_date[0], out stime) && DateTime.TryParse(report_date[1], out etime))
+                {
+                    etime = etime.AddDays(1).AddSeconds(-1);
+                    filter += $" AND A.report_date BETWEEN @stime AND @etime";
+                }
+            }
+
+            filter = filter.StringTrimStart(" AND ");
+
+            //排序
+            string orderBy = "A.report_date DESC";
+            if (!string.IsNullOrEmpty(order))
+            {
+                order = "descending".Equals(order) ? "DESC" : "ASC";
+                orderBy = sort switch
+                {
+                    _ => $"A.{sort} {order}",
+                };
+            }
+
+            var result = new DBContext.Table("v_tk_report_dailys")
+                .LeftJoin("daily_logs",
+                "B.log_date = A.report_date AND B.channel=0 AND B.accountId=@dailys_accountId",
+                "total_count, B.abandon_count, B.success_count, B.abandon_percentage, B.success_percentage," +
+                "B.parse_total_count, B.parse_abandon_count, B.parse_success_count, B.parse_abandon_percentage, B.parse_success_percentage," +
+                "B.coupon_total_count, B.coupon_abandon_count, B.coupon_success_count, B.coupon_abandon_percentage, B.coupon_success_percentage," +
+                "B.tool_total_count, B.tool_abandon_count, B.tool_success_count, B.tool_abandon_percentage, B.tool_success_percentage")
+                .Where(filter, new { dailys_accountId, dailys_accountName, stime, etime })
+                .Page(size, page)
+                .Order(orderBy)
+                .PageList<TkReportDTO>(getTotal);
+
+            foreach (var item in result.List)
+            {
+                if (item.report_date.Date < DateTime.Now.Date) continue;
+                if (item.total_count == 0)
+                {
+                    item.total_count = await TkLogCore.GetTotalAsync($":total:tb:{item.report_date:yyyyMMdd}");
+                    if (item.total_count > 0)
+                    {
+                        item.success_count = await TkLogCore.GetTotalAsync($":total:tb:success:{item.report_date:yyyyMMdd}");
+                        item.abandon_count = await TkLogCore.GetTotalAsync($":total:tb:放弃转链:{item.report_date:yyyyMMdd}");
+                        if (item.total_count > 0)
+                        {
+                            item.success_percentage = $"{item.success_count / (double)item.total_count * 100:f2}%";
+                            item.abandon_percentage = $"{item.abandon_count / (double)item.total_count * 100:f2}%";
+                        }
+                    }
+                }
+
+                if (item.parse_total_count == 0)
+                {
+                    item.parse_total_count = await TkLogCore.GetTotalAsync($":parse_total:tb:{item.report_date:yyyyMMdd}");
+                    if (item.parse_total_count > 0)
+                    {
+                        item.parse_success_count = await TkLogCore.GetTotalAsync($":parse_total:tb:success:{item.report_date:yyyyMMdd}");
+                        item.parse_abandon_count = await TkLogCore.GetTotalAsync($":parse_total:tb:放弃转链:{item.report_date:yyyyMMdd}");
+                        if (item.parse_total_count > 0)
+                        {
+                            item.parse_success_percentage = $"{item.parse_success_count / (double)item.parse_total_count * 100:f2}%";
+                            item.parse_abandon_percentage = $"{item.parse_abandon_count / (double)item.parse_total_count * 100:f2}%";
+                        }
+                    }
+                }
+
+                if (item.coupon_total_count == 0)
+                {
+                    item.coupon_total_count = await TkLogCore.GetTotalAsync($":coupon_total:tb:{item.report_date:yyyyMMdd}", false);
+                    if (item.coupon_total_count > 0)
+                    {
+                        item.coupon_success_count = await TkLogCore.GetTotalAsync($":coupon_total:tb:success:{item.report_date:yyyyMMdd}", false);
+                        item.coupon_abandon_count = await TkLogCore.GetTotalAsync($":coupon_total:tb:放弃转链:{item.report_date:yyyyMMdd}", false);
+                        if (item.coupon_total_count > 0)
+                        {
+                            item.coupon_success_percentage = $"{item.coupon_success_count / (double)item.coupon_total_count * 100:f2}%";
+                            item.coupon_abandon_percentage = $"{item.coupon_abandon_count / (double)item.coupon_total_count * 100:f2}%";
+                        }
+                    }
+                }
+
+                if (item.tool_total_count == 0)
+                {
+                    item.tool_total_count = await TkLogCore.GetTotalAsync($":parse_total:tool:{item.report_date:yyyyMMdd}");
+                    if (item.tool_total_count > 0)
+                    {
+                        item.tool_success_count = await TkLogCore.GetTotalAsync($":parse_total:tool:success:{item.report_date:yyyyMMdd}");
+                        item.tool_abandon_count = await TkLogCore.GetTotalAsync($":parse_total:tool:放弃转链:{item.report_date:yyyyMMdd}");
+                        if (item.tool_total_count > 0)
+                        {
+                            item.tool_success_percentage = $"{item.tool_success_count / (double)item.tool_total_count * 100:f2}%";
+                            item.tool_abandon_percentage = $"{item.tool_abandon_count / (double)item.tool_total_count * 100:f2}%";
+                        }
+
+                    }
+                }
+            }
+            return new APIResult(new
+            {
+                data = result
+            });
+        }
+
+
+        [HttpPost]
+        public async Task<ActionResult> report_hourlys([FromBody] JsonElement form)
+        {
+            int page = form.Read("current", 1);
+            int size = form.Read("pageSize", 10);
+            bool getTotal = form.Read("getTotal", true);
+            string sort = form.Read<string>("sort");
+            string order = form.Read<string>("order");
+
+            var report_date = form.PathReadArray<string>("query_date[]");
+            var filterHours = form.Read<int?>("filterHours", null);
+            string dailys_accountName = "all";
+            int dailys_accountId = 0;
+
+            string filter = string.Empty;
+            DateTime stime = DateTime.MinValue, etime = DateTime.MinValue;
+            if (report_date.Count == 2)
+            {
+                if (DateTime.TryParse(report_date[0], out stime) && DateTime.TryParse(report_date[1], out etime))
+                {
+                    etime = etime.AddDays(1).AddSeconds(-1);
+                    filter += $" AND A.report_date BETWEEN @stime AND @etime";
+                }
+            }
+
+            if (filterHours != null)
+            {
+                filterHours--;
+                filter += $" AND HOUR(report_date) = @filterHours";
+            }
+
+            filter = filter.StringTrimStart(" AND ");
+
+            //排序
+            string orderBy = "A.report_date DESC";
+            if (!string.IsNullOrEmpty(order))
+            {
+                order = "descending".Equals(order) ? "DESC" : "ASC";
+                orderBy = sort switch
+                {
+                    _ => $"A.{sort} {order}",
+                };
+            }
+
+            var result = new DBContext.Table("v_tk_report_hourlys")
+                .LeftJoin("daily_logs",
+                "B.log_date = A.report_date AND B.channel=0 AND B.accountId=@dailys_accountId",
+                "total_count, B.abandon_count, B.success_count, B.abandon_percentage, B.success_percentage," +
+                "B.parse_total_count, B.parse_abandon_count, B.parse_success_count, B.parse_abandon_percentage, B.parse_success_percentage," +
+                "B.coupon_total_count, B.coupon_abandon_count, B.coupon_success_count, B.coupon_abandon_percentage, B.coupon_success_percentage," +
+                "B.tool_total_count, B.tool_abandon_count, B.tool_success_count, B.tool_abandon_percentage, B.tool_success_percentage")
+                .Where(filter, new { dailys_accountId, dailys_accountName, filterHours, stime, etime })
+                .Page(size, page)
+                .Order(orderBy)
+                .PageList<TkReportDTO>(getTotal);
+
+            foreach (var item in result.List)
+            {
+                if (item.total_count == 0)
+                {
+                    item.total_count = await TkLogCore.GetTotalAsync($":total:tb:{item.report_date:yyyyMMddHH}");
+                    if (item.total_count > 0)
+                    {
+                        item.success_count = await TkLogCore.GetTotalAsync($":total:tb:success:{item.report_date:yyyyMMddHH}");
+                        item.abandon_count = await TkLogCore.GetTotalAsync($":total:tb:放弃转链:{item.report_date:yyyyMMddHH}");
+                        if (item.total_count > 0)
+                        {
+                            item.success_percentage = $"{item.success_count / (double)item.total_count * 100:f2}%";
+                            item.abandon_percentage = $"{item.abandon_count / (double)item.total_count * 100:f2}%";
+                        }
+                    }
+                }
+
+                if (item.parse_total_count == 0)
+                {
+                    item.parse_total_count = await TkLogCore.GetTotalAsync($":parse_total:tb:{item.report_date:yyyyMMddHH}");
+                    if (item.parse_total_count > 0)
+                    {
+                        item.parse_success_count = await TkLogCore.GetTotalAsync($":parse_total:tb:success:{item.report_date:yyyyMMddHH}");
+                        item.parse_abandon_count = await TkLogCore.GetTotalAsync($":parse_total:tb:放弃转链:{item.report_date:yyyyMMddHH}");
+                        if (item.parse_total_count > 0)
+                        {
+                            item.parse_success_percentage = $"{item.parse_success_count / (double)item.parse_total_count * 100:f2}%";
+                            item.parse_abandon_percentage = $"{item.parse_abandon_count / (double)item.parse_total_count * 100:f2}%";
+                        }
+                    }
+                }
+
+                if (item.coupon_total_count == 0)
+                {
+                    item.coupon_total_count = await TkLogCore.GetTotalAsync($":coupon_total:tb:{item.report_date:yyyyMMddHH}", false);
+                    if (item.coupon_total_count > 0)
+                    {
+                        item.coupon_success_count = await TkLogCore.GetTotalAsync($":coupon_total:tb:success:{item.report_date:yyyyMMddHH}", false);
+                        item.coupon_abandon_count = await TkLogCore.GetTotalAsync($":coupon_total:tb:放弃转链:{item.report_date:yyyyMMddHH}", false);
+                        if (item.coupon_total_count > 0)
+                        {
+                            item.coupon_success_percentage = $"{item.coupon_success_count / (double)item.coupon_total_count * 100:f2}%";
+                            item.coupon_abandon_percentage = $"{item.coupon_abandon_count / (double)item.coupon_total_count * 100:f2}%";
+                        }
+                    }
+                }
+
+                if (item.tool_total_count == 0)
+                {
+                    item.tool_total_count = await TkLogCore.GetTotalAsync($":parse_total:tool:{item.report_date:yyyyMMddHH}");
+                    if (item.tool_total_count > 0)
+                    {
+                        item.tool_success_count = await TkLogCore.GetTotalAsync($":parse_total:tool:success:{item.report_date:yyyyMMddHH}");
+                        item.tool_abandon_count = await TkLogCore.GetTotalAsync($":parse_total:tool:放弃转链:{item.report_date:yyyyMMddHH}");
+                        if (item.tool_total_count > 0)
+                        {
+                            item.tool_success_percentage = $"{item.tool_success_count / (double)item.tool_total_count * 100:f2}%";
+                            item.tool_abandon_percentage = $"{item.tool_abandon_count / (double)item.tool_total_count * 100:f2}%";
+                        }
+
+                    }
+                }
+            }
+            return new APIResult(new
+            {
+                data = result
+            });
+        }
+
+        [HttpPost]
+        public async Task<ActionResult> report_list([FromBody] JsonElement form)
+        {
+            int page = form.Read("current", 1);
+            int size = form.Read("pageSize", 10);
+            bool getTotal = form.Read("getTotal", true);
+            string sort = form.Read<string>("sort");
+            string order = form.Read<string>("order");
+
+            var report_date = form.PathReadArray<string>("query_date[]");
+            string accountName = form.Read<string>("accountName");
+
+            string filter = string.Empty;
+            if (!string.IsNullOrEmpty(accountName))
+            {
+                filter += $" AND accountName=@accountName";
+            }
+
+            DateTime stime = DateTime.MinValue, etime = DateTime.MinValue;
+            if (report_date.Count == 2)
+            {
+                if (DateTime.TryParse(report_date[0], out stime) && DateTime.TryParse(report_date[1], out etime))
+                {
+                    etime = etime.AddDays(1).AddSeconds(-1);
+                    filter += $" AND report_date BETWEEN @stime AND @etime";
+                }
+            }
+
+            filter = filter.StringTrimStart(" AND ");
+
+            //排序
+            string orderBy = "id DESC";
+            if (!string.IsNullOrEmpty(order))
+            {
+                order = "descending".Equals(order) ? "DESC" : "ASC";
+                orderBy = sort switch
+                {
+                    _ => $"{sort} {order}",
+                };
+            }
+
+            var result = new DBContext.Table("tk_report")
+                .Where(filter, new { accountName, stime, etime })
+                .Page(size, page)
+                .Order(orderBy)
+                .PageList<TkReportDTO>(getTotal);
+
+
+            var tb_accounts = await TkPoolCore.AllListAsync();
+            var tb_hide_ids = tb_accounts.Where(e => e.is_hide).Select(e => e.id).ToList();
+            result.List = result.List.Where(e => !tb_hide_ids.Contains(e.accountId));
+
+
+
+            foreach (var item in result.List)
+            {
+#if DEBUG
+#else
+                if (item.report_date.Date < DateTime.Now.Date) continue;
+#endif
+                //var tk = TkPoolCore.GetOne(item.accountId);
+                //if (tk != null) item.accountName = tk.company;
+                accountName = $"{TkChannelEnum.tb}_{item.accountId}";
+
+                if (item.total_count == 0)
+                {
+                    item.total_count = await TkLogCore.GetTotalAsync($":total:{accountName}:{item.report_date:yyyyMMdd}");
+                    if (item.total_count > 0)
+                    {
+                        //todo  过度用, 0613 跑两天就可以删掉
+                        accountName = item.accountName;
+                        item.total_count = await TkLogCore.GetTotalAsync($":total:{accountName}:{item.report_date:yyyyMMdd}");
+                    }
+                    if (item.total_count > 0)
+                    {
+                        item.success_count = await TkLogCore.GetTotalAsync($":total:{accountName}:success:{item.report_date:yyyyMMdd}");
+                        item.abandon_count = await TkLogCore.GetTotalAsync($":total:{accountName}:放弃转链:{item.report_date:yyyyMMdd}");
+                        if (item.total_count > 0)
+                        {
+                            item.success_percentage = $"{item.success_count / (double)item.total_count * 100:f2}%";
+                            item.abandon_percentage = $"{item.abandon_count / (double)item.total_count * 100:f2}%";
+                        }
+                    }
+                }
+
+                if (item.parse_total_count == 0)
+                {
+                    item.parse_total_count = await TkLogCore.GetTotalAsync($":parse_total:{accountName}:{item.report_date:yyyyMMdd}");
+                    if (item.parse_total_count > 0)
+                    {
+                        item.parse_success_count = await TkLogCore.GetTotalAsync($":parse_total:{accountName}:success:{item.report_date:yyyyMMdd}");
+                        item.parse_abandon_count = await TkLogCore.GetTotalAsync($":parse_total:{accountName}:放弃转链:{item.report_date:yyyyMMdd}");
+                        if (item.parse_total_count > 0)
+                        {
+                            item.parse_success_percentage = $"{item.parse_success_count / (double)item.parse_total_count * 100:f2}%";
+                            item.parse_abandon_percentage = $"{item.parse_abandon_count / (double)item.parse_total_count * 100:f2}%";
+                        }
+                    }
+                }
+
+                if (item.coupon_total_count == 0)
+                {
+                    item.coupon_total_count = await TkLogCore.GetTotalAsync($":coupon_total:{accountName}:{item.report_date:yyyyMMdd}", false);
+                    if (item.coupon_total_count > 0)
+                    {
+                        item.coupon_success_count = await TkLogCore.GetTotalAsync($":coupon_total:{accountName}:success:{item.report_date:yyyyMMdd}", false);
+                        item.coupon_abandon_count = await TkLogCore.GetTotalAsync($":coupon_total:{accountName}:放弃转链:{item.report_date:yyyyMMdd}", false);
+                        if (item.coupon_total_count > 0)
+                        {
+                            item.coupon_success_percentage = $"{item.coupon_success_count / (double)item.coupon_total_count * 100:f2}%";
+                            item.coupon_abandon_percentage = $"{item.coupon_abandon_count / (double)item.coupon_total_count * 100:f2}%";
+                        }
+                    }
+                }
+
+                if (item.tool_total_count == 0)
+                {
+                    item.tool_total_count = await TkLogCore.GetTotalAsync($":parse_total:tool:{item.report_date:yyyyMMdd}");
+                    if (item.tool_total_count > 0)
+                    {
+                        item.tool_success_count = await TkLogCore.GetTotalAsync($":parse_total:tool:success:{item.report_date:yyyyMMdd}");
+                        item.tool_abandon_count = await TkLogCore.GetTotalAsync($":parse_total:tool:放弃转链:{item.report_date:yyyyMMdd}");
+                        if (item.tool_total_count > 0)
+                        {
+                            item.tool_success_percentage = $"{item.tool_success_count / (double)item.tool_total_count * 100:f2}%";
+                            item.tool_abandon_percentage = $"{item.tool_abandon_count / (double)item.tool_total_count * 100:f2}%";
+                        }
+                    }
+                }
+            }
+            return new APIResult(new { data = result });
+        }
+
+        [HttpPost]
+        public async Task<ActionResult> report_hourtrend([FromBody] JsonElement form)
+        {
+            int page = form.Read("current", 1);
+            int size = form.Read("pageSize", 10);
+            bool getTotal = form.Read("getTotal", true);
+            string sort = form.Read<string>("sort");
+            string order = form.Read<string>("order");
+            var filterHours = form.Read<int?>("filterHours", null);
+            string accountName = form.Read<string>("accountName");
+
+            var report_date = form.PathReadArray<string>("query_date[]");
+            int accountId = form.Read("accountId", 0);
+
+            string filter = string.Empty;
+            if (!string.IsNullOrEmpty(accountName))
+            {
+                filter += $" AND accountName=@accountName";
+            }
+
+            if (accountId != 0)
+            {
+                filter += $" AND accountId=@accountId";
+            }
+
+            DateTime stime = DateTime.MinValue, etime = DateTime.MinValue;
+            if (report_date.Count == 2)
+            {
+                if (DateTime.TryParse(report_date[0], out stime) && DateTime.TryParse(report_date[1], out etime))
+                {
+                    etime = etime.AddDays(1).AddSeconds(-1);
+                    if (etime > DateTime.Now) etime = DateTime.Now;
+                    filter += $" AND report_date BETWEEN @stime AND @etime";
+                }
+            }
+            if (filterHours != null)
+            {
+                filterHours--;
+                filter += $" AND HOUR(report_date) = @filterHours";
+            }
+
+            filter = filter.StringTrimStart(" AND ");
+
+            //排序
+            string orderBy = "id ASC";
+            if (!string.IsNullOrEmpty(order))
+            {
+                order = "descending".Equals(order) ? "DESC" : "ASC";
+                orderBy = sort switch
+                {
+                    _ => $"{sort} {order}",
+                };
+            }
+
+            using var conn = DBContext.GetOpenConnection();
+            var result = new DBContext.Table(conn, "tk_report_hourtrend")
+
+                .Where(filter, new { accountId, stime, etime, accountName, filterHours })
+                .Page(size, page)
+                .Order(orderBy)
+                .PageList<TkReportDTO>(getTotal);
+
+            foreach (var item in result.List)
+            {
+
+#if DEBUG
+#else
+                if (item.report_date.Date < DateTime.Now.Date) continue;
+#endif
+                accountName = $"{TkChannelEnum.tb}_{item.accountId}";
+                if (item.total_count == 0)
+                {
+                    item.total_count = await TkLogCore.GetTotalAsync($":total:{accountName}:{item.report_date:yyyyMMddHH}");
+                    if (item.total_count == 0)
+                    {
+                        //todo  过度用, 0613 跑两天就可以删掉
+                        accountName = item.accountName;
+                        item.total_count = await TkLogCore.GetTotalAsync($":total:{accountName}:{item.report_date:yyyyMMddHH}");
+                    }
+                    if (item.total_count == 0) continue;
+                    item.success_count = await TkLogCore.GetTotalAsync($":total:{item.accountName}:success:{item.report_date:yyyyMMddHH}");
+                    item.abandon_count = await TkLogCore.GetTotalAsync($":total:{item.accountName}:放弃转链:{item.report_date:yyyyMMddHH}");
+                    if (item.total_count > 0)
+                    {
+                        item.success_percentage = $"{item.success_count / (double)item.total_count * 100:f2}%";
+                        item.abandon_percentage = $"{item.abandon_count / (double)item.total_count * 100:f2}%";
+                    }
+                }
+
+                if (item.parse_total_count == 0)
+                {
+                    item.parse_total_count = await TkLogCore.GetTotalAsync($":parse_total:{accountName}:{item.report_date:yyyyMMddHH}");
+                    if (item.parse_total_count > 0)
+                    {
+                        item.parse_success_count = await TkLogCore.GetTotalAsync($":parse_total:{item.accountName}:success:{item.report_date:yyyyMMddHH}");
+                        item.parse_abandon_count = await TkLogCore.GetTotalAsync($":parse_total:{item.accountName}:放弃转链:{item.report_date:yyyyMMddHH}");
+                        if (item.parse_total_count > 0)
+                        {
+                            item.parse_success_percentage = $"{item.parse_success_count / (double)item.parse_total_count * 100:f2}%";
+                            item.parse_abandon_percentage = $"{item.parse_abandon_count / (double)item.parse_total_count * 100:f2}%";
+                        }
+                    }
+                }
+
+                if (item.coupon_total_count == 0)
+                {
+                    item.coupon_total_count = await TkLogCore.GetTotalAsync($":coupon_total:{accountName}:{item.report_date:yyyyMMddHH}", false);
+                    if (item.coupon_total_count > 0)
+                    {
+                        item.coupon_success_count = await TkLogCore.GetTotalAsync($":coupon_total:{item.accountName}:success:{item.report_date:yyyyMMddHH}", false);
+                        item.coupon_abandon_count = await TkLogCore.GetTotalAsync($":coupon_total:{item.accountName}:放弃转链:{item.report_date:yyyyMMddHH}", false);
+                        if (item.coupon_total_count > 0)
+                        {
+                            item.coupon_success_percentage = $"{item.coupon_success_count / (double)item.coupon_total_count * 100:f2}%";
+                            item.coupon_abandon_percentage = $"{item.coupon_abandon_count / (double)item.coupon_total_count * 100:f2}%";
+                        }
+                    }
+                }
+
+                if (item.tool_total_count == 0)
+                {
+                    item.tool_total_count = await TkLogCore.GetTotalAsync($":tool_total:{accountName}:{item.report_date:yyyyMMddHH}");
+                    if (item.tool_total_count > 0)
+                    {
+                        item.tool_success_count = await TkLogCore.GetTotalAsync($":tool_total:{item.accountName}:success:{item.report_date:yyyyMMddHH}");
+                        item.tool_abandon_count = await TkLogCore.GetTotalAsync($":tool_total:{item.accountName}:放弃转链:{item.report_date:yyyyMMddHH}");
+                        if (item.tool_total_count > 0)
+                        {
+                            item.tool_success_percentage = $"{item.tool_success_count / (double)item.tool_total_count * 100:f2}%";
+                            item.tool_abandon_percentage = $"{item.tool_abandon_count / (double)item.tool_total_count * 100:f2}%";
+                        }
+                    }
+                }
+            }
+            return new APIResult(new { data = result });
+        }
+
+
+        [HttpPost]
+        public async Task<ActionResult> chartData([FromBody] JsonElement form)
+        {
+            var result = new List<dynamic>();
+            var reason_result = new List<dynamic>();
+
+            var report_date = form.PathReadArray<string>("query_date[]");
+            var accountName = form.Read("accountName", string.Empty);
+            var isHourtrend = form.Read<bool>("isLeaf", false);
+            var total_key = form.Read("total_key", "total");
+
+            DateTime stime = DateTime.MinValue;
+            if (!DateTime.TryParse(report_date[0], out stime)) return new APIResult(new { data = result, data2 = reason_result });
+
+            string _report_date = stime.ToString("yyyyMMdd");
+            if (isHourtrend) _report_date = stime.ToString("yyyyMMddHH");
+
+
+
+            if (string.IsNullOrEmpty(accountName)) accountName = "tb";
+            if (total_key == "tool_total")
+            {
+                accountName = "tool";
+                total_key = "parse_total";
+
+                string cacheKey = $":{total_key}:{accountName}:{_report_date}";
+                double total = await TkLogCore.GetTotalAsync(cacheKey);
+                if (total > 0)
+                {
+                    string[] keys = ["bdpan", "wemeet"];
+
+                    foreach (var keyname in keys)
+                    {
+                        //                    string[] keys = ["bdpan:success", "bdpan:fail", "wemeet:success", "wemeet:fail"];
+
+                        cacheKey = $":{total_key}:{keyname}:{_report_date}";
+                        total = await TkLogCore.GetTotalAsync(cacheKey);
+
+                        cacheKey = $":{total_key}:{keyname}:success:{_report_date}";
+                        int value = await TkLogCore.GetTotalAsync(cacheKey);
+                        if (value == 0) continue;
+                        result.Add(new { name = $"{keyname}-成功", value, increases = Math.Round(value / total * 100, 2) });
+
+
+                        cacheKey = $":{total_key}:{keyname}:fail:{_report_date}";
+                        value = await TkLogCore.GetTotalAsync(cacheKey);
+                        if (value == 0) continue;
+                        result.Add(new { name = $"{keyname}-失败", value, increases = Math.Round(value / total * 100, 2) });
+                    }
+
+                    cacheKey = $":{total_key}:{accountName}:reason:{_report_date}";
+                    string[] reason_keys = await TkLogCore.GetTotalKeysAsync(cacheKey);
+
+                    foreach (var keyname in reason_keys)
+                    {
+                        cacheKey = $":{total_key}:{accountName}:{keyname}:{_report_date}";
+                        int value = await TkLogCore.GetTotalAsync(cacheKey);
+                        if (value == 0) continue;
+                        reason_result.Add(new { name = keyname, value, increases = Math.Round(value / total * 100, 2) });
+                    }
+                    reason_result = reason_result.OrderByDescending(item => item.value).ToList();
+                }
+                return new APIResult(new { total, data = result, data2 = reason_result });
+            }
+            else
+            {
+                string cacheKey = $":{total_key}:{accountName}:{_report_date}";
+                double total = await TkLogCore.GetTotalAsync(cacheKey, false);
+                if (total > 0)
+                {
+                    string[] keys = ["success"];
+
+                    cacheKey = $":{total_key}:{accountName}:message:{_report_date}";
+                    string[] message_keys = await TkLogCore.GetTotalKeysAsync(cacheKey, false);
+
+                    var combinedKeys = keys.Union(message_keys).ToList();
+                    combinedKeys.RemoveAll(item => item == "fail");
+                    combinedKeys.RemoveAll(item => item == "OK");
+
+                    foreach (var keyname in combinedKeys)
+                    {
+                        cacheKey = $":{total_key}:{accountName}:{keyname}:{_report_date}";
+                        int value = await TkLogCore.GetTotalAsync(cacheKey, false);
+                        if (value == 0) continue;
+                        result.Add(new { name = keyname, value, increases = Math.Round(value / total * 100, 2) });
+                    }
+                    result = result.OrderByDescending(item => item.value).ToList();
+
+                    cacheKey = $":{total_key}:{accountName}:reason:{_report_date}";
+                    string[] reason_keys = await TkLogCore.GetTotalKeysAsync(cacheKey, false);
+
+                    foreach (var keyname in reason_keys)
+                    {
+                        if ("没有优惠券".Equals(keyname)) continue;
+                        cacheKey = $":{total_key}:{accountName}:{keyname}:{_report_date}";
+                        int value = await TkLogCore.GetTotalAsync(cacheKey, false);
+                        if (value < 50) continue;
+                        reason_result.Add(new { name = keyname, value, increases = Math.Round(value / total * 100, 2) });
+                    }
+                    reason_result = reason_result.OrderByDescending(item => item.value).ToList();
+                }
+                return new APIResult(new { total, data = result, data2 = reason_result });
+            }
+        }
+
+        [HttpPost]
+        public async Task<ActionResult> chartData2([FromBody] JsonElement form)
+        {
+            var result = new List<dynamic>();
+            var reason_result = new List<dynamic>();
+
+            var report_date = form.PathReadArray<string>("query_date[]");
+            var accountName = form.Read("accountName", string.Empty);
+            var isHourtrend = form.Read<bool>("isLeaf", false);
+            var total_key = form.Read("total_key", "total");
+
+            if (string.IsNullOrEmpty(accountName)) accountName = "tb";
+
+            DateTime stime = DateTime.MinValue;
+            if (!DateTime.TryParse(report_date[0], out stime)) return new APIResult(new { data = result, data2 = reason_result });
+
+            string _report_date = stime.ToString("yyyyMMdd");
+            if (isHourtrend) _report_date = stime.ToString("yyyyMMddHH");
+
+            string cacheKey = $":{total_key}:{accountName}:{_report_date}";
+            double total = await TkLogCore.GetTotalAsync(cacheKey);
+            if (total > 0)
+            {
+                string[] keys = ["success"];
+
+                cacheKey = $":{total_key}:{accountName}:message:{_report_date}";
+                string[] message_keys = await TkLogCore.GetTotalKeysAsync(cacheKey);
+
+                var combinedKeys = keys.Union(message_keys).ToList();
+                combinedKeys.RemoveAll(item => item == "fail");
+                combinedKeys.RemoveAll(item => item == "OK");
+
+
+                foreach (var keyname in combinedKeys)
+                {
+                    cacheKey = $":{total_key}:{accountName}:{keyname}:{_report_date}";
+                    int value = await TkLogCore.GetTotalAsync(cacheKey);
+                    if (value == 0) continue;
+                    result.Add(new { name = keyname, value, increases = Math.Round(value / total * 100, 2) });
+                }
+                result = result.OrderByDescending(item => item.value).ToList();
+
+                cacheKey = $":{total_key}:{accountName}:reason:{_report_date}";
+                string[] reason_keys = await TkLogCore.GetTotalKeysAsync(cacheKey);
+
+                foreach (var keyname in reason_keys)
+                {
+                    if ("没有优惠券".Equals(keyname)) continue;
+                    cacheKey = $":{total_key}:{accountName}:{keyname}:{_report_date}";
+                    int value = await TkLogCore.GetTotalAsync(cacheKey);
+                    if (value == 0) continue;
+                    reason_result.Add(new { name = keyname, value, increases = Math.Round(value / total * 100, 2) });
+                }
+                reason_result = reason_result.OrderByDescending(item => item.value).ToList();
+            }
+            return new APIResult(new { total, data = result, data2 = reason_result });
+        }
+
+
+
+        [HttpPost]
+        public ActionResult settle_bills_total([FromBody] JsonElement form)
+        {
+            int page = form.Read("current", 1);
+            int size = form.Read("pageSize", 10);
+            bool getTotal = form.Read("getTotal", true);
+            string sort = form.Read<string>("sort");
+            string order = form.Read<string>("order");
+
+            var belongTime = form.PathReadArray<string>("query_date[]");
+
+            string filter = string.Empty;
+            DateTime stime = DateTime.MinValue, etime = DateTime.MinValue;
+            if (belongTime.Count == 2)
+            {
+                if (DateTime.TryParse(belongTime[0], out stime) && DateTime.TryParse(belongTime[1], out etime))
+                {
+                    etime = etime.AddDays(1).AddSeconds(-1);
+                    filter += $" AND belongTime BETWEEN @stime AND @etime";
+                }
+            }
+
+            filter = filter.StringTrimStart(" AND ");
+
+            //排序
+            string orderBy = "belongTime DESC";
+            if (!string.IsNullOrEmpty(order))
+            {
+                order = "descending".Equals(order) ? "DESC" : "ASC";
+                orderBy = sort switch
+                {
+                    _ => $"{sort} {order}",
+                };
+            }
+            var result = new DBContext.Table("v_tk_report_bills")
+                .Where(filter, new { stime, etime })
+                .Page(size, page)
+                .Order(orderBy)
+                .PageList<TkSettleBillDTO>(getTotal);
+
+            return new APIResult(new { data = result });
+        }
+
+
+        [HttpPost]
+        public ActionResult settle_bills([FromBody] JsonElement form)
+        {
+            int page = form.Read("current", 1);
+            int size = form.Read("pageSize", 10);
+            bool getTotal = form.Read("getTotal", true);
+            string sort = form.Read<string>("sort");
+            string order = form.Read<string>("order");
+            string name = form.Read<string>("keyword");
+
+            var belongTime = form.PathReadArray<string>("query_date[]");
+
+            string filter = string.Empty;
+            if (!string.IsNullOrEmpty(name))
+            {
+                filter += $" AND accountId IN (SELECT ID FROM tk_pool WHERE company=@name)";
+            }
+            DateTime stime = DateTime.MinValue, etime = DateTime.MinValue;
+            if (belongTime.Count == 2)
+            {
+                if (DateTime.TryParse(belongTime[0], out stime) && DateTime.TryParse(belongTime[1], out etime))
+                {
+                    etime = etime.AddDays(1).AddSeconds(-1);
+                    filter += $" AND belongTime BETWEEN @stime AND @etime";
+                }
+            }
+
+            filter = filter.StringTrimStart(" AND ");
+
+            //排序
+            string orderBy = "belongTime DESC";
+            if (!string.IsNullOrEmpty(order))
+            {
+                order = "descending".Equals(order) ? "DESC" : "ASC";
+                orderBy = sort switch
+                {
+                    _ => $"{sort} {order}",
+                };
+            }
+
+            var result = new DBContext.Table("tk_settle_bill")
+                .Where(filter, new { name, stime, etime })
+                .Page(size, page)
+                .Order(orderBy)
+                .PageList<TkSettleBillDTO>(getTotal);
+
+
+            //foreach (var item in result.List)
+            //{
+            //    if (string.IsNullOrEmpty(name))
+            //    {
+            //        item.accountName = "-";
+            //    }
+            //}
+            return new APIResult(new { data = result });
+        }
+
+    }
 }

+ 99 - 99
molilian.api/Controllers/admin/TaobaoOrderController.cs

@@ -1,100 +1,100 @@
-using molilian.core;
-using dodohold.core;
-using Microsoft.AspNetCore.Mvc;
-using System.Text.Json;
-namespace molilian.api.Controllers
-{
-    [ApiController]
-    [MyAuthorize("admin")]
-    [Route("api/[controller]/[action]")]
-    public class TaobaoOrderController : ControllerBase
-    {
-        readonly IAuthorizationProvider provider = new AdminProvider();
-        protected IHttpContextAccessor _accessor;
-        public TaobaoOrderController(IHttpContextAccessor accessor)
-        {
-            _accessor = accessor;
-        }
-
-
-        [HttpPost]
-        public ActionResult list([FromBody] JsonElement form)
-        {
-            int page = form.Read("current", 1);
-            int size = form.Read("pageSize", 10);
-            bool getTotal = form.Read("getTotal", true);
-            string sort = form.Read<string>("sort");
-            string order = form.Read<string>("order");
-            string name = form.Read<string>("keyword");
-            string query_type = form.Read<string>("query_type", "tbPaidTime");
-            var tkStatus = form.PathReadArray<int>("tkStatus[]");
-            int maxTotal = form.Read("maxTotal", 0);
-
-            var time = form.PathReadArray<string>("query_date[]");
-
-            string filter = string.Empty;
-            if (!string.IsNullOrEmpty(name))
-            {
-                filter += $" AND accountId IN (SELECT ID FROM tk_pool WHERE company=@name)";
-            }
-            else
-            {
-                filter += $" AND accountId NOT IN (SELECT ID FROM tk_pool WHERE is_hide=1)";
-            }
-            if (tkStatus.Any())
-            {
-                filter += $" AND tkStatus IN @tkStatus";
-            }
-
-            DateTime stime = DateTime.MinValue, etime = DateTime.MinValue;
-            if (time.Count == 2)
-            {
-                if (DateTime.TryParse(time[0], out stime) && DateTime.TryParse(time[1], out etime))
-                {
-                    etime = etime.AddDays(1).AddSeconds(-1);
-
-                    filter += query_type switch
-                    {
-                        "tkEarningTime" or "tkCreateTime" => $" AND {query_type} BETWEEN @stime AND @etime",
-                        _ => $" AND tbPaidTime BETWEEN @stime AND @etime",
-                    };
-                }
-            }
-            filter = filter.StringTrimStart(" AND ");
-
-            //排序
-            string orderBy = $"{query_type} DESC";
-            if (!string.IsNullOrEmpty(order))
-            {
-                order = "descending".Equals(order) ? "DESC" : "ASC";
-                orderBy = sort switch
-                {
-                    _ => $"{sort} {order}",
-                };
-            }
-
-            var result = new DBContext.Table("tk_order_details")
-                .Where(filter, new { name, stime, etime, tkStatus })
-                .Page(size, page)
-                .Order(orderBy)
-                .PageList<TkOrderDetailDTO>(getTotal);
-
-
-            //foreach (var item in result.List)
-            //{
-            //    if (string.IsNullOrEmpty(name))
-            //    {
-            //        item.accountName = "-";
-            //    }
-            //}
-
-            if (maxTotal != 0 && result.Count > maxTotal)
-            {
-                result.Count = maxTotal;
-                result.PageCount = maxTotal / size;
-            }
-            return new APIResult(new { data = result });
-        }
-
-    }
+using molilian.core;
+using dodohold.core;
+using Microsoft.AspNetCore.Mvc;
+using System.Text.Json;
+namespace molilian.api.Controllers
+{
+    [ApiController]
+    [MyAuthorize("admin")]
+    [Route("api/[controller]/[action]")]
+    public class TaobaoOrderController : ControllerBase
+    {
+        readonly IAuthorizationProvider provider = new AdminProvider();
+        protected IHttpContextAccessor _accessor;
+        public TaobaoOrderController(IHttpContextAccessor accessor)
+        {
+            _accessor = accessor;
+        }
+
+
+        [HttpPost]
+        public ActionResult list([FromBody] JsonElement form)
+        {
+            int page = form.Read("current", 1);
+            int size = form.Read("pageSize", 10);
+            bool getTotal = form.Read("getTotal", true);
+            string sort = form.Read<string>("sort");
+            string order = form.Read<string>("order");
+            string name = form.Read<string>("keyword");
+            string query_type = form.Read<string>("query_type", "tbPaidTime");
+            var tkStatus = form.PathReadArray<int>("tkStatus[]");
+            int maxTotal = form.Read("maxTotal", 0);
+
+            var time = form.PathReadArray<string>("query_date[]");
+
+            string filter = string.Empty;
+            if (!string.IsNullOrEmpty(name))
+            {
+                filter += $" AND accountId IN (SELECT ID FROM tk_pool WHERE company=@name)";
+            }
+            else
+            {
+                filter += $" AND accountId NOT IN (SELECT ID FROM tk_pool WHERE is_hide=1)";
+            }
+            if (tkStatus.Any())
+            {
+                filter += $" AND tkStatus IN @tkStatus";
+            }
+
+            DateTime stime = DateTime.MinValue, etime = DateTime.MinValue;
+            if (time.Count == 2)
+            {
+                if (DateTime.TryParse(time[0], out stime) && DateTime.TryParse(time[1], out etime))
+                {
+                    etime = etime.AddDays(1).AddSeconds(-1);
+
+                    filter += query_type switch
+                    {
+                        "tkEarningTime" or "tkCreateTime" => $" AND {query_type} BETWEEN @stime AND @etime",
+                        _ => $" AND tbPaidTime BETWEEN @stime AND @etime",
+                    };
+                }
+            }
+            filter = filter.StringTrimStart(" AND ");
+
+            //排序
+            string orderBy = $"{query_type} DESC";
+            if (!string.IsNullOrEmpty(order))
+            {
+                order = "descending".Equals(order) ? "DESC" : "ASC";
+                orderBy = sort switch
+                {
+                    _ => $"{sort} {order}",
+                };
+            }
+
+            var result = new DBContext.Table("tk_order_details")
+                .Where(filter, new { name, stime, etime, tkStatus })
+                .Page(size, page)
+                .Order(orderBy)
+                .PageList<TkOrderDetailDTO>(getTotal);
+
+
+            //foreach (var item in result.List)
+            //{
+            //    if (string.IsNullOrEmpty(name))
+            //    {
+            //        item.accountName = "-";
+            //    }
+            //}
+
+            if (maxTotal != 0 && result.Count > maxTotal)
+            {
+                result.Count = maxTotal;
+                result.PageCount = maxTotal / size;
+            }
+            return new APIResult(new { data = result });
+        }
+
+    }
 }

+ 73 - 73
molilian.api/Controllers/admin/UserController.cs

@@ -1,74 +1,74 @@
-using molilian.core;
-using dodohold.core;
-using Microsoft.AspNetCore.Mvc;
-using Org.BouncyCastle.Ocsp;
-using System.Text.Json;
-using System.Runtime.InteropServices;
-using TencentCloud.Scf.V20180416.Models;
-using Quartz.Impl.AdoJobStore.Common;
-using TencentCloud.Gaap.V20180529.Models;
-
-namespace molilian.api.Controllers
-{
-    [ApiController]
-    [Route("api/[controller]/[action]")]
-    public class UserController : ControllerBase
-    {
-        readonly IAuthorizationProvider provider = new AdminProvider();
-        protected IHttpContextAccessor _accessor;
-        public UserController(IHttpContextAccessor accessor)
-        {
-            _accessor = accessor;
-        }
-
-        [HttpPost]
-        public ActionResult login([FromBody] JsonElement form)
-        {
-            var domain = _accessor.HttpContext.GetApiDomain();
-            string username = form.Read("username", string.Empty);
-            string password = form.Read("password", string.Empty);
-            if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password)) return new APIResult(new { msg = "账号密码不正确" }, APIResultCodeEnum.InvalidRequest);
-
-            var user = new DBContext.Table("admins").Get<dynamic>("username=@username", new { username });
-            if (user == null) return new APIResult(new { msg = "账号密码不正确" }, APIResultCodeEnum.InvalidRequest);
-            if (user.status != 1) return new APIResult(new { msg = "账号密码不正确" }, APIResultCodeEnum.InvalidRequest);
-
-            string dbPassword = user.password;
-
-            var config = TkConfigCore.Get();
-            string pwd = $"{password}{config.pass_salt}".MD5();
-            if (!dbPassword.Equals(pwd)) return new APIResult(new { msg = "账号密码不正确" }, APIResultCodeEnum.InvalidRequest);
-
-            //检查登录,然后发布token
-            var accessToken = provider.Create(user.id, username, password, user.roles, string.Empty, domain, _accessor.HttpContext.GetUserIp());
-            if (accessToken == null)
-            {
-                return new APIResult(new { msg = "账号/密码无效或已被禁用" }, APIResultCodeEnum.InvalidRequest);
-            }
-            return new APIResult(new { data = accessToken });
-        }
-
-
-        [HttpPost]
-        public ActionResult logout()
-        {
-            (var ticket, var domain) = _accessor.HttpContext.GetAccessToken();
-            provider.Revoke(domain, ticket);
-            return new APIResult(new { success = true, message = "ok" });
-        }
-
-
-        [HttpPost]
-        public ActionResult menu()
-        {
-            return new APIResult(new { success = true, message = "ok" });
-        }
-
-        [HttpPost]
-        public ActionResult info()
-        {
-            return new APIResult(new { success = true, message = "ok" });
-        }
-
-    }
+using molilian.core;
+using dodohold.core;
+using Microsoft.AspNetCore.Mvc;
+using Org.BouncyCastle.Ocsp;
+using System.Text.Json;
+using System.Runtime.InteropServices;
+using TencentCloud.Scf.V20180416.Models;
+using Quartz.Impl.AdoJobStore.Common;
+using TencentCloud.Gaap.V20180529.Models;
+
+namespace molilian.api.Controllers
+{
+    [ApiController]
+    [Route("api/[controller]/[action]")]
+    public class UserController : ControllerBase
+    {
+        readonly IAuthorizationProvider provider = new AdminProvider();
+        protected IHttpContextAccessor _accessor;
+        public UserController(IHttpContextAccessor accessor)
+        {
+            _accessor = accessor;
+        }
+
+        [HttpPost]
+        public ActionResult login([FromBody] JsonElement form)
+        {
+            var domain = _accessor.HttpContext.GetApiDomain();
+            string username = form.Read("username", string.Empty);
+            string password = form.Read("password", string.Empty);
+            if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password)) return new APIResult(new { msg = "账号密码不正确" }, APIResultCodeEnum.InvalidRequest);
+
+            var user = new DBContext.Table("admins").Get<dynamic>("username=@username", new { username });
+            if (user == null) return new APIResult(new { msg = "账号密码不正确" }, APIResultCodeEnum.InvalidRequest);
+            if (user.status != 1) return new APIResult(new { msg = "账号密码不正确" }, APIResultCodeEnum.InvalidRequest);
+
+            string dbPassword = user.password;
+
+            var config = TkConfigCore.Get();
+            string pwd = $"{password}{config.pass_salt}".MD5();
+            if (!dbPassword.Equals(pwd)) return new APIResult(new { msg = "账号密码不正确" }, APIResultCodeEnum.InvalidRequest);
+
+            //检查登录,然后发布token
+            var accessToken = provider.Create(user.id, username, password, user.roles, string.Empty, domain, _accessor.HttpContext.GetUserIp());
+            if (accessToken == null)
+            {
+                return new APIResult(new { msg = "账号/密码无效或已被禁用" }, APIResultCodeEnum.InvalidRequest);
+            }
+            return new APIResult(new { data = accessToken });
+        }
+
+
+        [HttpPost]
+        public ActionResult logout()
+        {
+            (var ticket, var domain) = _accessor.HttpContext.GetAccessToken();
+            provider.Revoke(domain, ticket);
+            return new APIResult(new { success = true, message = "ok" });
+        }
+
+
+        [HttpPost]
+        public ActionResult menu()
+        {
+            return new APIResult(new { success = true, message = "ok" });
+        }
+
+        [HttpPost]
+        public ActionResult info()
+        {
+            return new APIResult(new { success = true, message = "ok" });
+        }
+
+    }
 }

+ 162 - 162
molilian.api/Controllers/public/ApiController.cs

@@ -1,163 +1,163 @@
-using molilian.core;
-using dodohold.core;
-using Microsoft.AspNetCore.Mvc;
-using Org.BouncyCastle.Ocsp;
-using System.Text.Json;
-using System.Runtime.InteropServices;
-using System.Net;
-using System.Security.Cryptography;
-using Microsoft.AspNetCore.Authentication;
-using TencentCloud.Ecm.V20190719.Models;
-using COSXML.Network;
-using System.Linq;
-using static dodohold.core.ZTOExpress.CreateOrderArgs;
-using TencentCloud.Lighthouse.V20200324.Models;
-using TencentCloud.Ame.V20190916.Models;
-using TencentCloud.Omics.V20221128.Models;
-using System.Threading.Channels;
-
-namespace molilian.api.Controllers
-{
-    [ApiController]
-    [Route("api/[action]")]
-    public class ApiController : ControllerBase
-    {
-        protected IHttpContextAccessor _accessor;
-        TaokeOpenCore core;
-        public ApiController(IHttpContextAccessor accessor)
-        {
-            _accessor = accessor;
-            core = new TaokeOpenCore();
-        }
-
-        //[HttpGet]
-        //public async Task<ActionResult> RequestAsync([FromQuery] string url, [FromQuery] int proxyId)
-        //{
-        //    if (string.IsNullOrEmpty(url))
-        //    {
-        //        return BadRequest("URL cannot be empty");
-        //    }
-
-        //    var proxy = ProxyNodesCore.GetOne(proxyId);
-        //    if (proxy == null)
-        //    {
-        //        return Unauthorized("Invalid proxy configuration");
-        //    }
-        //    WebClientUtility cli = new()
-        //    {
-        //        Proxy = proxy
-        //    };
-        //    await cli.RequestAsync(url);
-        //    var content = cli.Body();
-        //    var contentType = cli.ResponseMessage.Content.Headers.ContentType?.MediaType ?? "text/plain";
-        //    return Content(content, contentType);
-        //}
-
-        [HttpGet]
-        public async Task<ActionResult> RequestAsync([FromQuery] string url, [FromQuery] string proxy)
-        {
-            if (string.IsNullOrEmpty(url))
-            {
-                return BadRequest("URL cannot be empty");
-            }
-
-            WebProxy _proxy = ProxyManager.Parse(proxy);
-            WebClientUtility cli = new();
-            cli.Proxy = _proxy;
-            cli.Timeout = TimeSpan.FromSeconds(10);
-            await cli.RequestAsync(url);
-            if (cli.Successed)
-            {
-                var content = cli.Body();
-
-                var contentType = cli.ResponseMessage.Content.Headers.ContentType?.MediaType ?? "text/plain";
-                return Content(content, contentType);
-            }
-            throw cli.ResponseException;
-        }
-
-
-        [HttpPost]
-        public async Task<ActionResult> PromotionQuery([FromBody] JsonElement form, [FromQuery] string a = "", [FromQuery] int t = 0, [FromQuery] string sign = "")
-        {
-            var img = form.Read("img", string.Empty);
-            var url = form.Read("url", string.Empty);
-            var ip = form.Read("ip", string.Empty);
-            var oaid = form.Read("oaid", string.Empty);
-            var scene = form.Read("scene", string.Empty);
-            var recommand = form.Read("recommand", true);
-            var aid = form.Read("aid", 0);
-
-            //验证签名
-            if (string.IsNullOrEmpty(a) || string.IsNullOrEmpty(sign))
-            {
-                return new APIResult(new { success = false, message = "验证错误" }, APIResultCodeEnum.Unauthorized);
-            }
-
-            DateTime time1 = t.Convert2Datetime();
-            if (time1 < DateTime.Now.AddMinutes(-10))
-            {
-                return new APIResult(new { success = false, message = "验证错误2" }, APIResultCodeEnum.Unauthorized);
-            }
-
-            var account = await ApiAccountCore.GetOneAsync(a);
-            if (account == null)
-            {
-                return new APIResult(new { success = false, message = "验证错误3" }, APIResultCodeEnum.Unauthorized);
-            }
-            string app_sign = $"{account.api_secret}@{t}".MD5();
-            if (sign != app_sign)
-            {
-                return new APIResult(new { success = false, message = "验证错误4" }, APIResultCodeEnum.Unauthorized);
-            }
-
-            if (!string.IsNullOrEmpty(url))
-            {
-                var bytes = new WebClientUtility().Request(url).ResponseBody;
-                img = Convert.ToBase64String(bytes);
-            }
-            return await core.PromotionQueryAsync(img, oaid, ip, scene, recommand, false, aid);
-        }
-
-
-        [HttpPost]
-        public async Task<ActionResult> unsafePromotionQuery([FromBody] JsonElement form)
-        {
-            var img = form.Read("img", string.Empty);
-            var url = form.Read("url", string.Empty);
-            var ip = form.Read("ip", string.Empty);
-            var oaid = form.Read("oaid", string.Empty);
-            var scene = form.Read("scene", string.Empty);
-            var recommand = form.Read("recommand", true);
-            var debug = form.Read("debug", false);
-            var aid = form.Read("aid", 0);
-
-            int count = form.Read("count", 0);
-
-            if (!string.IsNullOrEmpty(url))
-            {
-                var bytes = new WebClientUtility().Request(url).ResponseBody;
-                img = Convert.ToBase64String(bytes);
-            }
-
-            if (string.IsNullOrEmpty(oaid))
-            {
-                Random rand = new Random();
-                oaid = $"{rand.Next(100000, 999999)}-{rand.Next(100000, 999999)}-{rand.Next(100000, 999999)}";
-            }
-
-            if (count > 1)
-            {
-                for (int i = 0; i < count; i++)
-                {
-                    _ = core.PromotionQueryAsync(img, oaid, ip, scene, recommand, debug, aid);
-                }
-                return new APIResult(new { msg = "ok" });
-            }
-            return await core.PromotionQueryAsync(img, oaid, ip, scene, recommand, debug, aid);
-        }
-
-
-
-    }
+using molilian.core;
+using dodohold.core;
+using Microsoft.AspNetCore.Mvc;
+using Org.BouncyCastle.Ocsp;
+using System.Text.Json;
+using System.Runtime.InteropServices;
+using System.Net;
+using System.Security.Cryptography;
+using Microsoft.AspNetCore.Authentication;
+using TencentCloud.Ecm.V20190719.Models;
+using COSXML.Network;
+using System.Linq;
+using static dodohold.core.ZTOExpress.CreateOrderArgs;
+using TencentCloud.Lighthouse.V20200324.Models;
+using TencentCloud.Ame.V20190916.Models;
+using TencentCloud.Omics.V20221128.Models;
+using System.Threading.Channels;
+
+namespace molilian.api.Controllers
+{
+    [ApiController]
+    [Route("api/[action]")]
+    public class ApiController : ControllerBase
+    {
+        protected IHttpContextAccessor _accessor;
+        TaokeOpenCore core;
+        public ApiController(IHttpContextAccessor accessor)
+        {
+            _accessor = accessor;
+            core = new TaokeOpenCore();
+        }
+
+        //[HttpGet]
+        //public async Task<ActionResult> RequestAsync([FromQuery] string url, [FromQuery] int proxyId)
+        //{
+        //    if (string.IsNullOrEmpty(url))
+        //    {
+        //        return BadRequest("URL cannot be empty");
+        //    }
+
+        //    var proxy = ProxyNodesCore.GetOne(proxyId);
+        //    if (proxy == null)
+        //    {
+        //        return Unauthorized("Invalid proxy configuration");
+        //    }
+        //    WebClientUtility cli = new()
+        //    {
+        //        Proxy = proxy
+        //    };
+        //    await cli.RequestAsync(url);
+        //    var content = cli.Body();
+        //    var contentType = cli.ResponseMessage.Content.Headers.ContentType?.MediaType ?? "text/plain";
+        //    return Content(content, contentType);
+        //}
+
+        [HttpGet]
+        public async Task<ActionResult> RequestAsync([FromQuery] string url, [FromQuery] string proxy)
+        {
+            if (string.IsNullOrEmpty(url))
+            {
+                return BadRequest("URL cannot be empty");
+            }
+
+            WebProxy _proxy = ProxyManager.Parse(proxy);
+            WebClientUtility cli = new();
+            cli.Proxy = _proxy;
+            cli.Timeout = TimeSpan.FromSeconds(10);
+            await cli.RequestAsync(url);
+            if (cli.Successed)
+            {
+                var content = cli.Body();
+
+                var contentType = cli.ResponseMessage.Content.Headers.ContentType?.MediaType ?? "text/plain";
+                return Content(content, contentType);
+            }
+            throw cli.ResponseException;
+        }
+
+
+        [HttpPost]
+        public async Task<ActionResult> PromotionQuery([FromBody] JsonElement form, [FromQuery] string a = "", [FromQuery] int t = 0, [FromQuery] string sign = "")
+        {
+            var img = form.Read("img", string.Empty);
+            var url = form.Read("url", string.Empty);
+            var ip = form.Read("ip", string.Empty);
+            var oaid = form.Read("oaid", string.Empty);
+            var scene = form.Read("scene", string.Empty);
+            var recommand = form.Read("recommand", true);
+            var aid = form.Read("aid", 0);
+
+            //验证签名
+            if (string.IsNullOrEmpty(a) || string.IsNullOrEmpty(sign))
+            {
+                return new APIResult(new { success = false, message = "验证错误" }, APIResultCodeEnum.Unauthorized);
+            }
+
+            DateTime time1 = t.Convert2Datetime();
+            if (time1 < DateTime.Now.AddMinutes(-10))
+            {
+                return new APIResult(new { success = false, message = "验证错误2" }, APIResultCodeEnum.Unauthorized);
+            }
+
+            var account = await ApiAccountCore.GetOneAsync(a);
+            if (account == null)
+            {
+                return new APIResult(new { success = false, message = "验证错误3" }, APIResultCodeEnum.Unauthorized);
+            }
+            string app_sign = $"{account.api_secret}@{t}".MD5();
+            if (sign != app_sign)
+            {
+                return new APIResult(new { success = false, message = "验证错误4" }, APIResultCodeEnum.Unauthorized);
+            }
+
+            if (!string.IsNullOrEmpty(url))
+            {
+                var bytes = new WebClientUtility().Request(url).ResponseBody;
+                img = Convert.ToBase64String(bytes);
+            }
+            return await core.PromotionQueryAsync(img, oaid, ip, scene, recommand, false, aid);
+        }
+
+
+        [HttpPost]
+        public async Task<ActionResult> unsafePromotionQuery([FromBody] JsonElement form)
+        {
+            var img = form.Read("img", string.Empty);
+            var url = form.Read("url", string.Empty);
+            var ip = form.Read("ip", string.Empty);
+            var oaid = form.Read("oaid", string.Empty);
+            var scene = form.Read("scene", string.Empty);
+            var recommand = form.Read("recommand", true);
+            var debug = form.Read("debug", false);
+            var aid = form.Read("aid", 0);
+
+            int count = form.Read("count", 0);
+
+            if (!string.IsNullOrEmpty(url))
+            {
+                var bytes = new WebClientUtility().Request(url).ResponseBody;
+                img = Convert.ToBase64String(bytes);
+            }
+
+            if (string.IsNullOrEmpty(oaid))
+            {
+                Random rand = new Random();
+                oaid = $"{rand.Next(100000, 999999)}-{rand.Next(100000, 999999)}-{rand.Next(100000, 999999)}";
+            }
+
+            if (count > 1)
+            {
+                for (int i = 0; i < count; i++)
+                {
+                    _ = core.PromotionQueryAsync(img, oaid, ip, scene, recommand, debug, aid);
+                }
+                return new APIResult(new { msg = "ok" });
+            }
+            return await core.PromotionQueryAsync(img, oaid, ip, scene, recommand, debug, aid);
+        }
+
+
+
+    }
 }

+ 28 - 0
molilian.api/Controllers/public/CouponController.cs

@@ -0,0 +1,28 @@
+using molilian.core;
+using dodohold.core;
+using Microsoft.AspNetCore.Mvc;
+using System.Text.Json;
+using YunhuiKit;
+
+namespace molilian.api.Controllers
+{
+    [ApiController]
+    [Route("[controller]/[action]")]
+    public class CouponController : ControllerBase
+    {
+        protected IHttpContextAccessor _accessor;
+        public CouponController(IHttpContextAccessor accessor)
+        {
+            _accessor = accessor;
+        }
+
+        [HttpPost]
+        [HttpGet]
+        public ActionResult Activities([FromBody] JsonElement form)
+        {
+            var channel = form.Read("c", string.Empty);
+            var response = CouponCenterCore.GetActivities(channel);
+            return new APIResult(response);
+        }
+    }
+}

+ 93 - 93
molilian.api/Controllers/public/CpsController.cs

@@ -1,94 +1,94 @@
-using molilian.core;
-using dodohold.core;
-using Microsoft.AspNetCore.Mvc;
-using Org.BouncyCastle.Ocsp;
-using System.Text.Json;
-using System.Runtime.InteropServices;
-using System.Net;
-using System.Security.Cryptography;
-using Microsoft.AspNetCore.Authentication;
-using TencentCloud.Ecm.V20190719.Models;
-using COSXML.Network;
-using System.Linq;
-using static dodohold.core.ZTOExpress.CreateOrderArgs;
-using MySqlX.XDevAPI;
-
-namespace molilian.api.Controllers
-{
-    [ApiController]
-    [Route("[controller]/[action]")]
-    public class CpsController : ControllerBase
-    {
-
-        protected IHttpContextAccessor _accessor;
-        public CpsController(IHttpContextAccessor accessor)
-        {
-            _accessor = accessor;
-        }
-
-        /// <summary>
-        /// oppo调用
-        /// </summary>
-        /// <param name="c">渠道。tb/jd</param>
-        /// <param name="a">分配的客户端账号</param>
-        /// <param name="t">11位时间戳</param>
-        /// <param name="sign">签名</param>
-        /// <param name="ip">客户的IP</param>
-        /// <param name="oaid">客户的唯一ID</param>
-        /// <returns></returns>
-        [HttpPost]
-        public async Task<ActionResult> getLink([FromBody] JsonElement form, [FromQuery] string a = "", [FromQuery] int t = 0, [FromQuery] string sign = "")
-        {
-            var channel = form.Read("c", string.Empty);
-            var ip = form.Read("ip", string.Empty);
-            var oaid = form.Read("oaid", string.Empty);
-            var style = form.Read("style", string.Empty);
-
-#if DEBUG
-#else
-
-            //验证签名
-            if (string.IsNullOrEmpty(a) || string.IsNullOrEmpty(sign))
-            {
-                return new APIResult(new { success = false, message = "验证错误" }, APIResultCodeEnum.Unauthorized);
-            }
-
-            DateTime time1 = t.Convert2Datetime();
-            if (time1 < DateTime.Now.AddMinutes(-10))
-            {
-                return new APIResult(new { success = false, message = "验证错误2" }, APIResultCodeEnum.Unauthorized);
-            }
-
-            var account = await ApiAccountCore.GetOneAsync(a);
-            if (account == null)
-            {
-                return new APIResult(new { success = false, message = "验证错误3" }, APIResultCodeEnum.Unauthorized);
-            }
-            string app_sign = $"{account.api_secret}@{t}".MD5();
-            if (sign != app_sign)
-            {
-                return new APIResult(new { success = false, message = "验证错误4" }, APIResultCodeEnum.Unauthorized);
-            }
-
-
-#endif 
-            return await UnionCpsCore.UnionCpsAsync(channel, style, ip, oaid);
-        }
-
-        [HttpPost]
-        public async Task<ActionResult> unsafeGetLink([FromBody] JsonElement form)
-        {
-            var channel = form.Read("c", string.Empty);
-            var ip = form.Read("ip", string.Empty);
-            var oaid = form.Read("oaid", string.Empty);
-            var style = form.Read("style", string.Empty);
-#if DEBUG
-            ip = "127.0.0.1";
-            oaid = "test-oaid";
-#endif
-            return await UnionCpsCore.UnionCpsAsync(channel, style, ip, oaid);
-        }
-
-
-    }
+using molilian.core;
+using dodohold.core;
+using Microsoft.AspNetCore.Mvc;
+using Org.BouncyCastle.Ocsp;
+using System.Text.Json;
+using System.Runtime.InteropServices;
+using System.Net;
+using System.Security.Cryptography;
+using Microsoft.AspNetCore.Authentication;
+using TencentCloud.Ecm.V20190719.Models;
+using COSXML.Network;
+using System.Linq;
+using static dodohold.core.ZTOExpress.CreateOrderArgs;
+using MySqlX.XDevAPI;
+
+namespace molilian.api.Controllers
+{
+    [ApiController]
+    [Route("[controller]/[action]")]
+    public class CpsController : ControllerBase
+    {
+
+        protected IHttpContextAccessor _accessor;
+        public CpsController(IHttpContextAccessor accessor)
+        {
+            _accessor = accessor;
+        }
+
+        /// <summary>
+        /// oppo调用
+        /// </summary>
+        /// <param name="c">渠道。tb/jd</param>
+        /// <param name="a">分配的客户端账号</param>
+        /// <param name="t">11位时间戳</param>
+        /// <param name="sign">签名</param>
+        /// <param name="ip">客户的IP</param>
+        /// <param name="oaid">客户的唯一ID</param>
+        /// <returns></returns>
+        [HttpPost]
+        public async Task<ActionResult> getLink([FromBody] JsonElement form, [FromQuery] string a = "", [FromQuery] int t = 0, [FromQuery] string sign = "")
+        {
+            var channel = form.Read("c", string.Empty);
+            var ip = form.Read("ip", string.Empty);
+            var oaid = form.Read("oaid", string.Empty);
+            var style = form.Read("style", string.Empty);
+
+#if DEBUG
+#else
+
+            //验证签名
+            if (string.IsNullOrEmpty(a) || string.IsNullOrEmpty(sign))
+            {
+                return new APIResult(new { success = false, message = "验证错误" }, APIResultCodeEnum.Unauthorized);
+            }
+
+            DateTime time1 = t.Convert2Datetime();
+            if (time1 < DateTime.Now.AddMinutes(-10))
+            {
+                return new APIResult(new { success = false, message = "验证错误2" }, APIResultCodeEnum.Unauthorized);
+            }
+
+            var account = await ApiAccountCore.GetOneAsync(a);
+            if (account == null)
+            {
+                return new APIResult(new { success = false, message = "验证错误3" }, APIResultCodeEnum.Unauthorized);
+            }
+            string app_sign = $"{account.api_secret}@{t}".MD5();
+            if (sign != app_sign)
+            {
+                return new APIResult(new { success = false, message = "验证错误4" }, APIResultCodeEnum.Unauthorized);
+            }
+
+
+#endif 
+            return await UnionCpsCore.UnionCpsAsync(channel, style, ip, oaid);
+        }
+
+        [HttpPost]
+        public async Task<ActionResult> unsafeGetLink([FromBody] JsonElement form)
+        {
+            var channel = form.Read("c", string.Empty);
+            var ip = form.Read("ip", string.Empty);
+            var oaid = form.Read("oaid", string.Empty);
+            var style = form.Read("style", string.Empty);
+#if DEBUG
+            ip = "127.0.0.1";
+            oaid = "test-oaid";
+#endif
+            return await UnionCpsCore.UnionCpsAsync(channel, style, ip, oaid);
+        }
+
+
+    }
 }

+ 185 - 185
molilian.api/Controllers/public/H5Controller.cs

@@ -1,186 +1,186 @@
-using molilian.core;
-using dodohold.core;
-using Microsoft.AspNetCore.Mvc;
-using Org.BouncyCastle.Ocsp;
-using System.Text.Json;
-using System.Runtime.InteropServices;
-using System.Net;
-using System.Security.Cryptography;
-using Microsoft.AspNetCore.Authentication;
-using TencentCloud.Ecm.V20190719.Models;
-using Dataoke;
-using System.Web;
-
-namespace molilian.api.Controllers
-{
-    [ApiController]
-    [Route("[controller]/[action]")]
-    public class H5Controller : ControllerBase
-    {
-
-        protected IHttpContextAccessor _accessor;
-        public H5Controller(IHttpContextAccessor accessor)
-        {
-            _accessor = accessor;
-        }
-
-
-        [HttpGet]
-        public ActionResult sign()
-        {
-            string referer = _accessor.HttpContext.Request.Referer();
-
-            var api = DataokeCore.GetOne(referer);
-            if (api == null)
-            {
-                return new APIResult(new
-                {
-                    success = false,
-                    message = "没有可用签名账户"
-                });
-            }
-
-            int nonce;
-            string timer;
-            DtkApp app = new(api.app_key, api.app_secret);
-            string sign = app.MakeSign(out nonce, out timer);
-            string sign_url = $"appKey={api.app_key}&nonce={nonce}&signRan={sign}&timer={timer}";
-
-            return new APIResult(new
-            {
-                success = true,
-                message = "ok",
-                sign_url
-            });
-        }
-
-        [HttpPost]
-        public ActionResult ddsign([FromBody] JsonElement form)
-        {
-
-            var api = PddPoolCore.GetOne(PddUnionWorkMode.SiteApi);
-            if (api == null)
-            {
-                return new APIResult(new
-                {
-                    success = false,
-                    message = "没有可用签名账户"
-                });
-            }
-
-            string ts = DateTime.Now.Convert2UnixTimestamp().ToString();
-            string url = $"https://gw-api.pinduoduo.com/api/router";
-
-            Dictionary<string, string> args = new Dictionary<string, string>
-            {
-                { "client_id", api.app_key },
-                { "timestamp", ts }
-            };
-
-            // 提取客户端的业务参数并排除 "client_id" 和 "timestamp"
-            foreach (JsonProperty prop in form.EnumerateObject())
-            {
-                if (prop.Name != "client_id" && prop.Name != "timestamp")
-                {
-                    args.Add(prop.Name, prop.Value.GetString());
-                }
-            }
-
-
-            ////其他业务参数接收客户端的form,记得排除client_id 和 timestamp
-            //args.Add("type", "pdd.ddk.goods.recommend.get");
-            //args.Add("data_type", "JSON");
-            //args.Add("custom_parameters", "5");
-
-
-            args = PddUnionPlus.GenerateSignature(api, args);
-            string queryString = string.Join("&", args.Select(kv => $"{HttpUtility.UrlEncode(kv.Key)}={HttpUtility.UrlEncode(kv.Value)}"));
-            string sign_url = $"{url}?{queryString}";
-            return new APIResult(new
-            {
-                success = true,
-                message = "ok",
-                sign_url
-            });
-
-            //WebClientUtility client = new WebClientUtility();
-            //var response = client.Request(url);
-            //var body = response.Body();
-            //return Content(body);
-        }
-        [HttpPost]
-        public ActionResult jdsign([FromBody] JsonElement form)
-        {
-            var accountid = form.Read<int>("aid", 4);
-            var method = form.Read<string>("method", string.Empty);
-            var version = form.Read<string>("v", string.Empty);
-            var param_json = form.Read<string>("param_json", string.Empty);
-
-            var api = new DBContext.Table("jd_pool").Get<JdPoolDTO>("id=@id", new { id = accountid });
-
-            string url = "https://api.jd.com/routerjson";
-            string ts = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
-            Dictionary<string, string> args = new()
-            {
-                { "access_token", ""},
-                { "app_key", api.app_key },
-                { "method", method },
-                { "v", version },
-                { "360buy_param_json", param_json },
-                { "timestamp", ts }
-            };
-
-            var plus = new JdUnionPlus(api);
-            string sign = plus._sign(method, version, param_json, ts);
-            args.Add("sign", sign);
-
-            string queryString = string.Join("&", args.Select(kv => $"{HttpUtility.UrlEncode(kv.Key)}={HttpUtility.UrlEncode(kv.Value)}"));
-            string sign_url = $"{url}?{queryString}";
-            return new APIResult(new
-            {
-                success = true,
-                message = "ok",
-                sign_url
-            });
-        }
-
-        [HttpPost]
-        public ActionResult jdrequest([FromBody] JsonElement form)
-        {
-
-            var accountid = form.Read<int>("aid", 4);
-            var method = form.Read<string>("method", string.Empty);
-            var version = form.Read<string>("v", string.Empty);
-            var param_json = form.Read<string>("param_json", string.Empty);
-
-            var api = new DBContext.Table("jd_pool").Get<JdPoolDTO>("id=@id", new { id = accountid });
-
-            if (api == null)
-            {
-                return new APIResult(new
-                {
-                    success = false,
-                    message = "没有可用签名账户"
-                });
-            }
-
-            string url = "https://api.jd.com/routerjson";
-            string ts = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
-            Dictionary<string, string> args = new()
-            {
-                { "access_token", ""},
-                { "app_key", api.app_key },
-                { "method", method },
-                { "v", version },
-                { "360buy_param_json", param_json },
-                { "timestamp", ts }
-            };
-
-            var plus = new JdUnionPlus(api);
-
-            var result = plus.Request(method, version, param_json);
-            return Content(result, "application/json");
-        }
-
-    }
+using molilian.core;
+using dodohold.core;
+using Microsoft.AspNetCore.Mvc;
+using Org.BouncyCastle.Ocsp;
+using System.Text.Json;
+using System.Runtime.InteropServices;
+using System.Net;
+using System.Security.Cryptography;
+using Microsoft.AspNetCore.Authentication;
+using TencentCloud.Ecm.V20190719.Models;
+using Dataoke;
+using System.Web;
+
+namespace molilian.api.Controllers
+{
+    [ApiController]
+    [Route("[controller]/[action]")]
+    public class H5Controller : ControllerBase
+    {
+
+        protected IHttpContextAccessor _accessor;
+        public H5Controller(IHttpContextAccessor accessor)
+        {
+            _accessor = accessor;
+        }
+
+
+        [HttpGet]
+        public ActionResult sign()
+        {
+            string referer = _accessor.HttpContext.Request.Referer();
+
+            var api = DataokeCore.GetOne(referer);
+            if (api == null)
+            {
+                return new APIResult(new
+                {
+                    success = false,
+                    message = "没有可用签名账户"
+                });
+            }
+
+            int nonce;
+            string timer;
+            DtkApp app = new(api.app_key, api.app_secret);
+            string sign = app.MakeSign(out nonce, out timer);
+            string sign_url = $"appKey={api.app_key}&nonce={nonce}&signRan={sign}&timer={timer}";
+
+            return new APIResult(new
+            {
+                success = true,
+                message = "ok",
+                sign_url
+            });
+        }
+
+        [HttpPost]
+        public ActionResult ddsign([FromBody] JsonElement form)
+        {
+
+            var api = PddPoolCore.GetOne(PddUnionWorkMode.SiteApi);
+            if (api == null)
+            {
+                return new APIResult(new
+                {
+                    success = false,
+                    message = "没有可用签名账户"
+                });
+            }
+
+            string ts = DateTime.Now.Convert2UnixTimestamp().ToString();
+            string url = $"https://gw-api.pinduoduo.com/api/router";
+
+            Dictionary<string, string> args = new Dictionary<string, string>
+            {
+                { "client_id", api.app_key },
+                { "timestamp", ts }
+            };
+
+            // 提取客户端的业务参数并排除 "client_id" 和 "timestamp"
+            foreach (JsonProperty prop in form.EnumerateObject())
+            {
+                if (prop.Name != "client_id" && prop.Name != "timestamp")
+                {
+                    args.Add(prop.Name, prop.Value.GetString());
+                }
+            }
+
+
+            ////其他业务参数接收客户端的form,记得排除client_id 和 timestamp
+            //args.Add("type", "pdd.ddk.goods.recommend.get");
+            //args.Add("data_type", "JSON");
+            //args.Add("custom_parameters", "5");
+
+
+            args = PddUnionPlus.GenerateSignature(api, args);
+            string queryString = string.Join("&", args.Select(kv => $"{HttpUtility.UrlEncode(kv.Key)}={HttpUtility.UrlEncode(kv.Value)}"));
+            string sign_url = $"{url}?{queryString}";
+            return new APIResult(new
+            {
+                success = true,
+                message = "ok",
+                sign_url
+            });
+
+            //WebClientUtility client = new WebClientUtility();
+            //var response = client.Request(url);
+            //var body = response.Body();
+            //return Content(body);
+        }
+        [HttpPost]
+        public ActionResult jdsign([FromBody] JsonElement form)
+        {
+            var accountid = form.Read<int>("aid", 4);
+            var method = form.Read<string>("method", string.Empty);
+            var version = form.Read<string>("v", string.Empty);
+            var param_json = form.Read<string>("param_json", string.Empty);
+
+            var api = new DBContext.Table("jd_pool").Get<JdPoolDTO>("id=@id", new { id = accountid });
+
+            string url = "https://api.jd.com/routerjson";
+            string ts = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
+            Dictionary<string, string> args = new()
+            {
+                { "access_token", ""},
+                { "app_key", api.app_key },
+                { "method", method },
+                { "v", version },
+                { "360buy_param_json", param_json },
+                { "timestamp", ts }
+            };
+
+            var plus = new JdUnionPlus(api);
+            string sign = plus._sign(method, version, param_json, ts);
+            args.Add("sign", sign);
+
+            string queryString = string.Join("&", args.Select(kv => $"{HttpUtility.UrlEncode(kv.Key)}={HttpUtility.UrlEncode(kv.Value)}"));
+            string sign_url = $"{url}?{queryString}";
+            return new APIResult(new
+            {
+                success = true,
+                message = "ok",
+                sign_url
+            });
+        }
+
+        [HttpPost]
+        public ActionResult jdrequest([FromBody] JsonElement form)
+        {
+
+            var accountid = form.Read<int>("aid", 4);
+            var method = form.Read<string>("method", string.Empty);
+            var version = form.Read<string>("v", string.Empty);
+            var param_json = form.Read<string>("param_json", string.Empty);
+
+            var api = new DBContext.Table("jd_pool").Get<JdPoolDTO>("id=@id", new { id = accountid });
+
+            if (api == null)
+            {
+                return new APIResult(new
+                {
+                    success = false,
+                    message = "没有可用签名账户"
+                });
+            }
+
+            string url = "https://api.jd.com/routerjson";
+            string ts = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
+            Dictionary<string, string> args = new()
+            {
+                { "access_token", ""},
+                { "app_key", api.app_key },
+                { "method", method },
+                { "v", version },
+                { "360buy_param_json", param_json },
+                { "timestamp", ts }
+            };
+
+            var plus = new JdUnionPlus(api);
+
+            var result = plus.Request(method, version, param_json);
+            return Content(result, "application/json");
+        }
+
+    }
 }

+ 122 - 122
molilian.api/Controllers/public/JDTaskController.cs

@@ -1,123 +1,123 @@
-using molilian.core;
-using dodohold.core;
-using Microsoft.AspNetCore.Mvc;
-using Org.BouncyCastle.Ocsp;
-using System.Text.Json;
-using System.Runtime.InteropServices;
-using System.Net;
-using System.Threading.Channels;
-using TencentCloud.Cdwch.V20200915.Models;
-using COSXML.Network;
-using System.Security.Cryptography;
-using System.Threading;
-using TencentCloud.Batch.V20170312.Models;
-
-namespace molilian.api.Controllers
-{
-    [ApiController]
-    [Route("[controller]_70160bd632/[action]")]
-    public class JDTaskController : ControllerBase
-    {
-
-        protected IHttpContextAccessor _accessor;
-        public JDTaskController(IHttpContextAccessor accessor)
-        {
-            _accessor = accessor;
-        }
-
-
-        [HttpGet]
-        public async Task<ActionResult> GetHistoryOrders(int id, int sleep, int h = 0)
-        {
-            var list = await JdPoolCore.ListAsync();
-            if (list == null) return new APIResult(new { success = false, message = "没有有效账号", });
-
-            string message = string.Empty;
-            int total = 0;
-            foreach (var account in list)
-            {
-                if (id != 0 && id != account.id) continue;
-                 try
-                {
-                    DateTime endTime = DateTime.Now.Date;
-                    DateTime startTime = endTime.AddDays(-90);
-
-                    var plus = new JdUnionPlus(account);
-
-                    //for (int i = 89; i >= 0; i--)
-                    //{
-                    //    DateTime dayStartTime = startTime.AddDays(i);
-                    //    DateTime dayEndTime = dayStartTime.AddDays(1);
-                    //    total += await plus.GetHistoryOrders(sleep, dayStartTime, dayEndTime);
-                    //}
-
-                    if (h == 1)
-                    {
-                        for (int i = 24 * 90 - 1; i >= 0; i--)
-                        {
-                            DateTime hourStartTime = startTime.AddHours(i);
-                            DateTime hourEndTime = hourStartTime.AddHours(1);
-                            total += await plus.GetHistoryOrders(sleep, hourStartTime, hourEndTime);
-                        }
-                    }
-                    else
-                    {
-                        for (int i = 89; i >= 0; i--)
-                        {
-                            DateTime dayStartTime = startTime.AddDays(i);
-                            DateTime dayEndTime = dayStartTime.AddDays(1);
-                            total += await plus.GetHistoryOrders(sleep, dayStartTime, dayEndTime);
-                        }
-                    }
-
-                    //total += await plus.GetHistoryOrders(sleep, startTime, endTime);
-                }
-                catch (Exception ex)
-                {
-                    message = $"【JD历史订单接口异常】[{account.id}]{account.company}\n{ex.Message}\n{ex.StackTrace}";
-                    NotifyCore.Notify(new NifyMessage
-                    {
-                        message = message,
-                        priority = NifyMessagePriority.high,
-                        tags = ["red_circle"]
-                    });
-                    continue;
-                }
-            }
-            return new APIResult(new { success = true, message = "ok", total });
-        }
-
-        [HttpGet]
-        public async Task<ActionResult> GetIncyOrders(int sleep = 2000)
-        {
-            var list = await JdPoolCore.ListAsync();
-            if (list == null) return new APIResult(new { success = false, message = "没有有效账号", });
-            string message = string.Empty;
-            int total = 0;
-            foreach (var account in list)
-            {
-                if (!account.enable_sync_order) continue;
-
-                try
-                {
-                    var plus = new JdUnionPlus(account);
-                    total += await plus.GetIncyOrders(sleep);
-                }
-                catch (Exception ex)
-                {
-                    message = $"【JD新增订单接口异常】[{account.id}]{account.company}\n{ex.Message}\n{ex.StackTrace}";
-                    NotifyCore.Notify(new NifyMessage
-                    {
-                        message = message,
-                        priority = NifyMessagePriority.high,
-                        tags = ["red_circle"]
-                    });
-                    continue;
-                }
-            }
-            return new APIResult(new { success = true, message = "ok", total });
-        }
-
-
-    }
+using molilian.core;
+using dodohold.core;
+using Microsoft.AspNetCore.Mvc;
+using Org.BouncyCastle.Ocsp;
+using System.Text.Json;
+using System.Runtime.InteropServices;
+using System.Net;
+using System.Threading.Channels;
+using TencentCloud.Cdwch.V20200915.Models;
+using COSXML.Network;
+using System.Security.Cryptography;
+using System.Threading;
+using TencentCloud.Batch.V20170312.Models;
+
+namespace molilian.api.Controllers
+{
+    [ApiController]
+    [Route("[controller]_70160bd632/[action]")]
+    public class JDTaskController : ControllerBase
+    {
+
+        protected IHttpContextAccessor _accessor;
+        public JDTaskController(IHttpContextAccessor accessor)
+        {
+            _accessor = accessor;
+        }
+
+
+        [HttpGet]
+        public async Task<ActionResult> GetHistoryOrders(int id, int sleep, int h = 0)
+        {
+            var list = await JdPoolCore.ListAsync();
+            if (list == null) return new APIResult(new { success = false, message = "没有有效账号", });
+
+            string message = string.Empty;
+            int total = 0;
+            foreach (var account in list)
+            {
+                if (id != 0 && id != account.id) continue;
+                 try
+                {
+                    DateTime endTime = DateTime.Now.Date;
+                    DateTime startTime = endTime.AddDays(-90);
+
+                    var plus = new JdUnionPlus(account);
+
+                    //for (int i = 89; i >= 0; i--)
+                    //{
+                    //    DateTime dayStartTime = startTime.AddDays(i);
+                    //    DateTime dayEndTime = dayStartTime.AddDays(1);
+                    //    total += await plus.GetHistoryOrders(sleep, dayStartTime, dayEndTime);
+                    //}
+
+                    if (h == 1)
+                    {
+                        for (int i = 24 * 90 - 1; i >= 0; i--)
+                        {
+                            DateTime hourStartTime = startTime.AddHours(i);
+                            DateTime hourEndTime = hourStartTime.AddHours(1);
+                            total += await plus.GetHistoryOrders(sleep, hourStartTime, hourEndTime);
+                        }
+                    }
+                    else
+                    {
+                        for (int i = 89; i >= 0; i--)
+                        {
+                            DateTime dayStartTime = startTime.AddDays(i);
+                            DateTime dayEndTime = dayStartTime.AddDays(1);
+                            total += await plus.GetHistoryOrders(sleep, dayStartTime, dayEndTime);
+                        }
+                    }
+
+                    //total += await plus.GetHistoryOrders(sleep, startTime, endTime);
+                }
+                catch (Exception ex)
+                {
+                    message = $"【JD历史订单接口异常】[{account.id}]{account.company}\n{ex.Message}\n{ex.StackTrace}";
+                    NotifyCore.Notify(new NifyMessage
+                    {
+                        message = message,
+                        priority = NifyMessagePriority.high,
+                        tags = ["red_circle"]
+                    });
+                    continue;
+                }
+            }
+            return new APIResult(new { success = true, message = "ok", total });
+        }
+
+        [HttpGet]
+        public async Task<ActionResult> GetIncyOrders(int sleep = 2000)
+        {
+            var list = await JdPoolCore.ListAsync();
+            if (list == null) return new APIResult(new { success = false, message = "没有有效账号", });
+            string message = string.Empty;
+            int total = 0;
+            foreach (var account in list)
+            {
+                if (!account.enable_sync_order) continue;
+
+                try
+                {
+                    var plus = new JdUnionPlus(account);
+                    total += await plus.GetIncyOrders(sleep);
+                }
+                catch (Exception ex)
+                {
+                    message = $"【JD新增订单接口异常】[{account.id}]{account.company}\n{ex.Message}\n{ex.StackTrace}";
+                    NotifyCore.Notify(new NifyMessage
+                    {
+                        message = message,
+                        priority = NifyMessagePriority.high,
+                        tags = ["red_circle"]
+                    });
+                    continue;
+                }
+            }
+            return new APIResult(new { success = true, message = "ok", total });
+        }
+
+
+    }
 }

+ 444 - 444
molilian.api/Controllers/public/StressTestController.cs

@@ -1,444 +1,444 @@
-using CSRedis;
-using dodohold.core;
-using ICSharpCode.SharpZipLib.Zip;
-using Microsoft.AspNetCore.Mvc;
-using System.Diagnostics;
-using System.Text;
-using System.Text.Json.Serialization;
-
-namespace molilian.api.Controllers
-{
-    [ApiController]
-    [Route("api/[controller]")]
-    public class StressTestController : ControllerBase
-    {
-        private static readonly StressTestService _stressTestService = new();
-
-
-        [HttpPost("start")]
-        public IActionResult StartTest([FromBody] StressTestConfig config)
-        {
-            _stressTestService.StartTest(config);
-            return Ok(new { message = "压力测试已启动" });
-        }
-
-        [HttpPost("stop")]
-        public IActionResult StopTest()
-        {
-            _stressTestService.StopTest();
-            return Ok(new { message = "压力测试已停止" });
-        }
-
-        [HttpGet("status")]
-        public IActionResult GetStatus()
-        {
-            return Ok(_stressTestService.GetStatus());
-        }
-
-        // StressTestController.cs 中 GetMetrics 方法的修改
-        [HttpGet("metrics")]
-        public IActionResult GetMetrics()
-        {
-            var status = _stressTestService.GetStatus();
-            return Ok(new
-            {
-                每秒总操作数 = status.LastSecondOperations,
-                每秒读操作数 = status.LastSecondReads,
-                每秒写操作数 = status.LastSecondWrites,
-                数据吞吐量 = status.FormattedThroughput,
-                平均响应时间 = status.AverageResponseTime
-            });
-        }
-
-
-    }
-
-    // StressTestService.cs
-    public class StressTestService
-    {
-        private CancellationTokenSource _cancellationTokenSource;
-        private TestStatus _status = new TestStatus();
-        private readonly object _lockObject = new object();
-        private volatile int _currentConcurrentTasks;  // 当前并发数
-        private List<Task> _runningTasks;             // 正在运行的任务列表
-
-        // 计数器字段
-        private int _timeoutCount;
-        private int _errorCount;
-        private int _completedRequests;
-        private long _totalResponseTime;
-
-        private int _readCount;  // 添加读操作计数
-        private int _writeCount; // 添加写操作计数
-
-        private string _testValue; // 缓存生成的测试数据
-        private readonly Random _random = new Random();
-
-
-        private int _lastSecondReads;
-        private int _lastSecondWrites;
-        private DateTime _lastCounterReset = DateTime.Now;
-
-        private volatile StressTestConfig _currentConfig;  // 添加当前配置字段
-
-
-        private async Task ManageTasks(CancellationToken cancellationToken)
-        {
-            _runningTasks = new List<Task>();
-
-            while (!cancellationToken.IsCancellationRequested)
-            {
-                try
-                {
-                    // 移除已完成的任务
-                    _runningTasks.RemoveAll(t => t.IsCompleted || t.IsFaulted || t.IsCanceled);
-
-                    // 获取当前配置的目标并发数
-                    int targetConcurrency = _currentConfig.ConcurrentTasks;
-
-                    // 增加任务
-                    while (_runningTasks.Count < targetConcurrency)
-                    {
-                        var taskId = _runningTasks.Count;
-                        var task = RunSingleTask(taskId, cancellationToken);
-                        _runningTasks.Add(task);
-                        Console.WriteLine($"添加新任务,当前任务数:{_runningTasks.Count}");
-                    }
-
-                    // 减少任务
-                    while (_runningTasks.Count > targetConcurrency)
-                    {
-                        int removeCount = _runningTasks.Count - targetConcurrency;
-                        Console.WriteLine($"移除{removeCount}个任务");
-
-                        // 移除多余的任务
-                        _runningTasks.RemoveRange(targetConcurrency, removeCount);
-                    }
-
-                    await Task.Delay(1000, cancellationToken); // 每秒检查一次任务状态
-                }
-                catch (OperationCanceledException)
-                {
-                    // 正常的取消操作
-                    break;
-                }
-                catch (Exception ex)
-                {
-                    Console.WriteLine($"任务管理异常:{ex.Message}");
-                    // 继续运行,不要因为单次异常而停止整个测试
-                }
-            }
-        }
-
-        public void StartTest(StressTestConfig config)
-        {
-            lock (_lockObject)
-            {
-                // 更新配置
-                _currentConfig = config;
-
-                if (_status.IsRunning)
-                {
-                    Console.WriteLine($"更新配置 - 并发数: {config.ConcurrentTasks}, 读写比: {config.ReadsPerWrite}, 数据大小: {config.ValueSizeKB}KB");
-                    _status.CurrentConfig = config;
-                    return;
-                }
-
-                // 首次启动
-                Console.WriteLine($"启动测试 - 并发数: {config.ConcurrentTasks}, 读写比: {config.ReadsPerWrite}, 数据大小: {config.ValueSizeKB}KB");
-
-                _timeoutCount = 0;
-                _errorCount = 0;
-                _completedRequests = 0;
-                _totalResponseTime = 0;
-                _readCount = 0;
-                _writeCount = 0;
-                _totalDataSize = 0;
-
-                _cancellationTokenSource = new CancellationTokenSource();
-                _status = new TestStatus
-                {
-                    IsRunning = true,
-                    StartTime = DateTime.Now,
-                    CurrentConfig = config,
-                    EndTime = DateTime.Now.AddHours(1)
-                };
-
-                // 启动任务管理器
-                Task.Run(() => ManageTasks(_cancellationTokenSource.Token));
-            }
-        }
-
-
-
-        private async Task RunSingleTask(int taskId, CancellationToken cancellationToken)
-        {
-            var endTime = DateTime.Now.AddHours(1);
-            int requestId = 0;
-
-            while (DateTime.Now < endTime && !cancellationToken.IsCancellationRequested)
-            {
-                try
-                {
-                    // 获取最新配置
-                    var config = _currentConfig;
-                    var testValue = GenerateTestValue(config.ValueSizeKB);
-                    var key = $"test:key:{taskId}:{requestId++}";
-
-
-                    // 构造包含大小信息的值
-                    var value = new
-                    {
-                        id = $"{taskId}_{requestId}",
-                        timestamp = DateTime.Now.Ticks,
-                        data = testValue
-                    };
-
-                    // 写入操作
-                    try
-                    {
-                        var requestSw = Stopwatch.StartNew();
-                        var setTask = RedisHelper.SetAsync(key, value, 3600);
-                        var timeoutTask = Task.Delay(config.TimeoutMs);
-
-                        var completedTask = await Task.WhenAny(setTask, timeoutTask);
-                        if (completedTask == timeoutTask)
-                        {
-                            Interlocked.Increment(ref _timeoutCount);
-                            continue;
-                        }
-
-                        requestSw.Stop();
-                        Interlocked.Increment(ref _writeCount);
-                        Interlocked.Increment(ref _lastSecondWrites);
-                        Interlocked.Add(ref _totalResponseTime, requestSw.ElapsedMilliseconds);
-                        Interlocked.Add(ref _totalDataSize, config.ValueSizeKB * 1024);
-                        UpdateStatus();
-                    }
-                    catch (Exception)
-                    {
-                        Interlocked.Increment(ref _errorCount);
-                        continue;
-                    }
-
-                    // 执行多次读取操作,使用最新的配置
-                    for (int i = 0; i < config.ReadsPerWrite && !cancellationToken.IsCancellationRequested; i++)
-                    {
-                        try
-                        {
-                            var requestSw = Stopwatch.StartNew();
-                            var getTask = Task.Run(() =>
-                            {
-                                try
-                                {
-                                    RedisHelper.Get<object>(key);
-                                }
-                                catch (Exception)
-                                {
-                                    Interlocked.Increment(ref _errorCount);
-                                }
-                            });
-                            var timeoutTask = Task.Delay(config.TimeoutMs);
-
-                            var completedTask = await Task.WhenAny(getTask, timeoutTask);
-                            if (completedTask == timeoutTask)
-                            {
-                                Interlocked.Increment(ref _timeoutCount);
-                                continue;
-                            }
-
-                            requestSw.Stop();
-                            Interlocked.Increment(ref _readCount);
-                            Interlocked.Increment(ref _lastSecondReads);
-                            Interlocked.Add(ref _totalResponseTime, requestSw.ElapsedMilliseconds);
-                            Interlocked.Add(ref _totalDataSize, config.ValueSizeKB * 1024);
-                            UpdateStatus();
-
-                            if (config.OperationIntervalMs > 0)
-                            {
-                                await Task.Delay(config.OperationIntervalMs, cancellationToken);
-                            }
-                        }
-                        catch (Exception)
-                        {
-                            Interlocked.Increment(ref _errorCount);
-                        }
-                    }
-                }
-                catch (Exception ex)
-                {
-                    Console.WriteLine($"任务 {taskId} 异常:{ex.Message}");
-                    Interlocked.Increment(ref _errorCount);
-
-                    // 添加短暂延迟,避免出错时立即重试
-                    await Task.Delay(100, cancellationToken);
-                }
-
-            }
-        }
-
-
-        // 生成指定大小的随机字符串
-        private string GenerateTestValue(int sizeKB)
-        {
-            if (_testValue != null && _testValue.Length == sizeKB * 1024) // 如果已有相同大小的数据,直接返回
-                return _testValue;
-
-            const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
-            var stringBuilder = new StringBuilder(sizeKB * 1024);
-
-            for (int i = 0; i < sizeKB * 1024; i++)
-            {
-                stringBuilder.Append(chars[_random.Next(chars.Length)]);
-            }
-
-            _testValue = stringBuilder.ToString();
-            return _testValue;
-        }
-
-
-        private long _totalDataSize; // 添加数据大小统计
-
-        private void UpdateStatus()
-        {
-            lock (_lockObject)
-            {
-                var now = DateTime.Now;
-                if ((now - _lastCounterReset).TotalSeconds >= 1)
-                {
-                    _status.LastSecondReads = _lastSecondReads;
-                    _status.LastSecondWrites = _lastSecondWrites;
-                    _status.LastSecondOperations = _lastSecondReads + _lastSecondWrites;
-
-                    _lastSecondReads = 0;
-                    _lastSecondWrites = 0;
-                    _lastCounterReset = now;
-                }
-
-                _status.TimeoutCount = _timeoutCount;
-                _status.ErrorCount = _errorCount;
-                _status.CompletedRequests = _completedRequests;
-                _status.ReadCount = _readCount;
-                _status.WriteCount = _writeCount;
-                _status.AverageResponseTime = (double)_totalResponseTime / (_readCount + _writeCount);
-                _status.TotalDataSize = _totalDataSize;
-                _status.DataThroughput = _status.TotalDataSize /
-                    (DateTime.Now - _status.StartTime.Value).TotalSeconds;
-            }
-        }
-        public void StopTest()
-        {
-            lock (_lockObject)
-            {
-                if (_status.IsRunning)
-                {
-                    _cancellationTokenSource?.Cancel();
-                    _status.IsRunning = false;
-                }
-            }
-        }
-
-        public TestStatus GetStatus()
-        {
-            return _status;
-        }
-    }
-    // TestStatus.cs 的修改部分
-    public class TestStatus
-    {
-        [JsonPropertyName("运行状态")]
-        public bool IsRunning { get; set; }
-
-        [JsonPropertyName("计划总请求数")]
-        public int TotalRequests { get; set; }
-
-        [JsonPropertyName("已完成请求数")]
-        public int CompletedRequests { get; set; }
-
-        [JsonPropertyName("超时次数")]
-        public int TimeoutCount { get; set; }
-
-        [JsonPropertyName("错误次数")]
-        public int ErrorCount { get; set; }
-
-        [JsonPropertyName("平均响应时间(ms)")]
-        public double AverageResponseTime { get; set; }
-
-        [JsonPropertyName("开始时间")]
-        public DateTime? StartTime { get; set; }
-
-        [JsonPropertyName("当前配置")]
-        public StressTestConfig CurrentConfig { get; set; }
-
-        [JsonPropertyName("进度百分比")]
-        public double ProgressPercentage => TotalRequests == 0 ? 0 : (CompletedRequests * 100.0 / TotalRequests);
-
-        [JsonPropertyName("读操作总数")]
-        public int ReadCount { get; set; }
-
-        [JsonPropertyName("写操作总数")]
-        public int WriteCount { get; set; }
-
-        [JsonPropertyName("读写比例")]
-        public double ReadWriteRatio => WriteCount == 0 ? 0 : (double)ReadCount / WriteCount;
-
-        [JsonPropertyName("每秒平均操作数")]
-        public double OperationsPerSecond => StartTime.HasValue
-            ? (ReadCount + WriteCount) / (DateTime.Now - StartTime.Value).TotalSeconds
-            : 0;
-
-        [JsonPropertyName("结束时间")]
-        public DateTime? EndTime { get; set; }
-
-        [JsonPropertyName("剩余时间(分钟)")]
-        public double RemainingMinutes => EndTime.HasValue ?
-            Math.Max(0, (EndTime.Value - DateTime.Now).TotalMinutes) : 0;
-
-        [JsonPropertyName("总数据量(字节)")]
-        public long TotalDataSize { get; set; }
-
-        [JsonPropertyName("数据吞吐量(字节/秒)")]
-        public double DataThroughput { get; set; }
-
-        [JsonPropertyName("数据吞吐量")]
-        public string FormattedThroughput
-        {
-            get
-            {
-                if (DataThroughput < 1024) return $"{DataThroughput:F2} B/s";
-                if (DataThroughput < 1024 * 1024) return $"{DataThroughput / 1024:F2} KB/s";
-                if (DataThroughput < 1024 * 1024 * 1024) return $"{DataThroughput / (1024 * 1024):F2} MB/s";
-                return $"{DataThroughput / (1024 * 1024 * 1024):F2} GB/s";
-            }
-        }
-
-        [JsonPropertyName("最近每秒操作数")]
-        public int LastSecondOperations { get; set; }
-
-        [JsonPropertyName("最近每秒读操作数")]
-        public int LastSecondReads { get; set; }
-
-        [JsonPropertyName("最近每秒写操作数")]
-        public int LastSecondWrites { get; set; }
-    }
-
-    // StressTestConfig.cs 的修改部分
-    public class StressTestConfig
-    {
-        [JsonPropertyName("并发任务数")]
-        public int ConcurrentTasks { get; set; } = 100;
-
-        [JsonPropertyName("超时时间(ms)")]
-        public int TimeoutMs { get; set; } = 1000;
-
-        [JsonPropertyName("读写比例")]
-        public int ReadsPerWrite { get; set; } = 10;
-
-        [JsonPropertyName("操作间隔(ms)")]
-        public int OperationIntervalMs { get; set; } = 0;
-
-        [JsonPropertyName("数据大小(KB)")]
-        public int ValueSizeKB { get; set; } = 10;
-    }
-
-}
+using CSRedis;
+using dodohold.core;
+using ICSharpCode.SharpZipLib.Zip;
+using Microsoft.AspNetCore.Mvc;
+using System.Diagnostics;
+using System.Text;
+using System.Text.Json.Serialization;
+
+namespace molilian.api.Controllers
+{
+    [ApiController]
+    [Route("api/[controller]")]
+    public class StressTestController : ControllerBase
+    {
+        private static readonly StressTestService _stressTestService = new();
+
+
+        [HttpPost("start")]
+        public IActionResult StartTest([FromBody] StressTestConfig config)
+        {
+            _stressTestService.StartTest(config);
+            return Ok(new { message = "压力测试已启动" });
+        }
+
+        [HttpPost("stop")]
+        public IActionResult StopTest()
+        {
+            _stressTestService.StopTest();
+            return Ok(new { message = "压力测试已停止" });
+        }
+
+        [HttpGet("status")]
+        public IActionResult GetStatus()
+        {
+            return Ok(_stressTestService.GetStatus());
+        }
+
+        // StressTestController.cs 中 GetMetrics 方法的修改
+        [HttpGet("metrics")]
+        public IActionResult GetMetrics()
+        {
+            var status = _stressTestService.GetStatus();
+            return Ok(new
+            {
+                每秒总操作数 = status.LastSecondOperations,
+                每秒读操作数 = status.LastSecondReads,
+                每秒写操作数 = status.LastSecondWrites,
+                数据吞吐量 = status.FormattedThroughput,
+                平均响应时间 = status.AverageResponseTime
+            });
+        }
+
+
+    }
+
+    // StressTestService.cs
+    public class StressTestService
+    {
+        private CancellationTokenSource _cancellationTokenSource;
+        private TestStatus _status = new TestStatus();
+        private readonly object _lockObject = new object();
+        private volatile int _currentConcurrentTasks;  // 当前并发数
+        private List<Task> _runningTasks;             // 正在运行的任务列表
+
+        // 计数器字段
+        private int _timeoutCount;
+        private int _errorCount;
+        private int _completedRequests;
+        private long _totalResponseTime;
+
+        private int _readCount;  // 添加读操作计数
+        private int _writeCount; // 添加写操作计数
+
+        private string _testValue; // 缓存生成的测试数据
+        private readonly Random _random = new Random();
+
+
+        private int _lastSecondReads;
+        private int _lastSecondWrites;
+        private DateTime _lastCounterReset = DateTime.Now;
+
+        private volatile StressTestConfig _currentConfig;  // 添加当前配置字段
+
+
+        private async Task ManageTasks(CancellationToken cancellationToken)
+        {
+            _runningTasks = new List<Task>();
+
+            while (!cancellationToken.IsCancellationRequested)
+            {
+                try
+                {
+                    // 移除已完成的任务
+                    _runningTasks.RemoveAll(t => t.IsCompleted || t.IsFaulted || t.IsCanceled);
+
+                    // 获取当前配置的目标并发数
+                    int targetConcurrency = _currentConfig.ConcurrentTasks;
+
+                    // 增加任务
+                    while (_runningTasks.Count < targetConcurrency)
+                    {
+                        var taskId = _runningTasks.Count;
+                        var task = RunSingleTask(taskId, cancellationToken);
+                        _runningTasks.Add(task);
+                        Console.WriteLine($"添加新任务,当前任务数:{_runningTasks.Count}");
+                    }
+
+                    // 减少任务
+                    while (_runningTasks.Count > targetConcurrency)
+                    {
+                        int removeCount = _runningTasks.Count - targetConcurrency;
+                        Console.WriteLine($"移除{removeCount}个任务");
+
+                        // 移除多余的任务
+                        _runningTasks.RemoveRange(targetConcurrency, removeCount);
+                    }
+
+                    await Task.Delay(1000, cancellationToken); // 每秒检查一次任务状态
+                }
+                catch (OperationCanceledException)
+                {
+                    // 正常的取消操作
+                    break;
+                }
+                catch (Exception ex)
+                {
+                    Console.WriteLine($"任务管理异常:{ex.Message}");
+                    // 继续运行,不要因为单次异常而停止整个测试
+                }
+            }
+        }
+
+        public void StartTest(StressTestConfig config)
+        {
+            lock (_lockObject)
+            {
+                // 更新配置
+                _currentConfig = config;
+
+                if (_status.IsRunning)
+                {
+                    Console.WriteLine($"更新配置 - 并发数: {config.ConcurrentTasks}, 读写比: {config.ReadsPerWrite}, 数据大小: {config.ValueSizeKB}KB");
+                    _status.CurrentConfig = config;
+                    return;
+                }
+
+                // 首次启动
+                Console.WriteLine($"启动测试 - 并发数: {config.ConcurrentTasks}, 读写比: {config.ReadsPerWrite}, 数据大小: {config.ValueSizeKB}KB");
+
+                _timeoutCount = 0;
+                _errorCount = 0;
+                _completedRequests = 0;
+                _totalResponseTime = 0;
+                _readCount = 0;
+                _writeCount = 0;
+                _totalDataSize = 0;
+
+                _cancellationTokenSource = new CancellationTokenSource();
+                _status = new TestStatus
+                {
+                    IsRunning = true,
+                    StartTime = DateTime.Now,
+                    CurrentConfig = config,
+                    EndTime = DateTime.Now.AddHours(1)
+                };
+
+                // 启动任务管理器
+                Task.Run(() => ManageTasks(_cancellationTokenSource.Token));
+            }
+        }
+
+
+
+        private async Task RunSingleTask(int taskId, CancellationToken cancellationToken)
+        {
+            var endTime = DateTime.Now.AddHours(1);
+            int requestId = 0;
+
+            while (DateTime.Now < endTime && !cancellationToken.IsCancellationRequested)
+            {
+                try
+                {
+                    // 获取最新配置
+                    var config = _currentConfig;
+                    var testValue = GenerateTestValue(config.ValueSizeKB);
+                    var key = $"test:key:{taskId}:{requestId++}";
+
+
+                    // 构造包含大小信息的值
+                    var value = new
+                    {
+                        id = $"{taskId}_{requestId}",
+                        timestamp = DateTime.Now.Ticks,
+                        data = testValue
+                    };
+
+                    // 写入操作
+                    try
+                    {
+                        var requestSw = Stopwatch.StartNew();
+                        var setTask = RedisHelper.SetAsync(key, value, 3600);
+                        var timeoutTask = Task.Delay(config.TimeoutMs);
+
+                        var completedTask = await Task.WhenAny(setTask, timeoutTask);
+                        if (completedTask == timeoutTask)
+                        {
+                            Interlocked.Increment(ref _timeoutCount);
+                            continue;
+                        }
+
+                        requestSw.Stop();
+                        Interlocked.Increment(ref _writeCount);
+                        Interlocked.Increment(ref _lastSecondWrites);
+                        Interlocked.Add(ref _totalResponseTime, requestSw.ElapsedMilliseconds);
+                        Interlocked.Add(ref _totalDataSize, config.ValueSizeKB * 1024);
+                        UpdateStatus();
+                    }
+                    catch (Exception)
+                    {
+                        Interlocked.Increment(ref _errorCount);
+                        continue;
+                    }
+
+                    // 执行多次读取操作,使用最新的配置
+                    for (int i = 0; i < config.ReadsPerWrite && !cancellationToken.IsCancellationRequested; i++)
+                    {
+                        try
+                        {
+                            var requestSw = Stopwatch.StartNew();
+                            var getTask = Task.Run(() =>
+                            {
+                                try
+                                {
+                                    RedisHelper.Get<object>(key);
+                                }
+                                catch (Exception)
+                                {
+                                    Interlocked.Increment(ref _errorCount);
+                                }
+                            });
+                            var timeoutTask = Task.Delay(config.TimeoutMs);
+
+                            var completedTask = await Task.WhenAny(getTask, timeoutTask);
+                            if (completedTask == timeoutTask)
+                            {
+                                Interlocked.Increment(ref _timeoutCount);
+                                continue;
+                            }
+
+                            requestSw.Stop();
+                            Interlocked.Increment(ref _readCount);
+                            Interlocked.Increment(ref _lastSecondReads);
+                            Interlocked.Add(ref _totalResponseTime, requestSw.ElapsedMilliseconds);
+                            Interlocked.Add(ref _totalDataSize, config.ValueSizeKB * 1024);
+                            UpdateStatus();
+
+                            if (config.OperationIntervalMs > 0)
+                            {
+                                await Task.Delay(config.OperationIntervalMs, cancellationToken);
+                            }
+                        }
+                        catch (Exception)
+                        {
+                            Interlocked.Increment(ref _errorCount);
+                        }
+                    }
+                }
+                catch (Exception ex)
+                {
+                    Console.WriteLine($"任务 {taskId} 异常:{ex.Message}");
+                    Interlocked.Increment(ref _errorCount);
+
+                    // 添加短暂延迟,避免出错时立即重试
+                    await Task.Delay(100, cancellationToken);
+                }
+
+            }
+        }
+
+
+        // 生成指定大小的随机字符串
+        private string GenerateTestValue(int sizeKB)
+        {
+            if (_testValue != null && _testValue.Length == sizeKB * 1024) // 如果已有相同大小的数据,直接返回
+                return _testValue;
+
+            const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
+            var stringBuilder = new StringBuilder(sizeKB * 1024);
+
+            for (int i = 0; i < sizeKB * 1024; i++)
+            {
+                stringBuilder.Append(chars[_random.Next(chars.Length)]);
+            }
+
+            _testValue = stringBuilder.ToString();
+            return _testValue;
+        }
+
+
+        private long _totalDataSize; // 添加数据大小统计
+
+        private void UpdateStatus()
+        {
+            lock (_lockObject)
+            {
+                var now = DateTime.Now;
+                if ((now - _lastCounterReset).TotalSeconds >= 1)
+                {
+                    _status.LastSecondReads = _lastSecondReads;
+                    _status.LastSecondWrites = _lastSecondWrites;
+                    _status.LastSecondOperations = _lastSecondReads + _lastSecondWrites;
+
+                    _lastSecondReads = 0;
+                    _lastSecondWrites = 0;
+                    _lastCounterReset = now;
+                }
+
+                _status.TimeoutCount = _timeoutCount;
+                _status.ErrorCount = _errorCount;
+                _status.CompletedRequests = _completedRequests;
+                _status.ReadCount = _readCount;
+                _status.WriteCount = _writeCount;
+                _status.AverageResponseTime = (double)_totalResponseTime / (_readCount + _writeCount);
+                _status.TotalDataSize = _totalDataSize;
+                _status.DataThroughput = _status.TotalDataSize /
+                    (DateTime.Now - _status.StartTime.Value).TotalSeconds;
+            }
+        }
+        public void StopTest()
+        {
+            lock (_lockObject)
+            {
+                if (_status.IsRunning)
+                {
+                    _cancellationTokenSource?.Cancel();
+                    _status.IsRunning = false;
+                }
+            }
+        }
+
+        public TestStatus GetStatus()
+        {
+            return _status;
+        }
+    }
+    // TestStatus.cs 的修改部分
+    public class TestStatus
+    {
+        [JsonPropertyName("运行状态")]
+        public bool IsRunning { get; set; }
+
+        [JsonPropertyName("计划总请求数")]
+        public int TotalRequests { get; set; }
+
+        [JsonPropertyName("已完成请求数")]
+        public int CompletedRequests { get; set; }
+
+        [JsonPropertyName("超时次数")]
+        public int TimeoutCount { get; set; }
+
+        [JsonPropertyName("错误次数")]
+        public int ErrorCount { get; set; }
+
+        [JsonPropertyName("平均响应时间(ms)")]
+        public double AverageResponseTime { get; set; }
+
+        [JsonPropertyName("开始时间")]
+        public DateTime? StartTime { get; set; }
+
+        [JsonPropertyName("当前配置")]
+        public StressTestConfig CurrentConfig { get; set; }
+
+        [JsonPropertyName("进度百分比")]
+        public double ProgressPercentage => TotalRequests == 0 ? 0 : (CompletedRequests * 100.0 / TotalRequests);
+
+        [JsonPropertyName("读操作总数")]
+        public int ReadCount { get; set; }
+
+        [JsonPropertyName("写操作总数")]
+        public int WriteCount { get; set; }
+
+        [JsonPropertyName("读写比例")]
+        public double ReadWriteRatio => WriteCount == 0 ? 0 : (double)ReadCount / WriteCount;
+
+        [JsonPropertyName("每秒平均操作数")]
+        public double OperationsPerSecond => StartTime.HasValue
+            ? (ReadCount + WriteCount) / (DateTime.Now - StartTime.Value).TotalSeconds
+            : 0;
+
+        [JsonPropertyName("结束时间")]
+        public DateTime? EndTime { get; set; }
+
+        [JsonPropertyName("剩余时间(分钟)")]
+        public double RemainingMinutes => EndTime.HasValue ?
+            Math.Max(0, (EndTime.Value - DateTime.Now).TotalMinutes) : 0;
+
+        [JsonPropertyName("总数据量(字节)")]
+        public long TotalDataSize { get; set; }
+
+        [JsonPropertyName("数据吞吐量(字节/秒)")]
+        public double DataThroughput { get; set; }
+
+        [JsonPropertyName("数据吞吐量")]
+        public string FormattedThroughput
+        {
+            get
+            {
+                if (DataThroughput < 1024) return $"{DataThroughput:F2} B/s";
+                if (DataThroughput < 1024 * 1024) return $"{DataThroughput / 1024:F2} KB/s";
+                if (DataThroughput < 1024 * 1024 * 1024) return $"{DataThroughput / (1024 * 1024):F2} MB/s";
+                return $"{DataThroughput / (1024 * 1024 * 1024):F2} GB/s";
+            }
+        }
+
+        [JsonPropertyName("最近每秒操作数")]
+        public int LastSecondOperations { get; set; }
+
+        [JsonPropertyName("最近每秒读操作数")]
+        public int LastSecondReads { get; set; }
+
+        [JsonPropertyName("最近每秒写操作数")]
+        public int LastSecondWrites { get; set; }
+    }
+
+    // StressTestConfig.cs 的修改部分
+    public class StressTestConfig
+    {
+        [JsonPropertyName("并发任务数")]
+        public int ConcurrentTasks { get; set; } = 100;
+
+        [JsonPropertyName("超时时间(ms)")]
+        public int TimeoutMs { get; set; } = 1000;
+
+        [JsonPropertyName("读写比例")]
+        public int ReadsPerWrite { get; set; } = 10;
+
+        [JsonPropertyName("操作间隔(ms)")]
+        public int OperationIntervalMs { get; set; } = 0;
+
+        [JsonPropertyName("数据大小(KB)")]
+        public int ValueSizeKB { get; set; } = 10;
+    }
+
+}

+ 1216 - 1216
molilian.api/Controllers/public/TaskController.cs

@@ -1,1217 +1,1217 @@
-using molilian.core;
-using dodohold.core;
-using Microsoft.AspNetCore.Mvc;
-using Org.BouncyCastle.Ocsp;
-using System.Text.Json;
-using System.Runtime.InteropServices;
-using System.Net;
-using System.Threading.Channels;
-using TencentCloud.Cdwch.V20200915.Models;
-using COSXML.Network;
-using System.Security.Cryptography;
-using System.Threading;
-using TencentCloud.Batch.V20170312.Models;
-using Org.BouncyCastle.Bcpg.OpenPgp;
-using Microsoft.VisualBasic;
-using static System.Runtime.InteropServices.JavaScript.JSType;
-
-namespace molilian.api.Controllers
-{
-    [ApiController]
-    [Route("[controller]_70160bd632/[action]")]
-    public class TaskController : ControllerBase
-    {
-
-        protected IHttpContextAccessor _accessor;
-        public TaskController(IHttpContextAccessor accessor)
-        {
-            _accessor = accessor;
-        }
-
-
-        [HttpGet]
-        public ActionResult NotifyNotPushed()
-        {
-            PushDataReportCore core = new();
-            int count = core.NotifyNotPushed();
-            return new APIResult(new { success = true, count });
-        }
-
-        [HttpGet]
-        public async Task<ActionResult> FillPushReortData(DateTime date = default)
-        {
-            PushDataReportCore core = new();
-            if (date == default) date = DateTime.Now.AddDays(-1);
-
-            int count = await core.FillData(date.Date);
-            return new APIResult(new { success = true, count });
-        }
-
-
-
-        [HttpGet]
-        public async Task<ActionResult> PushReportData(int repush = 0, DateTime date = default)
-        {
-            PushDataReportCore core = new();
-            if (date == default) date = DateTime.Now.AddDays(-1);
-
-            int count = await core.PushReportData(date, repush == 1);
-            return new APIResult(new { success = true, count });
-        }
-
-        [HttpGet]
-        public async Task<ActionResult> GetRawItemIdTask(int limit = 100)
-        {
-            var list = await TkPoolCore.ListAsync();
-            if (list == null) return new APIResult(new { success = false, message = "没有有效账号", });
-            string message = string.Empty;
-            int total = 0;
-            foreach (var account in list)
-            {
-                if (!account.enable_sync_order) continue;
-                int accountId = account.id;
-
-                DateTime create_time = DateTime.Now.AddDays(-2);
-                string filter = "accountId=@accountId AND create_time>@create_time AND (itemId IS NULL OR itemId = '')";
-                var order_list = new DBContext.Table("tk_order_details")
-                      .Where(filter, new { accountId, create_time })
-                      .Order("create_time")
-                      .Limit(limit)
-                      .Select<TkOrderDetailDTO>();
-                if (!order_list.Any()) continue;
-
-                var alimama = new AlimamaPlus(account);
-
-                foreach (var item in order_list)
-                {
-                    string mktId = item.mktId;
-                    //进行订单和转链匹配
-                    (int state, string itemId) = alimama.GetRawItemId(mktId);
-                    if (state == -1) return new APIResult(new { success = false, message = "有账号掉线了", });
-                    if (string.IsNullOrEmpty(itemId)) continue;
-
-                    item.itemId = itemId;
-                    new DBContext.Table("tk_order_details")
-                        .Add("itemId", itemId)
-                        .Where("id=@id", new { item.id })
-                        .Update();
-                    TkOrderTrackingCore.MatchOrder(account, item);
-                }
-            }
-            return new APIResult(new { success = true, message = "ok", limit, total });
-        }
-
-
-        [HttpGet]
-        public async Task<ActionResult> BatchInsertLogDB(int limit = 100)
-        {
-            int total = await TkLogCore.BatchInsertLogDBAsync(limit);
-            return new APIResult(new { success = true, message = "ok", limit, total });
-        }
-
-
-        [HttpGet]
-        public async Task<ActionResult> GetAaliyunBlance()
-        {
-            AliyunPoolCore core = new AliyunPoolCore();
-            core.GetBlances();
-            return new APIResult(new { success = true, message = "ok" });
-        }
-
-
-        [HttpGet]
-        public async Task<ActionResult> GetSettleBills(int sleep = 2000)
-        {
-            var list = await TkPoolCore.ListAsync();
-            if (list == null) return new APIResult(new { success = false, message = "没有有效账号", });
-
-            string message = string.Empty;
-            int total = 0;
-            foreach (var account in list)
-            {
-                if (!account.enable_sync_order) continue;
-#if DEBUG
-                if (13 != account.id) continue;
-#endif
-                if (account.is_hide) continue;
-                try
-                {
-                    var alimama = new AlimamaPlus(account);
-                    total += await alimama.GetSettleBillsAsync(sleep);
-                }
-                catch (Exception ex)
-                {
-                    message = $"【收益接口异常】\t{account.name}\n{ex.Message}\n{ex.StackTrace}";
-                    NotifyCore.Notify(new NifyMessage
-                    {
-                        message = message,
-                        priority = NifyMessagePriority.high,
-                        tags = ["red_circle"]
-                    });
-                    continue;
-                }
-            }
-            return new APIResult(new { success = true, message = "ok", total });
-        }
-
-
-        [HttpGet]
-        public async Task<ActionResult> GetHistoryOrders(int id, int sleep, DateTime startTime, DateTime endTime)
-        {
-            var list = await TkPoolCore.ListAsync();
-            if (list == null) return new APIResult(new { success = false, message = "没有有效账号", });
-            if (id == 0) return new APIResult(new { success = false, message = "没有有效账号", });
-
-            string message = string.Empty;
-            int total = 0;
-            foreach (var account in list)
-            {
-
-                if (id != account.id) continue;
-
-                try
-                {
-                    //DateTime endTime = DateTime.Now;
-                    //DateTime startTime = endTime.AddDays(-90);
-                    if (account.id == 3) endTime = DateTime.Parse("2024-04-07");
-
-
-                    var alimama = new AlimamaPlus(account);
-                    total = await alimama.GetHistoryOrdersAsync(sleep, startTime, endTime);
-                }
-                catch (Exception ex)
-                {
-                    message = $"【历史订单接口异常】[{account.id}]{account.company}\n{ex.Message}\n{ex.StackTrace}";
-                    NotifyCore.Notify(new NifyMessage
-                    {
-                        message = message,
-                        priority = NifyMessagePriority.high,
-                        tags = ["red_circle"]
-                    });
-                    continue;
-                }
-            }
-            return new APIResult(new { success = true, message = "ok", total });
-        }
-
-        [HttpGet]
-        public async Task<ActionResult> GetIncyOrders(int sleep = 2000, int retry_days = 0)
-        {
-            var list = await TkPoolCore.ListAsync();
-            if (list == null) return new APIResult(new { success = false, message = "没有有效账号", });
-            string message = string.Empty;
-            int total = 0;
-            foreach (var account in list)
-            {
-                if (!account.enable_sync_order) continue;
-
-#if DEBUG
-                if (account.id != 128) continue;
-#endif
-                try
-                {
-                    var alimama = new AlimamaPlus(account);
-                    total = await alimama.GetIncyOrdersAsync(sleep, retry_days);
-                }
-                catch (Exception ex)
-                {
-                    message = $"【新增订单接口异常】[{account.id}]{account.company}\n{ex.Message}\n{ex.StackTrace}";
-                    NotifyCore.Notify(new NifyMessage
-                    {
-                        message = message,
-                        priority = NifyMessagePriority.high,
-                        tags = ["red_circle"]
-                    });
-                    continue;
-                }
-            }
-            return new APIResult(new { success = true, message = "ok", total });
-        }
-
-
-
-        [HttpGet]
-        public async Task<ActionResult> GetOrdersByAdZone(int id, long adzoneId, int sleep, DateTime startTime, DateTime endTime)
-        {
-            var list = await TkPoolCore.ListAsync();
-            if (list == null) return new APIResult(new { success = false, message = "没有有效账号", });
-            string message = string.Empty;
-            int total = 0;
-            foreach (var account in list)
-            {
-#if DEBUG
-                if (account.id != id) continue;
-#endif
-                try
-                {
-                    var alimama = new AlimamaPlus(account);
-                    total += await alimama.GetOrdersByAdZoneAsync(adzoneId, startTime, endTime, sleep);
-                }
-                catch (Exception ex)
-                {
-                    message = $"【新增订单接口异常adzone】[{account.id}]{account.company}\n{ex.Message}\n{ex.StackTrace}";
-                    NotifyCore.Notify(new NifyMessage
-                    {
-                        message = message,
-                        priority = NifyMessagePriority.high,
-                        tags = ["red_circle"]
-                    });
-                    continue;
-                }
-            }
-            return new APIResult(new { success = true, message = "ok", total });
-        }
-
-
-        [HttpGet]
-        public async Task<ActionResult> GetHistoryRefundOrders(int id, int sleep, DateTime startTime, DateTime endTime)
-        {
-            var list = await TkPoolCore.ListAsync();
-            if (list == null) return new APIResult(new { success = false, message = "没有有效账号", });
-            if (id == 0) return new APIResult(new { success = false, message = "没有有效账号", });
-
-            string message = string.Empty;
-            int total = 0;
-            foreach (var account in list)
-            {
-
-                if (id != account.id) continue;
-
-                try
-                {
-                    //DateTime endTime = DateTime.Now;
-                    //DateTime startTime = endTime.AddDays(-90);
-                    if (account.id == 3) endTime = DateTime.Parse("2024-04-07");
-
-
-                    var alimama = new AlimamaPlus(account);
-                    total += await alimama.GetHistoryRefundOrdersAsync(sleep, startTime, endTime);
-                }
-                catch (Exception ex)
-                {
-                    message = $"【维权订单接口异常】[{account.id}]{account.company}\n{ex.Message}\n{ex.StackTrace}";
-                    NotifyCore.Notify(new NifyMessage
-                    {
-                        message = message,
-                        priority = NifyMessagePriority.high,
-                        tags = ["red_circle"]
-                    });
-                    continue;
-                }
-            }
-            return new APIResult(new { success = true, message = "ok", total });
-        }
-
-        [HttpGet]
-        public async Task<ActionResult> GetIncyRefundOrders(int sleep = 2000)
-        {
-            var list = await TkPoolCore.ListAsync();
-            if (list == null) return new APIResult(new { success = false, message = "没有有效账号", });
-            string message = string.Empty;
-            int total = 0;
-            foreach (var account in list)
-            {
-#if DEBUG
-                if (account.id != 17) continue;
-#endif
-                if (!account.enable_sync_order) continue;
-
-                try
-                {
-                    var alimama = new AlimamaPlus(account);
-                    total += await alimama.GetIncyRefundOrdersAsync(sleep);
-                }
-                catch (Exception ex)
-                {
-                    message = $"【维权订单接口异常】[{account.id}]{account.company}\n{ex.Message}\n{ex.StackTrace}";
-                    NotifyCore.Notify(new NifyMessage
-                    {
-                        message = message,
-                        priority = NifyMessagePriority.high,
-                        tags = ["red_circle"]
-                    });
-                    continue;
-                }
-            }
-            return new APIResult(new { success = true, message = "ok", total });
-        }
-
-
-
-
-        [HttpGet]
-        public async Task<ActionResult> GetViolationuWarning()
-        {
-            var list = await TkPoolCore.ListAsync();
-            if (list == null) return new APIResult(new { success = false, message = "没有有效账号", });
-            string message = string.Empty;
-            foreach (var account in list)
-            {
-                try
-                {
-                    var alimama = new AlimamaPlus(account);
-                    alimama.GetViolationuWarning();
-                }
-                catch (Exception ex)
-                {
-                    message = $"【报表接口异常】GetViolationuWarning\n{ex.Message}\n{ex.StackTrace}";
-                    NotifyCore.Notify(new NifyMessage
-                    {
-                        message = message,
-                        priority = NifyMessagePriority.high,
-                        tags = ["red_circle"]
-                    });
-                    continue;
-                }
-            }
-            return new APIResult(new { success = true, message = "ok" });
-        }
-
-        [HttpGet]
-        public async Task<ActionResult> DailyLogs(int intervalDay = 1)
-        {
-            try
-            {
-                //本节点统计
-                await AlimamaPlus.NodeDailyLogsAsync(intervalDay, "parse_", "tb");
-                await AlimamaPlus.NodeDailyLogsAsync(intervalDay, "coupon_", "tb");
-                await AlimamaPlus.NodeDailyLogsAsync(intervalDay); //第三方老的统计
-
-                if (CenterHub.IsCenter)
-                {
-                    await JdUnionPlus.DailyLogsAsync(intervalDay);
-                    await ToolParsePlus.DailyLogsAsync(intervalDay);
-
-                    //中心服务器统计
-                    await AlimamaPlus.AllDailyLogsAsync(intervalDay, "parse_", "tb");
-                    await AlimamaPlus.AllDailyLogsAsync(intervalDay, "coupon_", "tb");
-
-                    await KsUnionPlus.DailyLogsAsync(intervalDay);
-                    await PddUnionPlus.DailyLogsAsync(intervalDay);
-
-                    await AlimamaPlus.AllDailyLogsAsync(intervalDay);
-                }
-            }
-            catch (Exception ex)
-            {
-                string message = $"【报表接口异常】DailyLogs\n{ex.Message}\n{ex.StackTrace}";
-                NotifyCore.Notify(new NifyMessage
-                {
-                    message = message,
-                    priority = NifyMessagePriority.high,
-                    tags = ["red_circle"]
-                });
-            }
-            return new APIResult(new { success = true, message = "ok" });
-        }
-
-
-        [HttpGet]
-
-        public async Task<ActionResult> ApiCallStatistics(int intervalDay = 1, int? chanel = null)
-        {
-            try
-            {
-                if (chanel == null)
-                {
-
-
-#if DEBUG
-                    await AlimamaPlus.ApiCallStatisticsAsync(3, intervalDay, TkChannelEnum.tb);
-#else
-                await AlimamaPlus.ApiCallStatisticsAsync(1, intervalDay, TkChannelEnum.tb);
-                await AlimamaPlus.ApiCallStatisticsAsync(2, intervalDay, TkChannelEnum.tb);
-                await AlimamaPlus.ApiCallStatisticsAsync(1, intervalDay, TkChannelEnum.jd);
-                await AlimamaPlus.ApiCallStatisticsAsync(1, intervalDay, TkChannelEnum.pdd);
-#endif
-
-                }
-                else
-                {
-                    TkChannelEnum _chanel = (TkChannelEnum)chanel;
-                    await AlimamaPlus.ApiCallStatisticsAsync(1, intervalDay, _chanel);
-
-                }
-            }
-            catch (Exception ex)
-            {
-                string message = $"【API报表更新异常】ApiCallStatistics\n{ex.Message}\n{ex.StackTrace}";
-                NotifyCore.Notify(new NifyMessage
-                {
-                    message = message,
-                    priority = NifyMessagePriority.high,
-                    tags = ["red_circle"]
-                });
-            }
-            return new APIResult(new { success = true, message = "ok" });
-        }
-
-
-        [HttpGet]
-        public async Task<ActionResult> DailyTable(int days = 3)
-        {
-            try
-            {
-                for (int i = 0; i < days; i++)
-                {
-                    if (CenterHub.IsCenter)
-                    {
-                        string table_suffix = DateTime.Now.AddDays(i).ToString("yyyyMMdd");
-                        string sql = $"CREATE TABLE IF NOT EXISTS tk_parse_logs_{table_suffix} LIKE tk_parse_logs;";
-                        DBContext.Execute(sql, null);
-                        sql = $"CREATE TABLE IF NOT EXISTS jd_parse_logs_{table_suffix} LIKE jd_parse_logs;";
-                        DBContext.Execute(sql, null);
-                        sql = $"CREATE TABLE IF NOT EXISTS ks_parse_logs_{table_suffix} LIKE ks_parse_logs;";
-                        DBContext.Execute(sql, null);
-                        sql = $"CREATE TABLE IF NOT EXISTS dy_parse_logs_{table_suffix} LIKE dy_parse_logs;";
-                        DBContext.Execute(sql, null);
-                        sql = $"CREATE TABLE IF NOT EXISTS tk_promotion_logs_{table_suffix} LIKE tk_promotion_logs;";
-                        DBContext.Execute(sql, null);
-
-                        sql = $"CREATE TABLE IF NOT EXISTS deeplink_parse_logs_{table_suffix} LIKE deeplink_parse_logs;";
-                        DBContext.Execute(sql, null);
-                        sql = $"CREATE TABLE IF NOT EXISTS deeplink_parse_logs_fail_{table_suffix} LIKE deeplink_parse_logs_fail_template;";
-                        DBContext.Execute(sql, null);
-                        sql = $"CREATE TABLE IF NOT EXISTS pdd_parse_logs_{table_suffix} LIKE pdd_parse_logs;";
-                        DBContext.Execute(sql, null);
-
-                        sql = $"CREATE TABLE IF NOT EXISTS tool_parse_logs_{table_suffix} LIKE tool_parse_logs_template;";
-                        DBContext.Execute(sql, null);
-                        sql = $"CREATE TABLE IF NOT EXISTS tk_dp_logs_{table_suffix} LIKE tk_dp_logs;";
-                        DBContext.Execute(sql, null);
-                        sql = $"CREATE TABLE IF NOT EXISTS pdd_dp_logs_{table_suffix} LIKE pdd_dp_logs;";
-                        DBContext.Execute(sql, null);
-                        sql = $"CREATE TABLE IF NOT EXISTS jd_dp_logs_{table_suffix} LIKE jd_dp_logs;";
-                        DBContext.Execute(sql, null);
-
-                    }
-                    else
-                    {
-                        string table_suffix = DateTime.Now.AddDays(i).ToString("yyyyMMdd");
-                        string sql = $"CREATE TABLE IF NOT EXISTS tk_cps_logs_{table_suffix} LIKE tk_cps_logs;";
-                        DBContext.Execute(sql, null);
-                        sql = $"CREATE TABLE IF NOT EXISTS tk_activity_logs_{table_suffix} LIKE tk_activity_logs;";
-                        DBContext.Execute(sql, null);
-                    }
-                }
-                TkLogCore.save_dailys_log = true;
-                RedisHelper.Set("turn:save_dailys_log", 1, 86400);
-            }
-            catch (Exception ex)
-            {
-                string message = $"【创建数据标错误】\n{ex.Message}\n{ex.StackTrace}";
-                NotifyCore.Notify(new NifyMessage
-                {
-                    message = message,
-                    priority = NifyMessagePriority.high,
-                    tags = ["red_circle"]
-                });
-            }
-            return new APIResult(new { success = true, message = "ok" });
-        }
-
-        //[HttpGet]
-
-        //public async Task<ActionResult> ApiCallStatistics(int intervalDay = 1)
-        //{
-        //    try
-        //    {
-        //        AlimamaPlus.ApiCallStatistics(intervalDay, TkChannelEnum.tb);
-        //        AlimamaPlus.ApiCallStatistics(intervalDay, TkChannelEnum.tb, "parse_");
-        //        AlimamaPlus.ApiCallStatistics(intervalDay, TkChannelEnum.tb, "coupon_");
-
-        //        AlimamaPlus.ApiCallStatistics(intervalDay, TkChannelEnum.wemeet, "parse_");
-        //        AlimamaPlus.ApiCallStatistics(intervalDay, TkChannelEnum.bdpan, "parse_");
-
-        //        AlimamaPlus.ApiCallStatistics(intervalDay, TkChannelEnum.dy, "parse_");
-        //        AlimamaPlus.ApiCallStatistics(intervalDay, TkChannelEnum.jd, "parse_");
-
-        //    }
-        //    catch (Exception ex)
-        //    {
-        //        string message = $"【报表接口异常】ApiCallStatistics\n{ex.Message}\n{ex.StackTrace}";
-        //        NotifyCore.Notify(new NifyMessage
-        //        {
-        //            message = message,
-        //            priority = NifyMessagePriority.high,
-        //            tags = ["red_circle"]
-        //        });
-        //    }
-        //    return new APIResult(new { success = true, message = "ok" });
-        //}
-
-
-        [HttpGet]
-        public async Task<ActionResult> GetDrawBalance()
-        {
-            var list = await TkPoolCore.ListAsync();
-            if (list == null) return new APIResult(new { success = false, message = "没有有效账号", });
-            string message = string.Empty;
-            foreach (var account in list)
-            {
-                if (!account.enable_sync_order) continue;
-                try
-                {
-                    var alimama = new AlimamaPlus(account);
-                    alimama.GetDrawBalance();
-                }
-                catch (Exception ex)
-                {
-                    message = $"【报表接口异常】GetDrawBalance\n{ex.Message}\n{ex.StackTrace}";
-                    NotifyCore.Notify(new NifyMessage
-                    {
-                        message = message,
-                        priority = NifyMessagePriority.high,
-                        tags = ["red_circle"]
-                    });
-                    continue;
-                }
-            }
-            return new APIResult(new { success = true, message = "ok" });
-        }
-
-        [HttpGet]
-        public async Task<ActionResult> LastFastReport(int days = 0)
-        {
-            string lockKey = "lock:LastFastReport";
-
-            if (!RedisHelper.Set(lockKey, "1", 300))
-            {
-                string message = "【LastFastReport】任务正在执行中,跳过本次调用";
-                _ = new LoggerLibrary("task_lock", "skip")
-                    .Info(message)
-                    .SaveAsync();
-                return new APIResult(new { success = false, message = "任务正在执行中" });
-            }
-
-            var startTimestamp = DateTime.Now;
-            try
-            {
-                string message = string.Empty;
-                var list = await TkPoolCore.ListAsync();
-                foreach (var account in list)
-                {
-#if DEBUG
-                    if (account.id != 113) continue;
-#endif
-                    if (!account.enable_sync_order) continue;
-                    try
-                    {
-                        var alimama = new AlimamaPlus(account);
-                        await alimama.SaveReportOverview(days);
-                    }
-                    catch (Exception ex)
-                    {
-                        message = $"【报表接口异常】LastFastReport\n{ex.Message}\n{ex.StackTrace}";
-                        NotifyCore.Notify(new NifyMessage
-                        {
-                            message = message,
-                            priority = NifyMessagePriority.high,
-                            tags = ["red_circle"]
-                        });
-                        continue;
-                    }
-                }
-
-                await EndPointCore.NotifyReload(true);
-                return new APIResult(new { success = true, message = "ok" });
-            }
-            catch (Exception ex)
-            {
-                string errorMessage = $"【LastFastReport】执行异常: {ex.Message}";
-                _ = new LoggerLibrary("task_error", "LastFastReport")
-                    .Info(errorMessage, ex.StackTrace)
-                    .SaveAsync();
-
-                NotifyCore.Notify(new NifyMessage
-                {
-                    message = errorMessage,
-                    priority = NifyMessagePriority.high,
-                    tags = ["red_circle"]
-                });
-                return new APIResult(new { success = false, message = errorMessage });
-            }
-            finally
-            {
-                var executionTime = DateTime.Now - startTimestamp;
-                string logMessage = $"【LastFastReport】执行完成,耗时: {executionTime.TotalSeconds:F2}秒";
-                _ = new LoggerLibrary("task_performance", "LastFastReport")
-                    .Info(logMessage)
-                    .SaveAsync();
-                RedisHelper.Del(lockKey);
-            }
-        }
-
-
-        [HttpGet]
-        public async Task<ActionResult> FastReport(string ignore = "")
-        {
-            string lockKey = "lock:FastReport";
-
-            if (!RedisHelper.Set(lockKey, "1", 300))
-            {
-                string message = "【FastReport】任务正在执行中,跳过本次调用";
-                _ = new LoggerLibrary("task_lock", "skip")
-                    .Info(message)
-                    .SaveAsync();
-                return new APIResult(new { success = false, message = "任务正在执行中" });
-            }
-
-            var startTimestamp = DateTime.Now;
-            try
-            {
-                string message = string.Empty;
-
-
-                var list = await TkPoolCore.ListAsync();
-                if (!ignore.Contains("tb") && list != null)
-                {
-                    foreach (var account in list)
-                    {
-#if DEBUG
-                        if (account.id != 113) continue;
-#endif
-                        try
-                        {
-                            account.current_daily_calls = await RiskControlCore.GetAllNodesCallsAsync(TkChannelEnum.tb, account.id, DateTime.Now.ToString("yyyyMMdd"));
-                            account.current_hourly_calls = await RiskControlCore.GetAllNodesCallsAsync(TkChannelEnum.tb, account.id, DateTime.Now.ToString("yyyyMMddHH"));
-
-                            new DBContext.Table("tk_pool")
-                                .Add("current_hourly_calls", account.current_hourly_calls)
-                                .Add("current_daily_calls", account.current_daily_calls)
-                                .Where("id=@id", new { account.id })
-                                .Update();
-                        }
-                        catch (Exception ex)
-                        {
-                            message = $"【TB计数异常】SaveCalls\n{ex.Message}\n{ex.StackTrace}";
-                            NotifyCore.Notify(new NifyMessage
-                            {
-                                message = message,
-                                priority = NifyMessagePriority.high,
-                                tags = ["red_circle"]
-                            });
-                            continue;
-                        }
-
-                        if (!account.enable_sync_order) continue;
-                        try
-                        {
-                            var alimama = new AlimamaPlus(account);
-                            await alimama.SaveReportOverview();
-                        }
-                        catch (Exception ex)
-                        {
-                            message = $"【报表接口异常】FastReport\n{ex.Message}\n{ex.StackTrace}";
-                            NotifyCore.Notify(new NifyMessage
-                            {
-                                message = message,
-                                priority = NifyMessagePriority.high,
-                                tags = ["red_circle"]
-                            });
-                            continue;
-                        }
-                    }
-                }
-
-                string filter = "";
-#if DEBUG
-                filter = "id IN (44)";
-#endif
-                var jd_list = new DBContext.Table("jd_pool").Where(filter, null).Select<JdPoolDTO>();
-
-                if (!ignore.Contains("jd") && jd_list != null)
-                {
-                    bool any_jd_changed = false;
-                    foreach (var account in jd_list)
-                    {
-                        bool changed = false;
-                        try
-                        {
-                            account.current_daily_calls = await RiskControlCore.GetAllNodesCallsAsync(TkChannelEnum.jd, account.id, DateTime.Now.ToString("yyyyMMdd"));
-                            account.current_hourly_calls = await RiskControlCore.GetAllNodesCallsAsync(TkChannelEnum.jd, account.id, DateTime.Now.ToString("yyyyMMddHH"));
-                            changed = true;
-                            any_jd_changed = true;
-                        }
-                        catch (Exception ex)
-                        {
-                            message = $"【JD计数异常】SaveCalls\n{ex.Message}\n{ex.StackTrace}";
-                            NotifyCore.Notify(new NifyMessage
-                            {
-                                message = message,
-                                priority = NifyMessagePriority.high,
-                                tags = ["red_circle"]
-                            });
-                            continue;
-                        }
-                        if (!account.status) continue;
-
-                        string sleepLockKey = $"lock:exception:sleep:jd_{account.id}";
-                        if (!string.IsNullOrEmpty(RedisHelper.Get(sleepLockKey))) continue;
-
-                        if (changed) JdPoolCore.Update(account);
-                    }
-                    if (any_jd_changed) JdPoolCore.Refresh();
-                }
-
-
-                var pdd_list = new DBContext.Table("pdd_pool").Where(filter, null).Select<PddPoolDTO>();
-                if (!ignore.Contains("pdd") && pdd_list != null)
-                {
-                    bool any_pdd_changed = false;
-                    foreach (var account in pdd_list)
-                    {
-                        bool changed = false;
-                        try
-                        {
-                            account.current_daily_calls = await RiskControlCore.GetAllNodesCallsAsync(TkChannelEnum.pdd, account.id, DateTime.Now.ToString("yyyyMMdd"));
-                            account.current_hourly_calls = await RiskControlCore.GetAllNodesCallsAsync(TkChannelEnum.pdd, account.id, DateTime.Now.ToString("yyyyMMddHH"));
-                            changed = true;
-                            any_pdd_changed = true;
-                        }
-                        catch (Exception ex)
-                        {
-                            message = $"【Pdd计数异常】SaveCalls\n{ex.Message}\n{ex.StackTrace}";
-                            NotifyCore.Notify(new NifyMessage
-                            {
-                                message = message,
-                                priority = NifyMessagePriority.high,
-                                tags = ["red_circle"]
-                            });
-                            continue;
-                        }
-                        if (changed) PddPoolCore.Update(account);
-                    }
-                    if (any_pdd_changed) PddPoolCore.Refresh();
-                }
-
-
-
-                var cps_list = new DBContext.Table("cps_links")
-                    .Where("status=@status", new { status = 1 })
-                    .Select<CpsLinksDTO>();
-
-                if (!ignore.Contains("cps") && cps_list != null)
-                {
-                    bool any_cps_changed = false;
-                    foreach (var link in cps_list)
-                    {
-                        bool changed = false;
-                        try
-                        {
-                            link.current_daily_calls = await RiskControlCore.GetAllNodesCallsAsync(TkChannelEnum.cps, link.id, DateTime.Now.ToString("yyyyMMdd"));
-                            link.current_hourly_calls = await RiskControlCore.GetAllNodesCallsAsync(TkChannelEnum.cps, link.id, DateTime.Now.ToString("yyyyMMddHH"));
-                            changed = true;
-                            any_cps_changed = true;
-                        }
-                        catch (Exception ex)
-                        {
-                            message = $"【Cps计数异常】SaveCalls\n{ex.Message}\n{ex.StackTrace}";
-                            NotifyCore.Notify(new NifyMessage
-                            {
-                                message = message,
-                                priority = NifyMessagePriority.high,
-                                tags = ["red_circle"]
-                            });
-                            continue;
-                        }
-                        if (changed) CpsPoolCore.Update(link);
-                    }
-                    if (any_cps_changed) CpsPoolCore.Refresh();
-                }
-                await EndPointCore.NotifyReload(true);
-                return new APIResult(new { success = true, message = "ok" });
-            }
-            catch (Exception ex)
-            {
-                string errorMessage = $"【FastReport】执行异常: {ex.Message}";
-                _ = new LoggerLibrary("task_error", "FastReport")
-                    .Info(errorMessage, ex.StackTrace)
-                    .SaveAsync();
-
-                NotifyCore.Notify(new NifyMessage
-                {
-                    message = errorMessage,
-                    priority = NifyMessagePriority.high,
-                    tags = ["red_circle"]
-                });
-                return new APIResult(new { success = false, message = errorMessage });
-            }
-            finally
-            {
-                var executionTime = DateTime.Now - startTimestamp;
-                string logMessage = $"【FastReport】执行完成,耗时: {executionTime.TotalSeconds:F2}秒";
-                _ = new LoggerLibrary("task_performance", "FastReport")
-                    .Info(logMessage)
-                    .SaveAsync();
-
-                RedisHelper.Del(lockKey);
-            }
-        }
-
-
-
-        [HttpGet]
-        public async Task<ActionResult> jdQuerySpreadEffectData(string ignore = "")
-        {
-            string message = string.Empty;
-
-
-            string filter = "";
-#if DEBUG
-            filter = "id IN (37, 15)";
-#endif
-            var jd_list = new DBContext.Table("jd_pool").Where(filter, null).Select<JdPoolDTO>();
-
-            if (!ignore.Contains("jd") && jd_list != null)
-            {
-                bool any_jd_changed = false;
-                foreach (var account in jd_list)
-                {
-                    bool changed = false;
-                    if (!account.status) continue;
-                    if (!account.enable_sync_report) continue;
-
-                    string lockKey = $"lock:exception:sleep:jd_{account.id}";
-                    if (!string.IsNullOrEmpty(RedisHelper.Get(lockKey))) continue;
-
-                    if (!string.IsNullOrEmpty(account.union_cookies))
-                    {
-                        try
-                        {
-                            var plus = new JdUnionPlus(account);
-                            var data = await plus.queryTodaySpreadEffectData();
-                            account.today_clickNum = data.clickNum;
-                            account.today_cosFee = data.cosFee;
-                            account.today_cosPrice = data.cosPrice;
-                            account.today_finishCosFee = data.finishCosFee;
-                            account.today_finishCosPrice = data.finishCosPrice;
-                            account.today_finishOrderNum = data.finishOrderNum;
-                            account.today_orderNum = data.orderNum;
-
-                            changed = true;
-                            any_jd_changed = true;
-                        }
-                        catch (Exception ex)
-                        {
-                            if (ex.Message.Contains("no login"))
-                            {
-                                account.status = false;
-                                _ = JdPoolCore.DisabledAsync(account.id, account.name, ex.Message);
-                            }
-                            else
-                            {
-                                RedisHelper.Set(lockKey, 1, 3600);
-                            }
-                            message = $"【JD报表接口异常】{account.id}:{account.name}\tqueryTodaySpreadEffectData\n{ex.Message}\n{ex.StackTrace}";
-                            NotifyCore.Notify(message);
-                            continue;
-                        }
-                    }
-                    if (changed) JdPoolCore.Update(account);
-                }
-                if (any_jd_changed) JdPoolCore.Refresh();
-            }
-            return new APIResult(new { success = true, message = "ok" });
-        }
-
-
-
-        [HttpGet]
-        public async Task<ActionResult> SaveReport()
-        {
-            string message = string.Empty;
-
-            var list = await TkPoolCore.ListAsync();
-            if (list != null)
-            {
-                foreach (var account in list)
-                {
-                    if (!account.enable_sync_order) continue;
-#if DEBUG
-                    if (account.id != 5) continue;
-#endif
-                    if (account.is_hide) continue;
-                    try
-                    {
-                        var alimama = new AlimamaPlus(account);
-                        alimama.SaveReportHourtrend(DateTime.Now.AddDays(-1));
-                        alimama.SaveReportHourtrend(DateTime.Now);
-
-                        if (DateTime.Now.Hour >= 12 && DateTime.Now.Hour < 16)
-                        {
-                            DateTime date = DateTime.Now.AddDays(-1);
-                            await alimama.FixReport(date);
-                        }
-                    }
-                    catch (Exception ex)
-                    {
-                        message = $"【报表接口异常】SaveReport\n{ex.Message}\n{ex.StackTrace}";
-                        NotifyCore.Notify(new NifyMessage
-                        {
-                            message = message,
-                            priority = NifyMessagePriority.high,
-                            tags = ["red_circle"]
-                        });
-                        continue;
-                    }
-                }
-            }
-            return new APIResult(new { success = true, message = "ok" });
-        }
-
-        [HttpGet]
-        public async Task<ActionResult> JdSaveReport(int id = 0)
-        {
-            string message = string.Empty;
-            string filter = string.Empty;
-            if (id > 0) filter = "id=@id";
-            var jd_list = new DBContext.Table("jd_pool").Where(filter, new { id }).Select<JdPoolDTO>();
-            if (jd_list != null)
-            {
-                DateTime startDate = DateTime.Now.AddDays(-89);
-                DateTime endDate = DateTime.Now.AddDays(-1);
-                foreach (var account in jd_list)
-                {
-                    if (!account.status) continue;
-#if DEBUG
-                    startDate = DateTime.Parse("2024-07-31");
-                    endDate = DateTime.Parse("2024-07-31");
-                    if (account.id != 15) continue;
-#endif
-                    if (account.is_hide) continue;
-                    if (id == 0 && !account.enable_sync_report) continue;
-
-                    if (!string.IsNullOrEmpty(account.union_cookies))
-                    {
-                        try
-                        {
-                            var plus = new JdUnionPlus(account);
-                            _ = await plus.querySpreadEffectData(startDate, endDate);
-                            _ = await plus.queryEstimateCommList();
-                        }
-                        catch (Exception ex)
-                        {
-                            if (ex.Message.Contains("no login"))
-                            {
-                                _ = JdPoolCore.DisabledAsync(account.id, account.name, ex.Message);
-                            }
-                            message = $"【JD报表接口异常】{account.id}:{account.name}\tquerySpreadEffectData\n{ex.Message}\n{ex.StackTrace}";
-                            NotifyCore.Notify(message);
-                            continue;
-                        }
-                    }
-                }
-            }
-            return new APIResult(new { success = true, message = "ok" });
-        }
-        [HttpGet]
-        public async Task<ActionResult> PddSaveReport(int days = -1)
-        {
-            string message = string.Empty;
-
-            var pdd_list = new DBContext.Table("pdd_pool").Where("cookie_status=1", new { }).Select<PddPoolDTO>();
-            if (pdd_list != null)
-            {
-                DateTime startDate = DateTime.Now.AddDays(days);
-                DateTime endDate = DateTime.Now;
-                foreach (var account in pdd_list)
-                {
-                    if (account.is_hide) continue;
-                    if (!account.enable_sync_revenue) continue;
-
-                    if (!string.IsNullOrEmpty(account.cookies))
-                    {
-                        try
-                        {
-                            var plus = new PddUnionPlus(account);
-                            await plus.queryEstimateRevenueList(startDate, endDate);
-                        }
-                        catch (Exception ex)
-                        {
-                            if (ex.Message.Contains("43001:会话已过期"))
-                            {
-                                PddPoolCore.CookieDisabled(account.id);
-                            }
-                            message = $"【PDD报表接口异常】{account.id}:{account.name}\tqueryEstimateRevenueList\n{ex.Message}\n{ex.StackTrace}";
-                            NotifyCore.Notify(new NifyMessage
-                            {
-                                message = message,
-                                priority = NifyMessagePriority.high,
-                                tags = ["red_circle"]
-                            });
-                            continue;
-                        }
-                    }
-                }
-            }
-            //更新前一天的pdd统计表
-            await AlimamaPlus.ApiCallStatisticsAsync(1, 1, TkChannelEnum.pdd);
-            return new APIResult(new { success = true, message = "ok" });
-        }
-
-
-        [HttpGet]
-        public async Task<ActionResult> FixReport(int id, string dt)
-        {
-            if (!DateTime.TryParse(dt, out DateTime date))
-            {
-                return new APIResult(new { success = false, message = "日期错误", });
-            }
-
-            var list = await TkPoolCore.ListAsync();
-            if (list == null) return new APIResult(new { success = false, message = "没有有效账号", });
-            string message = string.Empty;
-            foreach (var account in list)
-            {
-                if (id != account.id) continue;
-                if (account.is_hide) continue;
-                try
-                {
-                    var alimama = new AlimamaPlus(account);
-
-                    while (date.Date < DateTime.Now.Date)
-                    {
-                        await alimama.FixReport(date);
-                        date = date.AddDays(1);
-                    }
-                }
-                catch (Exception ex)
-                {
-                    message = $"【报表接口异常】SaveReport\n{ex.Message}\n{ex.StackTrace}";
-                    NotifyCore.Notify(new NifyMessage
-                    {
-                        message = message,
-                        priority = NifyMessagePriority.high,
-                        tags = ["red_circle"]
-                    });
-                    continue;
-                }
-            }
-            return new APIResult(new { success = true, message = "ok" });
-        }
-
-        [HttpGet]
-        public ActionResult RenewCookie(string name)
-        {
-            var account = new DBContext.Table("tk_pool").Get<TkPoolDTO>("name=@name", new { name });
-
-            if (account == null) return new APIResult(new { success = false, message = "没有有效账号", });
-            var alimama = new AlimamaPlus(account);
-
-            (bool success, string message) = alimama.RenewCookie();
-            return new APIResult(new { success, message });
-        }
-
-
-        [HttpGet]
-        public ActionResult Reload()
-        {
-            RiskControlCore.Refresh();
-            TkConfigCore.Refresh();
-            EndPointCore.Refresh();
-            ProxyNodesCore.Refresh();
-            TkPoolCore.Refresh();
-            OtherApiPoolCore.Refresh();
-            TkEndpointCore.Refresh();
-            TkEndpointManager.Refresh();
-
-
-            ApiAccountCore.Refresh();
-            DataokeCore.Refresh();
-            JdPoolCore.Refresh();
-            PddPoolCore.Refresh();
-            PangolinPoolCore.Refresh();
-            ReduPoolCore.Refresh();
-
-            //ElePoolCore.Refresh();
-            //MeituanPoolCore.Refresh();
-            CpsPoolCore.Refresh();
-            DeeplinkParseRuleCore.Refresh();
-
-            OverrideRuleCore.Refresh();
-            return new APIResult(new
-            {
-                success = true,
-                message = "ok",
-            });
-        }
-
-        [HttpGet]
-        public ActionResult ReloadAccount()
-        {
-            RiskControlCore.Refresh();
-            EndPointCore.Refresh();
-            ProxyNodesCore.Refresh();
-            TkPoolCore.Refresh();
-            OtherApiPoolCore.Refresh();
-            JdPoolCore.Refresh();
-            PddPoolCore.Refresh();
-
-            //ElePoolCore.Refresh();
-            //MeituanPoolCore.Refresh();
-            CpsPoolCore.Refresh();
-
-            OverrideRuleCore.Refresh();
-            return new APIResult(new
-            {
-                success = true,
-                message = "ok",
-            });
-        }
-
-
-
-        [HttpGet]
-        public async Task<ActionResult> CheckDeepUrl(int accountid)
-        {
-            var postContent = "88✔7Fi43dJwWpV£ https://m.tb.cn/h.g92hfR4JLgn7yvG  MF7997 我分享给你了一个超赞的内容,快来看看吧";
-            var channel = "tb";
-            var ip = "127.0.0.1";
-            var oaid = "test-oaid";
-
-            bool success = false;
-            string message;
-
-            //============================== 放弃转链-没有匹配账号 ==============================
-            TkPoolDTO? account = await TkPoolCore.GetOneAsync(accountid);
-            if (account == null)
-            {
-                return new APIResult(new
-                {
-                    success = false,
-                    message = "没有匹配账号",
-                });
-            }
-            var alimama = new AlimamaPlus(account);
-
-            TkDataDTO result = new TkDataDTO();
-            string url = AlimamaPlus.GetLink(postContent);
-            if (string.IsNullOrEmpty(url))
-            {
-                return new APIResult(new
-                {
-                    success = false,
-                    message = "无效URL",
-                });
-            }
-            WebProxy proxy = ProxyNodesCore.RandomOne(account.nodeName);
-            (success, string content) = await alimama.GetDesiredUrlAsync(proxy, url);
-            if (content.Contains("霸下通用 web 页面-验证码"))
-            {
-                message = "霸下验证码";
-            }
-            else
-            {
-                message = content;
-            }
-            return new APIResult(new
-            {
-                success = true,
-                message,
-            });
-        }
-
-
-    }
+using molilian.core;
+using dodohold.core;
+using Microsoft.AspNetCore.Mvc;
+using Org.BouncyCastle.Ocsp;
+using System.Text.Json;
+using System.Runtime.InteropServices;
+using System.Net;
+using System.Threading.Channels;
+using TencentCloud.Cdwch.V20200915.Models;
+using COSXML.Network;
+using System.Security.Cryptography;
+using System.Threading;
+using TencentCloud.Batch.V20170312.Models;
+using Org.BouncyCastle.Bcpg.OpenPgp;
+using Microsoft.VisualBasic;
+using static System.Runtime.InteropServices.JavaScript.JSType;
+
+namespace molilian.api.Controllers
+{
+    [ApiController]
+    [Route("[controller]_70160bd632/[action]")]
+    public class TaskController : ControllerBase
+    {
+
+        protected IHttpContextAccessor _accessor;
+        public TaskController(IHttpContextAccessor accessor)
+        {
+            _accessor = accessor;
+        }
+
+
+        [HttpGet]
+        public ActionResult NotifyNotPushed()
+        {
+            PushDataReportCore core = new();
+            int count = core.NotifyNotPushed();
+            return new APIResult(new { success = true, count });
+        }
+
+        [HttpGet]
+        public async Task<ActionResult> FillPushReortData(DateTime date = default)
+        {
+            PushDataReportCore core = new();
+            if (date == default) date = DateTime.Now.AddDays(-1);
+
+            int count = await core.FillData(date.Date);
+            return new APIResult(new { success = true, count });
+        }
+
+
+
+        [HttpGet]
+        public async Task<ActionResult> PushReportData(int repush = 0, DateTime date = default)
+        {
+            PushDataReportCore core = new();
+            if (date == default) date = DateTime.Now.AddDays(-1);
+
+            int count = await core.PushReportData(date, repush == 1);
+            return new APIResult(new { success = true, count });
+        }
+
+        [HttpGet]
+        public async Task<ActionResult> GetRawItemIdTask(int limit = 100)
+        {
+            var list = await TkPoolCore.ListAsync();
+            if (list == null) return new APIResult(new { success = false, message = "没有有效账号", });
+            string message = string.Empty;
+            int total = 0;
+            foreach (var account in list)
+            {
+                if (!account.enable_sync_order) continue;
+                int accountId = account.id;
+
+                DateTime create_time = DateTime.Now.AddDays(-2);
+                string filter = "accountId=@accountId AND create_time>@create_time AND (itemId IS NULL OR itemId = '')";
+                var order_list = new DBContext.Table("tk_order_details")
+                      .Where(filter, new { accountId, create_time })
+                      .Order("create_time")
+                      .Limit(limit)
+                      .Select<TkOrderDetailDTO>();
+                if (!order_list.Any()) continue;
+
+                var alimama = new AlimamaPlus(account);
+
+                foreach (var item in order_list)
+                {
+                    string mktId = item.mktId;
+                    //进行订单和转链匹配
+                    (int state, string itemId) = alimama.GetRawItemId(mktId);
+                    if (state == -1) return new APIResult(new { success = false, message = "有账号掉线了", });
+                    if (string.IsNullOrEmpty(itemId)) continue;
+
+                    item.itemId = itemId;
+                    new DBContext.Table("tk_order_details")
+                        .Add("itemId", itemId)
+                        .Where("id=@id", new { item.id })
+                        .Update();
+                    TkOrderTrackingCore.MatchOrder(account, item);
+                }
+            }
+            return new APIResult(new { success = true, message = "ok", limit, total });
+        }
+
+
+        [HttpGet]
+        public async Task<ActionResult> BatchInsertLogDB(int limit = 100)
+        {
+            int total = await TkLogCore.BatchInsertLogDBAsync(limit);
+            return new APIResult(new { success = true, message = "ok", limit, total });
+        }
+
+
+        [HttpGet]
+        public async Task<ActionResult> GetAaliyunBlance()
+        {
+            AliyunPoolCore core = new AliyunPoolCore();
+            core.GetBlances();
+            return new APIResult(new { success = true, message = "ok" });
+        }
+
+
+        [HttpGet]
+        public async Task<ActionResult> GetSettleBills(int sleep = 2000)
+        {
+            var list = await TkPoolCore.ListAsync();
+            if (list == null) return new APIResult(new { success = false, message = "没有有效账号", });
+
+            string message = string.Empty;
+            int total = 0;
+            foreach (var account in list)
+            {
+                if (!account.enable_sync_order) continue;
+#if DEBUG
+                if (13 != account.id) continue;
+#endif
+                if (account.is_hide) continue;
+                try
+                {
+                    var alimama = new AlimamaPlus(account);
+                    total += await alimama.GetSettleBillsAsync(sleep);
+                }
+                catch (Exception ex)
+                {
+                    message = $"【收益接口异常】\t{account.name}\n{ex.Message}\n{ex.StackTrace}";
+                    NotifyCore.Notify(new NifyMessage
+                    {
+                        message = message,
+                        priority = NifyMessagePriority.high,
+                        tags = ["red_circle"]
+                    });
+                    continue;
+                }
+            }
+            return new APIResult(new { success = true, message = "ok", total });
+        }
+
+
+        [HttpGet]
+        public async Task<ActionResult> GetHistoryOrders(int id, int sleep, DateTime startTime, DateTime endTime)
+        {
+            var list = await TkPoolCore.ListAsync();
+            if (list == null) return new APIResult(new { success = false, message = "没有有效账号", });
+            if (id == 0) return new APIResult(new { success = false, message = "没有有效账号", });
+
+            string message = string.Empty;
+            int total = 0;
+            foreach (var account in list)
+            {
+
+                if (id != account.id) continue;
+
+                try
+                {
+                    //DateTime endTime = DateTime.Now;
+                    //DateTime startTime = endTime.AddDays(-90);
+                    if (account.id == 3) endTime = DateTime.Parse("2024-04-07");
+
+
+                    var alimama = new AlimamaPlus(account);
+                    total = await alimama.GetHistoryOrdersAsync(sleep, startTime, endTime);
+                }
+                catch (Exception ex)
+                {
+                    message = $"【历史订单接口异常】[{account.id}]{account.company}\n{ex.Message}\n{ex.StackTrace}";
+                    NotifyCore.Notify(new NifyMessage
+                    {
+                        message = message,
+                        priority = NifyMessagePriority.high,
+                        tags = ["red_circle"]
+                    });
+                    continue;
+                }
+            }
+            return new APIResult(new { success = true, message = "ok", total });
+        }
+
+        [HttpGet]
+        public async Task<ActionResult> GetIncyOrders(int sleep = 2000, int retry_days = 0)
+        {
+            var list = await TkPoolCore.ListAsync();
+            if (list == null) return new APIResult(new { success = false, message = "没有有效账号", });
+            string message = string.Empty;
+            int total = 0;
+            foreach (var account in list)
+            {
+                if (!account.enable_sync_order) continue;
+
+#if DEBUG
+                if (account.id != 128) continue;
+#endif
+                try
+                {
+                    var alimama = new AlimamaPlus(account);
+                    total = await alimama.GetIncyOrdersAsync(sleep, retry_days);
+                }
+                catch (Exception ex)
+                {
+                    message = $"【新增订单接口异常】[{account.id}]{account.company}\n{ex.Message}\n{ex.StackTrace}";
+                    NotifyCore.Notify(new NifyMessage
+                    {
+                        message = message,
+                        priority = NifyMessagePriority.high,
+                        tags = ["red_circle"]
+                    });
+                    continue;
+                }
+            }
+            return new APIResult(new { success = true, message = "ok", total });
+        }
+
+
+
+        [HttpGet]
+        public async Task<ActionResult> GetOrdersByAdZone(int id, long adzoneId, int sleep, DateTime startTime, DateTime endTime)
+        {
+            var list = await TkPoolCore.ListAsync();
+            if (list == null) return new APIResult(new { success = false, message = "没有有效账号", });
+            string message = string.Empty;
+            int total = 0;
+            foreach (var account in list)
+            {
+#if DEBUG
+                if (account.id != id) continue;
+#endif
+                try
+                {
+                    var alimama = new AlimamaPlus(account);
+                    total += await alimama.GetOrdersByAdZoneAsync(adzoneId, startTime, endTime, sleep);
+                }
+                catch (Exception ex)
+                {
+                    message = $"【新增订单接口异常adzone】[{account.id}]{account.company}\n{ex.Message}\n{ex.StackTrace}";
+                    NotifyCore.Notify(new NifyMessage
+                    {
+                        message = message,
+                        priority = NifyMessagePriority.high,
+                        tags = ["red_circle"]
+                    });
+                    continue;
+                }
+            }
+            return new APIResult(new { success = true, message = "ok", total });
+        }
+
+
+        [HttpGet]
+        public async Task<ActionResult> GetHistoryRefundOrders(int id, int sleep, DateTime startTime, DateTime endTime)
+        {
+            var list = await TkPoolCore.ListAsync();
+            if (list == null) return new APIResult(new { success = false, message = "没有有效账号", });
+            if (id == 0) return new APIResult(new { success = false, message = "没有有效账号", });
+
+            string message = string.Empty;
+            int total = 0;
+            foreach (var account in list)
+            {
+
+                if (id != account.id) continue;
+
+                try
+                {
+                    //DateTime endTime = DateTime.Now;
+                    //DateTime startTime = endTime.AddDays(-90);
+                    if (account.id == 3) endTime = DateTime.Parse("2024-04-07");
+
+
+                    var alimama = new AlimamaPlus(account);
+                    total += await alimama.GetHistoryRefundOrdersAsync(sleep, startTime, endTime);
+                }
+                catch (Exception ex)
+                {
+                    message = $"【维权订单接口异常】[{account.id}]{account.company}\n{ex.Message}\n{ex.StackTrace}";
+                    NotifyCore.Notify(new NifyMessage
+                    {
+                        message = message,
+                        priority = NifyMessagePriority.high,
+                        tags = ["red_circle"]
+                    });
+                    continue;
+                }
+            }
+            return new APIResult(new { success = true, message = "ok", total });
+        }
+
+        [HttpGet]
+        public async Task<ActionResult> GetIncyRefundOrders(int sleep = 2000)
+        {
+            var list = await TkPoolCore.ListAsync();
+            if (list == null) return new APIResult(new { success = false, message = "没有有效账号", });
+            string message = string.Empty;
+            int total = 0;
+            foreach (var account in list)
+            {
+#if DEBUG
+                if (account.id != 17) continue;
+#endif
+                if (!account.enable_sync_order) continue;
+
+                try
+                {
+                    var alimama = new AlimamaPlus(account);
+                    total += await alimama.GetIncyRefundOrdersAsync(sleep);
+                }
+                catch (Exception ex)
+                {
+                    message = $"【维权订单接口异常】[{account.id}]{account.company}\n{ex.Message}\n{ex.StackTrace}";
+                    NotifyCore.Notify(new NifyMessage
+                    {
+                        message = message,
+                        priority = NifyMessagePriority.high,
+                        tags = ["red_circle"]
+                    });
+                    continue;
+                }
+            }
+            return new APIResult(new { success = true, message = "ok", total });
+        }
+
+
+
+
+        [HttpGet]
+        public async Task<ActionResult> GetViolationuWarning()
+        {
+            var list = await TkPoolCore.ListAsync();
+            if (list == null) return new APIResult(new { success = false, message = "没有有效账号", });
+            string message = string.Empty;
+            foreach (var account in list)
+            {
+                try
+                {
+                    var alimama = new AlimamaPlus(account);
+                    alimama.GetViolationuWarning();
+                }
+                catch (Exception ex)
+                {
+                    message = $"【报表接口异常】GetViolationuWarning\n{ex.Message}\n{ex.StackTrace}";
+                    NotifyCore.Notify(new NifyMessage
+                    {
+                        message = message,
+                        priority = NifyMessagePriority.high,
+                        tags = ["red_circle"]
+                    });
+                    continue;
+                }
+            }
+            return new APIResult(new { success = true, message = "ok" });
+        }
+
+        [HttpGet]
+        public async Task<ActionResult> DailyLogs(int intervalDay = 1)
+        {
+            try
+            {
+                //本节点统计
+                await AlimamaPlus.NodeDailyLogsAsync(intervalDay, "parse_", "tb");
+                await AlimamaPlus.NodeDailyLogsAsync(intervalDay, "coupon_", "tb");
+                await AlimamaPlus.NodeDailyLogsAsync(intervalDay); //第三方老的统计
+
+                if (CenterHub.IsCenter)
+                {
+                    await JdUnionPlus.DailyLogsAsync(intervalDay);
+                    await ToolParsePlus.DailyLogsAsync(intervalDay);
+
+                    //中心服务器统计
+                    await AlimamaPlus.AllDailyLogsAsync(intervalDay, "parse_", "tb");
+                    await AlimamaPlus.AllDailyLogsAsync(intervalDay, "coupon_", "tb");
+
+                    await KsUnionPlus.DailyLogsAsync(intervalDay);
+                    await PddUnionPlus.DailyLogsAsync(intervalDay);
+
+                    await AlimamaPlus.AllDailyLogsAsync(intervalDay);
+                }
+            }
+            catch (Exception ex)
+            {
+                string message = $"【报表接口异常】DailyLogs\n{ex.Message}\n{ex.StackTrace}";
+                NotifyCore.Notify(new NifyMessage
+                {
+                    message = message,
+                    priority = NifyMessagePriority.high,
+                    tags = ["red_circle"]
+                });
+            }
+            return new APIResult(new { success = true, message = "ok" });
+        }
+
+
+        [HttpGet]
+
+        public async Task<ActionResult> ApiCallStatistics(int intervalDay = 1, int? chanel = null)
+        {
+            try
+            {
+                if (chanel == null)
+                {
+
+
+#if DEBUG
+                    await AlimamaPlus.ApiCallStatisticsAsync(3, intervalDay, TkChannelEnum.tb);
+#else
+                await AlimamaPlus.ApiCallStatisticsAsync(1, intervalDay, TkChannelEnum.tb);
+                await AlimamaPlus.ApiCallStatisticsAsync(2, intervalDay, TkChannelEnum.tb);
+                await AlimamaPlus.ApiCallStatisticsAsync(1, intervalDay, TkChannelEnum.jd);
+                await AlimamaPlus.ApiCallStatisticsAsync(1, intervalDay, TkChannelEnum.pdd);
+#endif
+
+                }
+                else
+                {
+                    TkChannelEnum _chanel = (TkChannelEnum)chanel;
+                    await AlimamaPlus.ApiCallStatisticsAsync(1, intervalDay, _chanel);
+
+                }
+            }
+            catch (Exception ex)
+            {
+                string message = $"【API报表更新异常】ApiCallStatistics\n{ex.Message}\n{ex.StackTrace}";
+                NotifyCore.Notify(new NifyMessage
+                {
+                    message = message,
+                    priority = NifyMessagePriority.high,
+                    tags = ["red_circle"]
+                });
+            }
+            return new APIResult(new { success = true, message = "ok" });
+        }
+
+
+        [HttpGet]
+        public async Task<ActionResult> DailyTable(int days = 3)
+        {
+            try
+            {
+                for (int i = 0; i < days; i++)
+                {
+                    if (CenterHub.IsCenter)
+                    {
+                        string table_suffix = DateTime.Now.AddDays(i).ToString("yyyyMMdd");
+                        string sql = $"CREATE TABLE IF NOT EXISTS tk_parse_logs_{table_suffix} LIKE tk_parse_logs;";
+                        DBContext.Execute(sql, null);
+                        sql = $"CREATE TABLE IF NOT EXISTS jd_parse_logs_{table_suffix} LIKE jd_parse_logs;";
+                        DBContext.Execute(sql, null);
+                        sql = $"CREATE TABLE IF NOT EXISTS ks_parse_logs_{table_suffix} LIKE ks_parse_logs;";
+                        DBContext.Execute(sql, null);
+                        sql = $"CREATE TABLE IF NOT EXISTS dy_parse_logs_{table_suffix} LIKE dy_parse_logs;";
+                        DBContext.Execute(sql, null);
+                        sql = $"CREATE TABLE IF NOT EXISTS tk_promotion_logs_{table_suffix} LIKE tk_promotion_logs;";
+                        DBContext.Execute(sql, null);
+
+                        sql = $"CREATE TABLE IF NOT EXISTS deeplink_parse_logs_{table_suffix} LIKE deeplink_parse_logs;";
+                        DBContext.Execute(sql, null);
+                        sql = $"CREATE TABLE IF NOT EXISTS deeplink_parse_logs_fail_{table_suffix} LIKE deeplink_parse_logs_fail_template;";
+                        DBContext.Execute(sql, null);
+                        sql = $"CREATE TABLE IF NOT EXISTS pdd_parse_logs_{table_suffix} LIKE pdd_parse_logs;";
+                        DBContext.Execute(sql, null);
+
+                        sql = $"CREATE TABLE IF NOT EXISTS tool_parse_logs_{table_suffix} LIKE tool_parse_logs_template;";
+                        DBContext.Execute(sql, null);
+                        sql = $"CREATE TABLE IF NOT EXISTS tk_dp_logs_{table_suffix} LIKE tk_dp_logs;";
+                        DBContext.Execute(sql, null);
+                        sql = $"CREATE TABLE IF NOT EXISTS pdd_dp_logs_{table_suffix} LIKE pdd_dp_logs;";
+                        DBContext.Execute(sql, null);
+                        sql = $"CREATE TABLE IF NOT EXISTS jd_dp_logs_{table_suffix} LIKE jd_dp_logs;";
+                        DBContext.Execute(sql, null);
+
+                    }
+                    else
+                    {
+                        string table_suffix = DateTime.Now.AddDays(i).ToString("yyyyMMdd");
+                        string sql = $"CREATE TABLE IF NOT EXISTS tk_cps_logs_{table_suffix} LIKE tk_cps_logs;";
+                        DBContext.Execute(sql, null);
+                        sql = $"CREATE TABLE IF NOT EXISTS tk_activity_logs_{table_suffix} LIKE tk_activity_logs;";
+                        DBContext.Execute(sql, null);
+                    }
+                }
+                TkLogCore.save_dailys_log = true;
+                RedisHelper.Set("turn:save_dailys_log", 1, 86400);
+            }
+            catch (Exception ex)
+            {
+                string message = $"【创建数据标错误】\n{ex.Message}\n{ex.StackTrace}";
+                NotifyCore.Notify(new NifyMessage
+                {
+                    message = message,
+                    priority = NifyMessagePriority.high,
+                    tags = ["red_circle"]
+                });
+            }
+            return new APIResult(new { success = true, message = "ok" });
+        }
+
+        //[HttpGet]
+
+        //public async Task<ActionResult> ApiCallStatistics(int intervalDay = 1)
+        //{
+        //    try
+        //    {
+        //        AlimamaPlus.ApiCallStatistics(intervalDay, TkChannelEnum.tb);
+        //        AlimamaPlus.ApiCallStatistics(intervalDay, TkChannelEnum.tb, "parse_");
+        //        AlimamaPlus.ApiCallStatistics(intervalDay, TkChannelEnum.tb, "coupon_");
+
+        //        AlimamaPlus.ApiCallStatistics(intervalDay, TkChannelEnum.wemeet, "parse_");
+        //        AlimamaPlus.ApiCallStatistics(intervalDay, TkChannelEnum.bdpan, "parse_");
+
+        //        AlimamaPlus.ApiCallStatistics(intervalDay, TkChannelEnum.dy, "parse_");
+        //        AlimamaPlus.ApiCallStatistics(intervalDay, TkChannelEnum.jd, "parse_");
+
+        //    }
+        //    catch (Exception ex)
+        //    {
+        //        string message = $"【报表接口异常】ApiCallStatistics\n{ex.Message}\n{ex.StackTrace}";
+        //        NotifyCore.Notify(new NifyMessage
+        //        {
+        //            message = message,
+        //            priority = NifyMessagePriority.high,
+        //            tags = ["red_circle"]
+        //        });
+        //    }
+        //    return new APIResult(new { success = true, message = "ok" });
+        //}
+
+
+        [HttpGet]
+        public async Task<ActionResult> GetDrawBalance()
+        {
+            var list = await TkPoolCore.ListAsync();
+            if (list == null) return new APIResult(new { success = false, message = "没有有效账号", });
+            string message = string.Empty;
+            foreach (var account in list)
+            {
+                if (!account.enable_sync_order) continue;
+                try
+                {
+                    var alimama = new AlimamaPlus(account);
+                    alimama.GetDrawBalance();
+                }
+                catch (Exception ex)
+                {
+                    message = $"【报表接口异常】GetDrawBalance\n{ex.Message}\n{ex.StackTrace}";
+                    NotifyCore.Notify(new NifyMessage
+                    {
+                        message = message,
+                        priority = NifyMessagePriority.high,
+                        tags = ["red_circle"]
+                    });
+                    continue;
+                }
+            }
+            return new APIResult(new { success = true, message = "ok" });
+        }
+
+        [HttpGet]
+        public async Task<ActionResult> LastFastReport(int days = 0)
+        {
+            string lockKey = "lock:LastFastReport";
+
+            if (!RedisHelper.Set(lockKey, "1", 300))
+            {
+                string message = "【LastFastReport】任务正在执行中,跳过本次调用";
+                _ = new LoggerLibrary("task_lock", "skip")
+                    .Info(message)
+                    .SaveAsync();
+                return new APIResult(new { success = false, message = "任务正在执行中" });
+            }
+
+            var startTimestamp = DateTime.Now;
+            try
+            {
+                string message = string.Empty;
+                var list = await TkPoolCore.ListAsync();
+                foreach (var account in list)
+                {
+#if DEBUG
+                    if (account.id != 113) continue;
+#endif
+                    if (!account.enable_sync_order) continue;
+                    try
+                    {
+                        var alimama = new AlimamaPlus(account);
+                        await alimama.SaveReportOverview(days);
+                    }
+                    catch (Exception ex)
+                    {
+                        message = $"【报表接口异常】LastFastReport\n{ex.Message}\n{ex.StackTrace}";
+                        NotifyCore.Notify(new NifyMessage
+                        {
+                            message = message,
+                            priority = NifyMessagePriority.high,
+                            tags = ["red_circle"]
+                        });
+                        continue;
+                    }
+                }
+
+                await EndPointCore.NotifyReload(true);
+                return new APIResult(new { success = true, message = "ok" });
+            }
+            catch (Exception ex)
+            {
+                string errorMessage = $"【LastFastReport】执行异常: {ex.Message}";
+                _ = new LoggerLibrary("task_error", "LastFastReport")
+                    .Info(errorMessage, ex.StackTrace)
+                    .SaveAsync();
+
+                NotifyCore.Notify(new NifyMessage
+                {
+                    message = errorMessage,
+                    priority = NifyMessagePriority.high,
+                    tags = ["red_circle"]
+                });
+                return new APIResult(new { success = false, message = errorMessage });
+            }
+            finally
+            {
+                var executionTime = DateTime.Now - startTimestamp;
+                string logMessage = $"【LastFastReport】执行完成,耗时: {executionTime.TotalSeconds:F2}秒";
+                _ = new LoggerLibrary("task_performance", "LastFastReport")
+                    .Info(logMessage)
+                    .SaveAsync();
+                RedisHelper.Del(lockKey);
+            }
+        }
+
+
+        [HttpGet]
+        public async Task<ActionResult> FastReport(string ignore = "")
+        {
+            string lockKey = "lock:FastReport";
+
+            if (!RedisHelper.Set(lockKey, "1", 300))
+            {
+                string message = "【FastReport】任务正在执行中,跳过本次调用";
+                _ = new LoggerLibrary("task_lock", "skip")
+                    .Info(message)
+                    .SaveAsync();
+                return new APIResult(new { success = false, message = "任务正在执行中" });
+            }
+
+            var startTimestamp = DateTime.Now;
+            try
+            {
+                string message = string.Empty;
+
+
+                var list = await TkPoolCore.ListAsync();
+                if (!ignore.Contains("tb") && list != null)
+                {
+                    foreach (var account in list)
+                    {
+#if DEBUG
+                        if (account.id != 113) continue;
+#endif
+                        try
+                        {
+                            account.current_daily_calls = await RiskControlCore.GetAllNodesCallsAsync(TkChannelEnum.tb, account.id, DateTime.Now.ToString("yyyyMMdd"));
+                            account.current_hourly_calls = await RiskControlCore.GetAllNodesCallsAsync(TkChannelEnum.tb, account.id, DateTime.Now.ToString("yyyyMMddHH"));
+
+                            new DBContext.Table("tk_pool")
+                                .Add("current_hourly_calls", account.current_hourly_calls)
+                                .Add("current_daily_calls", account.current_daily_calls)
+                                .Where("id=@id", new { account.id })
+                                .Update();
+                        }
+                        catch (Exception ex)
+                        {
+                            message = $"【TB计数异常】SaveCalls\n{ex.Message}\n{ex.StackTrace}";
+                            NotifyCore.Notify(new NifyMessage
+                            {
+                                message = message,
+                                priority = NifyMessagePriority.high,
+                                tags = ["red_circle"]
+                            });
+                            continue;
+                        }
+
+                        if (!account.enable_sync_order) continue;
+                        try
+                        {
+                            var alimama = new AlimamaPlus(account);
+                            await alimama.SaveReportOverview();
+                        }
+                        catch (Exception ex)
+                        {
+                            message = $"【报表接口异常】FastReport\n{ex.Message}\n{ex.StackTrace}";
+                            NotifyCore.Notify(new NifyMessage
+                            {
+                                message = message,
+                                priority = NifyMessagePriority.high,
+                                tags = ["red_circle"]
+                            });
+                            continue;
+                        }
+                    }
+                }
+
+                string filter = "";
+#if DEBUG
+                filter = "id IN (44)";
+#endif
+                var jd_list = new DBContext.Table("jd_pool").Where(filter, null).Select<JdPoolDTO>();
+
+                if (!ignore.Contains("jd") && jd_list != null)
+                {
+                    bool any_jd_changed = false;
+                    foreach (var account in jd_list)
+                    {
+                        bool changed = false;
+                        try
+                        {
+                            account.current_daily_calls = await RiskControlCore.GetAllNodesCallsAsync(TkChannelEnum.jd, account.id, DateTime.Now.ToString("yyyyMMdd"));
+                            account.current_hourly_calls = await RiskControlCore.GetAllNodesCallsAsync(TkChannelEnum.jd, account.id, DateTime.Now.ToString("yyyyMMddHH"));
+                            changed = true;
+                            any_jd_changed = true;
+                        }
+                        catch (Exception ex)
+                        {
+                            message = $"【JD计数异常】SaveCalls\n{ex.Message}\n{ex.StackTrace}";
+                            NotifyCore.Notify(new NifyMessage
+                            {
+                                message = message,
+                                priority = NifyMessagePriority.high,
+                                tags = ["red_circle"]
+                            });
+                            continue;
+                        }
+                        if (!account.status) continue;
+
+                        string sleepLockKey = $"lock:exception:sleep:jd_{account.id}";
+                        if (!string.IsNullOrEmpty(RedisHelper.Get(sleepLockKey))) continue;
+
+                        if (changed) JdPoolCore.Update(account);
+                    }
+                    if (any_jd_changed) JdPoolCore.Refresh();
+                }
+
+
+                var pdd_list = new DBContext.Table("pdd_pool").Where(filter, null).Select<PddPoolDTO>();
+                if (!ignore.Contains("pdd") && pdd_list != null)
+                {
+                    bool any_pdd_changed = false;
+                    foreach (var account in pdd_list)
+                    {
+                        bool changed = false;
+                        try
+                        {
+                            account.current_daily_calls = await RiskControlCore.GetAllNodesCallsAsync(TkChannelEnum.pdd, account.id, DateTime.Now.ToString("yyyyMMdd"));
+                            account.current_hourly_calls = await RiskControlCore.GetAllNodesCallsAsync(TkChannelEnum.pdd, account.id, DateTime.Now.ToString("yyyyMMddHH"));
+                            changed = true;
+                            any_pdd_changed = true;
+                        }
+                        catch (Exception ex)
+                        {
+                            message = $"【Pdd计数异常】SaveCalls\n{ex.Message}\n{ex.StackTrace}";
+                            NotifyCore.Notify(new NifyMessage
+                            {
+                                message = message,
+                                priority = NifyMessagePriority.high,
+                                tags = ["red_circle"]
+                            });
+                            continue;
+                        }
+                        if (changed) PddPoolCore.Update(account);
+                    }
+                    if (any_pdd_changed) PddPoolCore.Refresh();
+                }
+
+
+
+                var cps_list = new DBContext.Table("cps_links")
+                    .Where("status=@status", new { status = 1 })
+                    .Select<CpsLinksDTO>();
+
+                if (!ignore.Contains("cps") && cps_list != null)
+                {
+                    bool any_cps_changed = false;
+                    foreach (var link in cps_list)
+                    {
+                        bool changed = false;
+                        try
+                        {
+                            link.current_daily_calls = await RiskControlCore.GetAllNodesCallsAsync(TkChannelEnum.cps, link.id, DateTime.Now.ToString("yyyyMMdd"));
+                            link.current_hourly_calls = await RiskControlCore.GetAllNodesCallsAsync(TkChannelEnum.cps, link.id, DateTime.Now.ToString("yyyyMMddHH"));
+                            changed = true;
+                            any_cps_changed = true;
+                        }
+                        catch (Exception ex)
+                        {
+                            message = $"【Cps计数异常】SaveCalls\n{ex.Message}\n{ex.StackTrace}";
+                            NotifyCore.Notify(new NifyMessage
+                            {
+                                message = message,
+                                priority = NifyMessagePriority.high,
+                                tags = ["red_circle"]
+                            });
+                            continue;
+                        }
+                        if (changed) CpsPoolCore.Update(link);
+                    }
+                    if (any_cps_changed) CpsPoolCore.Refresh();
+                }
+                await EndPointCore.NotifyReload(true);
+                return new APIResult(new { success = true, message = "ok" });
+            }
+            catch (Exception ex)
+            {
+                string errorMessage = $"【FastReport】执行异常: {ex.Message}";
+                _ = new LoggerLibrary("task_error", "FastReport")
+                    .Info(errorMessage, ex.StackTrace)
+                    .SaveAsync();
+
+                NotifyCore.Notify(new NifyMessage
+                {
+                    message = errorMessage,
+                    priority = NifyMessagePriority.high,
+                    tags = ["red_circle"]
+                });
+                return new APIResult(new { success = false, message = errorMessage });
+            }
+            finally
+            {
+                var executionTime = DateTime.Now - startTimestamp;
+                string logMessage = $"【FastReport】执行完成,耗时: {executionTime.TotalSeconds:F2}秒";
+                _ = new LoggerLibrary("task_performance", "FastReport")
+                    .Info(logMessage)
+                    .SaveAsync();
+
+                RedisHelper.Del(lockKey);
+            }
+        }
+
+
+
+        [HttpGet]
+        public async Task<ActionResult> jdQuerySpreadEffectData(string ignore = "")
+        {
+            string message = string.Empty;
+
+
+            string filter = "";
+#if DEBUG
+            filter = "id IN (37, 15)";
+#endif
+            var jd_list = new DBContext.Table("jd_pool").Where(filter, null).Select<JdPoolDTO>();
+
+            if (!ignore.Contains("jd") && jd_list != null)
+            {
+                bool any_jd_changed = false;
+                foreach (var account in jd_list)
+                {
+                    bool changed = false;
+                    if (!account.status) continue;
+                    if (!account.enable_sync_report) continue;
+
+                    string lockKey = $"lock:exception:sleep:jd_{account.id}";
+                    if (!string.IsNullOrEmpty(RedisHelper.Get(lockKey))) continue;
+
+                    if (!string.IsNullOrEmpty(account.union_cookies))
+                    {
+                        try
+                        {
+                            var plus = new JdUnionPlus(account);
+                            var data = await plus.queryTodaySpreadEffectData();
+                            account.today_clickNum = data.clickNum;
+                            account.today_cosFee = data.cosFee;
+                            account.today_cosPrice = data.cosPrice;
+                            account.today_finishCosFee = data.finishCosFee;
+                            account.today_finishCosPrice = data.finishCosPrice;
+                            account.today_finishOrderNum = data.finishOrderNum;
+                            account.today_orderNum = data.orderNum;
+
+                            changed = true;
+                            any_jd_changed = true;
+                        }
+                        catch (Exception ex)
+                        {
+                            if (ex.Message.Contains("no login"))
+                            {
+                                account.status = false;
+                                _ = JdPoolCore.DisabledAsync(account.id, account.name, ex.Message);
+                            }
+                            else
+                            {
+                                RedisHelper.Set(lockKey, 1, 3600);
+                            }
+                            message = $"【JD报表接口异常】{account.id}:{account.name}\tqueryTodaySpreadEffectData\n{ex.Message}\n{ex.StackTrace}";
+                            NotifyCore.Notify(message);
+                            continue;
+                        }
+                    }
+                    if (changed) JdPoolCore.Update(account);
+                }
+                if (any_jd_changed) JdPoolCore.Refresh();
+            }
+            return new APIResult(new { success = true, message = "ok" });
+        }
+
+
+
+        [HttpGet]
+        public async Task<ActionResult> SaveReport()
+        {
+            string message = string.Empty;
+
+            var list = await TkPoolCore.ListAsync();
+            if (list != null)
+            {
+                foreach (var account in list)
+                {
+                    if (!account.enable_sync_order) continue;
+#if DEBUG
+                    if (account.id != 5) continue;
+#endif
+                    if (account.is_hide) continue;
+                    try
+                    {
+                        var alimama = new AlimamaPlus(account);
+                        alimama.SaveReportHourtrend(DateTime.Now.AddDays(-1));
+                        alimama.SaveReportHourtrend(DateTime.Now);
+
+                        if (DateTime.Now.Hour >= 12 && DateTime.Now.Hour < 16)
+                        {
+                            DateTime date = DateTime.Now.AddDays(-1);
+                            await alimama.FixReport(date);
+                        }
+                    }
+                    catch (Exception ex)
+                    {
+                        message = $"【报表接口异常】SaveReport\n{ex.Message}\n{ex.StackTrace}";
+                        NotifyCore.Notify(new NifyMessage
+                        {
+                            message = message,
+                            priority = NifyMessagePriority.high,
+                            tags = ["red_circle"]
+                        });
+                        continue;
+                    }
+                }
+            }
+            return new APIResult(new { success = true, message = "ok" });
+        }
+
+        [HttpGet]
+        public async Task<ActionResult> JdSaveReport(int id = 0)
+        {
+            string message = string.Empty;
+            string filter = string.Empty;
+            if (id > 0) filter = "id=@id";
+            var jd_list = new DBContext.Table("jd_pool").Where(filter, new { id }).Select<JdPoolDTO>();
+            if (jd_list != null)
+            {
+                DateTime startDate = DateTime.Now.AddDays(-89);
+                DateTime endDate = DateTime.Now.AddDays(-1);
+                foreach (var account in jd_list)
+                {
+                    if (!account.status) continue;
+#if DEBUG
+                    startDate = DateTime.Parse("2024-07-31");
+                    endDate = DateTime.Parse("2024-07-31");
+                    if (account.id != 15) continue;
+#endif
+                    if (account.is_hide) continue;
+                    if (id == 0 && !account.enable_sync_report) continue;
+
+                    if (!string.IsNullOrEmpty(account.union_cookies))
+                    {
+                        try
+                        {
+                            var plus = new JdUnionPlus(account);
+                            _ = await plus.querySpreadEffectData(startDate, endDate);
+                            _ = await plus.queryEstimateCommList();
+                        }
+                        catch (Exception ex)
+                        {
+                            if (ex.Message.Contains("no login"))
+                            {
+                                _ = JdPoolCore.DisabledAsync(account.id, account.name, ex.Message);
+                            }
+                            message = $"【JD报表接口异常】{account.id}:{account.name}\tquerySpreadEffectData\n{ex.Message}\n{ex.StackTrace}";
+                            NotifyCore.Notify(message);
+                            continue;
+                        }
+                    }
+                }
+            }
+            return new APIResult(new { success = true, message = "ok" });
+        }
+        [HttpGet]
+        public async Task<ActionResult> PddSaveReport(int days = -1)
+        {
+            string message = string.Empty;
+
+            var pdd_list = new DBContext.Table("pdd_pool").Where("cookie_status=1", new { }).Select<PddPoolDTO>();
+            if (pdd_list != null)
+            {
+                DateTime startDate = DateTime.Now.AddDays(days);
+                DateTime endDate = DateTime.Now;
+                foreach (var account in pdd_list)
+                {
+                    if (account.is_hide) continue;
+                    if (!account.enable_sync_revenue) continue;
+
+                    if (!string.IsNullOrEmpty(account.cookies))
+                    {
+                        try
+                        {
+                            var plus = new PddUnionPlus(account);
+                            await plus.queryEstimateRevenueList(startDate, endDate);
+                        }
+                        catch (Exception ex)
+                        {
+                            if (ex.Message.Contains("43001:会话已过期"))
+                            {
+                                PddPoolCore.CookieDisabled(account.id);
+                            }
+                            message = $"【PDD报表接口异常】{account.id}:{account.name}\tqueryEstimateRevenueList\n{ex.Message}\n{ex.StackTrace}";
+                            NotifyCore.Notify(new NifyMessage
+                            {
+                                message = message,
+                                priority = NifyMessagePriority.high,
+                                tags = ["red_circle"]
+                            });
+                            continue;
+                        }
+                    }
+                }
+            }
+            //更新前一天的pdd统计表
+            await AlimamaPlus.ApiCallStatisticsAsync(1, 1, TkChannelEnum.pdd);
+            return new APIResult(new { success = true, message = "ok" });
+        }
+
+
+        [HttpGet]
+        public async Task<ActionResult> FixReport(int id, string dt)
+        {
+            if (!DateTime.TryParse(dt, out DateTime date))
+            {
+                return new APIResult(new { success = false, message = "日期错误", });
+            }
+
+            var list = await TkPoolCore.ListAsync();
+            if (list == null) return new APIResult(new { success = false, message = "没有有效账号", });
+            string message = string.Empty;
+            foreach (var account in list)
+            {
+                if (id != account.id) continue;
+                if (account.is_hide) continue;
+                try
+                {
+                    var alimama = new AlimamaPlus(account);
+
+                    while (date.Date < DateTime.Now.Date)
+                    {
+                        await alimama.FixReport(date);
+                        date = date.AddDays(1);
+                    }
+                }
+                catch (Exception ex)
+                {
+                    message = $"【报表接口异常】SaveReport\n{ex.Message}\n{ex.StackTrace}";
+                    NotifyCore.Notify(new NifyMessage
+                    {
+                        message = message,
+                        priority = NifyMessagePriority.high,
+                        tags = ["red_circle"]
+                    });
+                    continue;
+                }
+            }
+            return new APIResult(new { success = true, message = "ok" });
+        }
+
+        [HttpGet]
+        public ActionResult RenewCookie(string name)
+        {
+            var account = new DBContext.Table("tk_pool").Get<TkPoolDTO>("name=@name", new { name });
+
+            if (account == null) return new APIResult(new { success = false, message = "没有有效账号", });
+            var alimama = new AlimamaPlus(account);
+
+            (bool success, string message) = alimama.RenewCookie();
+            return new APIResult(new { success, message });
+        }
+
+
+        [HttpGet]
+        public ActionResult Reload()
+        {
+            RiskControlCore.Refresh();
+            TkConfigCore.Refresh();
+            EndPointCore.Refresh();
+            ProxyNodesCore.Refresh();
+            TkPoolCore.Refresh();
+            OtherApiPoolCore.Refresh();
+            TkEndpointCore.Refresh();
+            TkEndpointManager.Refresh();
+
+
+            ApiAccountCore.Refresh();
+            DataokeCore.Refresh();
+            JdPoolCore.Refresh();
+            PddPoolCore.Refresh();
+            PangolinPoolCore.Refresh();
+            ReduPoolCore.Refresh();
+
+            //ElePoolCore.Refresh();
+            //MeituanPoolCore.Refresh();
+            CpsPoolCore.Refresh();
+            DeeplinkParseRuleCore.Refresh();
+
+            OverrideRuleCore.Refresh();
+            return new APIResult(new
+            {
+                success = true,
+                message = "ok",
+            });
+        }
+
+        [HttpGet]
+        public ActionResult ReloadAccount()
+        {
+            RiskControlCore.Refresh();
+            EndPointCore.Refresh();
+            ProxyNodesCore.Refresh();
+            TkPoolCore.Refresh();
+            OtherApiPoolCore.Refresh();
+            JdPoolCore.Refresh();
+            PddPoolCore.Refresh();
+
+            //ElePoolCore.Refresh();
+            //MeituanPoolCore.Refresh();
+            CpsPoolCore.Refresh();
+
+            OverrideRuleCore.Refresh();
+            return new APIResult(new
+            {
+                success = true,
+                message = "ok",
+            });
+        }
+
+
+
+        [HttpGet]
+        public async Task<ActionResult> CheckDeepUrl(int accountid)
+        {
+            var postContent = "88✔7Fi43dJwWpV£ https://m.tb.cn/h.g92hfR4JLgn7yvG  MF7997 我分享给你了一个超赞的内容,快来看看吧";
+            var channel = "tb";
+            var ip = "127.0.0.1";
+            var oaid = "test-oaid";
+
+            bool success = false;
+            string message;
+
+            //============================== 放弃转链-没有匹配账号 ==============================
+            TkPoolDTO? account = await TkPoolCore.GetOneAsync(accountid);
+            if (account == null)
+            {
+                return new APIResult(new
+                {
+                    success = false,
+                    message = "没有匹配账号",
+                });
+            }
+            var alimama = new AlimamaPlus(account);
+
+            TkDataDTO result = new TkDataDTO();
+            string url = AlimamaPlus.GetLink(postContent);
+            if (string.IsNullOrEmpty(url))
+            {
+                return new APIResult(new
+                {
+                    success = false,
+                    message = "无效URL",
+                });
+            }
+            WebProxy proxy = ProxyNodesCore.RandomOne(account.nodeName);
+            (success, string content) = await alimama.GetDesiredUrlAsync(proxy, url);
+            if (content.Contains("霸下通用 web 页面-验证码"))
+            {
+                message = "霸下验证码";
+            }
+            else
+            {
+                message = content;
+            }
+            return new APIResult(new
+            {
+                success = true,
+                message,
+            });
+        }
+
+
+    }
 }

+ 508 - 508
molilian.api/Controllers/public/TestController.cs

@@ -1,509 +1,509 @@
-using molilian.core;
-using dodohold.core;
-using Microsoft.AspNetCore.Mvc;
-using Org.BouncyCastle.Ocsp;
-using System.Text.Json;
-using System.Runtime.InteropServices;
-using System.Net;
-using System.Threading;
-using Microsoft.AspNetCore.Mvc.RazorPages;
-using TencentCloud.Soe.V20180724.Models;
-using static dodohold.core.ZTOExpress.CreateOrderArgs;
-using static Spire.Xls.Core.Spreadsheet.HTMLOptions;
-using System.Net.Http.Json;
-using System.Text.RegularExpressions;
-using System.Text;
-using TencentCloud.Oceanus.V20190422.Models;
-using YunhuiKit;
-using TencentCloud.Cdn.V20180606.Models;
-
-namespace molilian.api.Controllers
-{
-    [ApiController]
-    [Route("[controller]/[action]")]
-    public class TestController : ControllerBase
-    {
-
-        protected IHttpContextAccessor _accessor;
-        public TestController(IHttpContextAccessor accessor)
-        {
-            _accessor = accessor;
-        }
-
-
-        [HttpGet]
-        public async Task<ActionResult> ecs_list_test()
-        {
-            var list = AliyunPoolCore.EcsList();
-            return new APIResult(new
-            {
-                success = true,
-                msg = "ok",
-                list
-            });
-        }
-         
-
-        [HttpGet]
-        public async Task<ActionResult> testReconnectionRedis()
-        {
-            string cacheKey = "test";
-            RedisKit.SetAsync(cacheKey, 1, 3600);
-            string val = await RedisKit.GetAsync<string>(cacheKey);
-            return new APIResult(new { success = "ok", val });
-
-        }
-
-        [HttpGet]
-        public async Task<ActionResult> ChangePublicIpByName(string nodeName)
-        {
-
-            var proxy_node = new DBContext.Table("proxy_nodes").Get<dynamic>("nodeName=@nodeName", new { nodeName });
-            if (proxy_node == null) return new APIResult(new { success = false, msg = "没有匹配的 proxy_nodes 记录" });
-
-            int aliyun_id = proxy_node.aliyun_id;
-            string proxy_server = proxy_node.server;
-
-            var account = new DBContext.Table("aliyun_pool").Get<AliyunPoolDTO>(aliyun_id);
-            if (account == null) return new APIResult(new { success = false, msg = "没有匹配的 aliyun_pool 记录" });
-
-
-            var uri = new Uri(proxy_server);
-            string privateIp = uri.Host;
-            AliyunCore core = new AliyunCore(account);
-            var success = await core.ChangePublicIpAsync(privateIp);
-            return new APIResult(new { success = "ok" });
-        }
-
-        [HttpGet]
-        public async Task<ActionResult> ChangePublicIp(int id)
-        {
-            id = id >= 20000 ? id - 20000 : id;
-
-            var proxy_node = new DBContext.Table("proxy_nodes").Get<dynamic>(id);
-            if (proxy_node == null) return new APIResult(new { success = false, msg = "没有匹配的 proxy_nodes 记录" });
-
-            int aliyun_id = proxy_node.aliyun_id;
-            string proxy_server = proxy_node.server;
-
-            var account = new DBContext.Table("aliyun_pool").Get<AliyunPoolDTO>(aliyun_id);
-            if (account == null) return new APIResult(new { success = false, msg = "没有匹配的 aliyun_pool 记录" });
-
-
-
-            var uri = new Uri(proxy_server);
-            string privateIp = uri.Host;
-            AliyunCore core = new AliyunCore(account);
-            var success = await core.ChangePublicIpAsync(privateIp);
-            return new APIResult(new { success = "ok" });
-        }
-
-
-
-        [HttpGet]
-        //第一部 先加一个网卡,
-        public async Task<ActionResult> CreateNetworkInterface(int id, string ecsid)
-        {
-            AliyunCore core = new AliyunCore(id);
-            //创建弹性网卡并绑定公网IP
-            var success = core.EcsCreateNetworkInterface("cn-beijing", ecsid);
-
-            return new APIResult(new { success });
-        }
-
-
-        [HttpGet]
-        //第二部 给网卡绑定很多个辅助ip
-        public async Task<ActionResult> EcsAssignPrivateIpAddresses(int id, string ecsid, int count, bool isSecondary = true)
-        {
-            AliyunCore core = new AliyunCore(id);
-            //创建弹性网卡并绑定公网IP
-            var success = core.EcsAssignPrivateIpAddresses("cn-beijing", ecsid, count, !isSecondary);
-            return new APIResult(new { success });
-        }
-
-        [HttpGet]
-        public async Task<ActionResult> QueryAccountBalance()
-        {
-            AliyunPlus plus = new AliyunPlus("LTAI5tQbkTjtULQcrWGaw2VJ", "UIkkolVVEddooOKOIUByCqymZkK6ZA");
-            var response = plus.QueryAccountBalance();
-            var balance = response.Body.Data.AvailableAmount;
-            return new APIResult(new { response });
-        }
-
-        //[HttpGet]
-        //public async Task<ActionResult> test(string table = "tk_parse_logs_shop", int count = 100)
-        //{
-        //    //table = "tk_parse_logs_shop";
-        //    //table = "tk_parse_logs_live";
-        //    //table = "tk_parse_logs_video";
-
-        //    string filter = "success=0 AND message='放弃转链' AND reason='非标准链接'";
-
-        //    for (int i = 0; i < 100; i++)
-        //    {
-        //        try
-        //        {
-        //            var list = new DBContext.Table(table)
-        //                .Where(filter, new { })
-        //                .Limit(count).Select<TkDataDTO>();
-        //            if (!list.Any())
-        //            {
-
-        //                return new APIResult(new
-        //                {
-        //                    success = false,
-        //                    message = "所有任务完成"
-        //                });
-
-        //            }
-        //            foreach (var item in list)
-        //            {
-        //                TkPoolDTO? account = TkPoolCore.GetOne(TkPoolCore.TkAction.parse);
-        //                if (account == null)
-        //                {
-        //                    return new APIResult(new
-        //                    {
-        //                        success = false,
-        //                        message = "没有工作账号"
-        //                    });
-        //                }
-
-        //                var result = item.Convert2Json().Convert2Object<TkDataDTO>();
-
-        //                result.accountId = account.id;
-        //                result.accountName = account.company;
-
-        //                var alimama = new AlimamaPlus(account);
-
-
-        //                int timeout = alimama._config.rt_max;
-        //                using var cts = new CancellationTokenSource();
-        //                cts.CancelAfter(timeout);
-
-        //                result = await alimama.UnionParseAsync(item.content, result);
-        //                string reason = "非标准链接".Equals(result.reason) ? "非标准链接ok" : result.reason;
-
-        //                new DBContext.Table(table)
-        //                    .Add("linkType", (int)result.link_type)
-        //                    .Add("rawContent", result.rawContent)
-        //                    .Add("success", result.success)
-        //                    .Add("message", result.message)
-        //                    .Add("reason", reason)
-        //                    .Add("content", result.content)
-        //                    .Add("itemId", result.itemId)
-        //                    .Add("itemName", result.itemName)
-        //                    .Add("pic", result.pic)
-        //                    .Add("couponAmount", result.couponAmount)
-        //                    .Add("promotionPrice", result.promotionPrice)
-        //                    .Add("taoToken", result.taoToken)
-        //                    .Add("shortLinkurl", result.shortLinkurl)
-        //                    .Add("deeplink_url", result.deeplink_url)
-        //                    .Add("num_iid", result.num_iid)
-        //                    .Add("elapsedTime", result.elapsedTime)
-        //                    .Add("elapsedTime2", result.elapsedTime2)
-        //                    .Add("elapsedTime3", result.elapsedTime3)
-        //                    .Add("subCode", result.subCode)
-        //                    .Where("id=@id", new { item.id })
-        //                    .Update();
-        //            }
-        //        }
-        //        catch
-        //        {
-        //        }
-        //    }
-
-        //    return new APIResult(new
-        //    {
-        //        success = true,
-        //        message = "ok"
-        //    });
-
-
-        //}
-
-
-
-        [HttpGet]
-        public async Task<ActionResult> redu_douyin(string command)
-        {
-            ReduPlus plus = new ReduPlus("", "", "");
-            var result = await plus.DouyinParse(command);
-            return Content(result.Convert2Json());
-        }
-
-        [HttpGet]
-        public async Task<ActionResult> redu_kuaishou(string command)
-        {
-
-            ReduPlus plus = new ReduPlus("", "", "");
-            var result = await plus.KuaishouParse(command);
-            return Content(result.Convert2Json());
-        }
-
-
-
-        [HttpPost]
-        public async Task<ActionResult> test2([FromForm] string jsonContent, [FromForm] string testText)
-        {
-
-            // 用于存储输出结果的StringBuilder
-            StringBuilder outputBuilder = new StringBuilder();
-
-            // 解析JSON数据
-            JsonDocument jsonDoc = JsonDocument.Parse(jsonContent);
-
-            // 获取根元素
-            JsonElement root = jsonDoc.RootElement;
-
-            // 遍历规则
-            foreach (JsonElement ruleSet in root.EnumerateArray())
-            {
-
-                string platformType = ruleSet.Read("platformType", string.Empty);
-                string supplier = ruleSet.Read("supplier", string.Empty);
-                outputBuilder.AppendLine($"平台类型: {platformType}, 供应商: {supplier}");
-
-                JsonElement pwdRules = ruleSet.GetProperty("pwdRules");
-                int idx = 0;
-                foreach (JsonElement patternElement in pwdRules.EnumerateArray())
-                {
-                    string pattern = patternElement.GetString();
-                    try
-                    {
-                        // 使用Regex类来编译正则表达式
-                        Regex compiledPattern = new Regex(pattern);
-                        if (compiledPattern.IsMatch(testText))
-                        {
-                            outputBuilder.AppendLine($"规则 {idx + 1}: 匹配\t{pattern}");
-                        }
-                        else
-                        {
-                            outputBuilder.AppendLine($"规则 {idx + 1}: 不匹配");
-                        }
-                    }
-                    catch (Exception e)
-                    {
-                        outputBuilder.AppendLine($"规则 {idx + 1}: 正则表达式错误 - {e.Message}\t{pattern}");
-                    }
-                    idx++;
-                }
-            }
-
-            outputBuilder.AppendLine("测试完成");
-
-            return Content(outputBuilder.ToString());
-        }
-
-
-        [HttpGet]
-        public async Task<ActionResult> ip(string ip)
-        {
-
-            string result = IP2RegionPlus.Search(ip);
-
-            return new APIResult(new
-            {
-                success = true,
-                message = result
-            });
-        }
-
-
-        [HttpPost]
-        public async Task<ActionResult> testreg([FromBody] JsonElement form)
-        {
-            var content = form.Read("s", string.Empty);
-
-            string shortLinkurl = AlimamaPlus.GetTaobaoLink(content);
-            bool is_tao_token = AlimamaPlus.MatchRegexes(content, []);
-            bool is_other = AlimamaPlus.MatchOtherInfo(content, []);
-
-            return new APIResult(new
-            {
-                shortLinkurl,
-                is_tao_token,
-                is_other
-            });
-
-
-        }
-        //[HttpGet]
-        //public async Task<ActionResult> comparison_tk([FromQuery] int count = 100)
-        //{
-        //    string cacheKey = ":lock_key:comparison_tk_logs";
-        //    int last_id = RedisHelper.Get<int>(cacheKey);
-
-        //    string filter = "id>@last_id";
-        //    var result = new DBContext.Table("comparison_tk_logs")
-        //        .Where(filter, new { last_id })
-        //        .Page(count, 1)
-        //        .Order("ID")
-        //        .PageList<TkDataDTO>(false);
-
-        //    var ip = "127.0.0.1";
-        //    var oaid = "test-comparison_tk_logs";
-        //    foreach (var item in result.List)
-        //    {
-        //        await UnionParseCore.TaobaoParseAsync(item.rawContent, ip, oaid);
-        //        RedisHelper.Set(cacheKey, item.id, 10 * 86400);
-        //    }
-        //    if (result.Count < count)
-        //    {
-        //        cacheKey = ":lock_key:start_comparison_tk";
-        //        RedisHelper.Set(cacheKey, 1, 600);
-        //    }
-        //    return new APIResult(new
-        //    {
-        //        success = true,
-        //        message = "ok"
-        //    });
-        //}
-
-        //[HttpGet]
-        //public async Task<ActionResult> comparison_tk_raw([FromQuery] int count = 100)
-        //{
-        //    string cacheKey = ":lock_key:comparison_tk_logs_raw";
-        //    int last_id = RedisHelper.Get<int>(cacheKey);
-
-        //    string filter = "id>@last_id";
-        //    var result = new DBContext.Table("comparison_tk_logs")
-        //        .Where(filter, new { last_id })
-        //        .Page(count, 1)
-        //        .Order("ID")
-        //        .PageList<TkDataDTO>(false);
-
-        //    var ip = "127.0.0.1";
-        //    var oaid = "test-comparison_tk_logs_raw";
-        //    foreach (var item in result.List)
-        //    {
-        //        await UnionParseCore.TaobaoParseAsync(item.rawContent, ip, oaid);
-        //        RedisHelper.Set(cacheKey, item.id, 10 * 86400);
-        //    }
-        //    if (result.Count < count)
-        //    {
-        //        cacheKey = ":lock_key:start_comparison_tk";
-        //        RedisHelper.Set(cacheKey, 1, 600);
-        //    }
-        //    return new APIResult(new
-        //    {
-        //        success = true,
-        //        message = "ok"
-        //    });
-        //}
-
-
-        [HttpGet]
-        public async Task<ActionResult> testTask()
-        {
-            string url = "https://www.taobao.com";
-            TkDataDTO result = new TkDataDTO();
-            result.message = "init";
-            result.success = true;
-
-
-            int timeout = 1000;
-            using var cts = new CancellationTokenSource();
-            cts.CancelAfter(timeout);
-
-
-            var requestTask = Task.Run(() => AlimamaPlus.testTaskAsync(result, cts.Token), cts.Token);
-
-
-            var delayTask = Task.Delay(timeout, cts.Token);
-            var completedTask = await Task.WhenAny(requestTask, delayTask);
-            if (completedTask == requestTask)
-            {
-                result = await requestTask;
-            }
-            else
-            {
-                cts.Cancel();
-                result.success = false;
-                result.message = "放弃转链";
-                result.reason = "请求超时";
-                result.itemName = "点击打开淘宝APP";
-            }
-
-            return new APIResult(new
-            {
-                success = true,
-                message = "ok",
-                result,
-            });
-        }
-
-        [HttpGet]
-        public async Task<ActionResult> testThread()
-        {
-            // 查看默认的最小和最大线程数
-            ThreadPool.GetMinThreads(out int defaultMinWorker, out int defaultMinIOC);
-            ThreadPool.GetMaxThreads(out int defaultMaxWorker, out int defaultMaxIOC);
-            string tmp = $"Default Min worker threads: {defaultMinWorker}, Min I/O completion threads: {defaultMinIOC}";
-            string tmp2 = $"Default Max worker threads: {defaultMaxWorker}, Max I/O completion threads: {defaultMaxIOC}";
-
-
-
-            // 获取当前线程池中可用的工作线程数和 I/O 完成端口线程数
-            ThreadPool.GetAvailableThreads(out int availableWorkerThreads, out int availableIOCompletionThreads);
-            string tmp3 = $"Current available worker threads: {availableWorkerThreads}, available I/O completion threads: {availableIOCompletionThreads}";
-
-            return new APIResult(new
-            {
-                tmp,
-                tmp2,
-                tmp3,
-            });
-        }
-
-        [HttpPost]
-        public async Task<ActionResult> test1([FromBody] JsonElement form)
-        {
-
-            NotifyCore.Notify(new NifyMessage
-            {
-                message = $"【淘宝联盟:test】cookie 掉线",
-                priority = NifyMessagePriority.high,
-                tags = ["red_circle"]
-            });
-
-
-            return new APIResult(new
-            {
-                success = true,
-                message = "ok"
-            });
-        }
-
-        [HttpGet]
-        public async Task<ActionResult> xxx()
-        {
-            var list = await TkPoolCore.ListAsync();
-            if (list == null) return new APIResult(new { success = false, message = "没有有效账号", });
-            string message = string.Empty;
-            foreach (var account in list)
-            {
-                try
-                {
-                    var alimama = new AlimamaPlus(account);
-                    alimama.RenewCookie();
-                }
-                catch (Exception ex)
-                {
-                    message = $"【cookie续期】xxxx\n{ex.Message}\n{ex.StackTrace}";
-                    NotifyCore.Notify(new NifyMessage
-                    {
-                        message = message,
-                        priority = NifyMessagePriority.high,
-                        tags = ["red_circle"]
-                    });
-                    continue;
-                }
-            }
-            return new APIResult(new { success = true, message = "ok" });
-        }
-
-
-
-    }
+using molilian.core;
+using dodohold.core;
+using Microsoft.AspNetCore.Mvc;
+using Org.BouncyCastle.Ocsp;
+using System.Text.Json;
+using System.Runtime.InteropServices;
+using System.Net;
+using System.Threading;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+using TencentCloud.Soe.V20180724.Models;
+using static dodohold.core.ZTOExpress.CreateOrderArgs;
+using static Spire.Xls.Core.Spreadsheet.HTMLOptions;
+using System.Net.Http.Json;
+using System.Text.RegularExpressions;
+using System.Text;
+using TencentCloud.Oceanus.V20190422.Models;
+using YunhuiKit;
+using TencentCloud.Cdn.V20180606.Models;
+
+namespace molilian.api.Controllers
+{
+    [ApiController]
+    [Route("[controller]/[action]")]
+    public class TestController : ControllerBase
+    {
+
+        protected IHttpContextAccessor _accessor;
+        public TestController(IHttpContextAccessor accessor)
+        {
+            _accessor = accessor;
+        }
+
+
+        [HttpGet]
+        public async Task<ActionResult> ecs_list_test()
+        {
+            var list = AliyunPoolCore.EcsList();
+            return new APIResult(new
+            {
+                success = true,
+                msg = "ok",
+                list
+            });
+        }
+         
+
+        [HttpGet]
+        public async Task<ActionResult> testReconnectionRedis()
+        {
+            string cacheKey = "test";
+            RedisKit.SetAsync(cacheKey, 1, 3600);
+            string val = await RedisKit.GetAsync<string>(cacheKey);
+            return new APIResult(new { success = "ok", val });
+
+        }
+
+        [HttpGet]
+        public async Task<ActionResult> ChangePublicIpByName(string nodeName)
+        {
+
+            var proxy_node = new DBContext.Table("proxy_nodes").Get<dynamic>("nodeName=@nodeName", new { nodeName });
+            if (proxy_node == null) return new APIResult(new { success = false, msg = "没有匹配的 proxy_nodes 记录" });
+
+            int aliyun_id = proxy_node.aliyun_id;
+            string proxy_server = proxy_node.server;
+
+            var account = new DBContext.Table("aliyun_pool").Get<AliyunPoolDTO>(aliyun_id);
+            if (account == null) return new APIResult(new { success = false, msg = "没有匹配的 aliyun_pool 记录" });
+
+
+            var uri = new Uri(proxy_server);
+            string privateIp = uri.Host;
+            AliyunCore core = new AliyunCore(account);
+            var success = await core.ChangePublicIpAsync(privateIp);
+            return new APIResult(new { success = "ok" });
+        }
+
+        [HttpGet]
+        public async Task<ActionResult> ChangePublicIp(int id)
+        {
+            id = id >= 20000 ? id - 20000 : id;
+
+            var proxy_node = new DBContext.Table("proxy_nodes").Get<dynamic>(id);
+            if (proxy_node == null) return new APIResult(new { success = false, msg = "没有匹配的 proxy_nodes 记录" });
+
+            int aliyun_id = proxy_node.aliyun_id;
+            string proxy_server = proxy_node.server;
+
+            var account = new DBContext.Table("aliyun_pool").Get<AliyunPoolDTO>(aliyun_id);
+            if (account == null) return new APIResult(new { success = false, msg = "没有匹配的 aliyun_pool 记录" });
+
+
+
+            var uri = new Uri(proxy_server);
+            string privateIp = uri.Host;
+            AliyunCore core = new AliyunCore(account);
+            var success = await core.ChangePublicIpAsync(privateIp);
+            return new APIResult(new { success = "ok" });
+        }
+
+
+
+        [HttpGet]
+        //第一部 先加一个网卡,
+        public async Task<ActionResult> CreateNetworkInterface(int id, string ecsid)
+        {
+            AliyunCore core = new AliyunCore(id);
+            //创建弹性网卡并绑定公网IP
+            var success = core.EcsCreateNetworkInterface("cn-beijing", ecsid);
+
+            return new APIResult(new { success });
+        }
+
+
+        [HttpGet]
+        //第二部 给网卡绑定很多个辅助ip
+        public async Task<ActionResult> EcsAssignPrivateIpAddresses(int id, string ecsid, int count, bool isSecondary = true)
+        {
+            AliyunCore core = new AliyunCore(id);
+            //创建弹性网卡并绑定公网IP
+            var success = core.EcsAssignPrivateIpAddresses("cn-beijing", ecsid, count, !isSecondary);
+            return new APIResult(new { success });
+        }
+
+        [HttpGet]
+        public async Task<ActionResult> QueryAccountBalance()
+        {
+            AliyunPlus plus = new AliyunPlus("LTAI5tQbkTjtULQcrWGaw2VJ", "UIkkolVVEddooOKOIUByCqymZkK6ZA");
+            var response = plus.QueryAccountBalance();
+            var balance = response.Body.Data.AvailableAmount;
+            return new APIResult(new { response });
+        }
+
+        //[HttpGet]
+        //public async Task<ActionResult> test(string table = "tk_parse_logs_shop", int count = 100)
+        //{
+        //    //table = "tk_parse_logs_shop";
+        //    //table = "tk_parse_logs_live";
+        //    //table = "tk_parse_logs_video";
+
+        //    string filter = "success=0 AND message='放弃转链' AND reason='非标准链接'";
+
+        //    for (int i = 0; i < 100; i++)
+        //    {
+        //        try
+        //        {
+        //            var list = new DBContext.Table(table)
+        //                .Where(filter, new { })
+        //                .Limit(count).Select<TkDataDTO>();
+        //            if (!list.Any())
+        //            {
+
+        //                return new APIResult(new
+        //                {
+        //                    success = false,
+        //                    message = "所有任务完成"
+        //                });
+
+        //            }
+        //            foreach (var item in list)
+        //            {
+        //                TkPoolDTO? account = TkPoolCore.GetOne(TkPoolCore.TkAction.parse);
+        //                if (account == null)
+        //                {
+        //                    return new APIResult(new
+        //                    {
+        //                        success = false,
+        //                        message = "没有工作账号"
+        //                    });
+        //                }
+
+        //                var result = item.Convert2Json().Convert2Object<TkDataDTO>();
+
+        //                result.accountId = account.id;
+        //                result.accountName = account.company;
+
+        //                var alimama = new AlimamaPlus(account);
+
+
+        //                int timeout = alimama._config.rt_max;
+        //                using var cts = new CancellationTokenSource();
+        //                cts.CancelAfter(timeout);
+
+        //                result = await alimama.UnionParseAsync(item.content, result);
+        //                string reason = "非标准链接".Equals(result.reason) ? "非标准链接ok" : result.reason;
+
+        //                new DBContext.Table(table)
+        //                    .Add("linkType", (int)result.link_type)
+        //                    .Add("rawContent", result.rawContent)
+        //                    .Add("success", result.success)
+        //                    .Add("message", result.message)
+        //                    .Add("reason", reason)
+        //                    .Add("content", result.content)
+        //                    .Add("itemId", result.itemId)
+        //                    .Add("itemName", result.itemName)
+        //                    .Add("pic", result.pic)
+        //                    .Add("couponAmount", result.couponAmount)
+        //                    .Add("promotionPrice", result.promotionPrice)
+        //                    .Add("taoToken", result.taoToken)
+        //                    .Add("shortLinkurl", result.shortLinkurl)
+        //                    .Add("deeplink_url", result.deeplink_url)
+        //                    .Add("num_iid", result.num_iid)
+        //                    .Add("elapsedTime", result.elapsedTime)
+        //                    .Add("elapsedTime2", result.elapsedTime2)
+        //                    .Add("elapsedTime3", result.elapsedTime3)
+        //                    .Add("subCode", result.subCode)
+        //                    .Where("id=@id", new { item.id })
+        //                    .Update();
+        //            }
+        //        }
+        //        catch
+        //        {
+        //        }
+        //    }
+
+        //    return new APIResult(new
+        //    {
+        //        success = true,
+        //        message = "ok"
+        //    });
+
+
+        //}
+
+
+
+        [HttpGet]
+        public async Task<ActionResult> redu_douyin(string command)
+        {
+            ReduPlus plus = new ReduPlus("", "", "");
+            var result = await plus.DouyinParse(command);
+            return Content(result.Convert2Json());
+        }
+
+        [HttpGet]
+        public async Task<ActionResult> redu_kuaishou(string command)
+        {
+
+            ReduPlus plus = new ReduPlus("", "", "");
+            var result = await plus.KuaishouParse(command);
+            return Content(result.Convert2Json());
+        }
+
+
+
+        [HttpPost]
+        public async Task<ActionResult> test2([FromForm] string jsonContent, [FromForm] string testText)
+        {
+
+            // 用于存储输出结果的StringBuilder
+            StringBuilder outputBuilder = new StringBuilder();
+
+            // 解析JSON数据
+            JsonDocument jsonDoc = JsonDocument.Parse(jsonContent);
+
+            // 获取根元素
+            JsonElement root = jsonDoc.RootElement;
+
+            // 遍历规则
+            foreach (JsonElement ruleSet in root.EnumerateArray())
+            {
+
+                string platformType = ruleSet.Read("platformType", string.Empty);
+                string supplier = ruleSet.Read("supplier", string.Empty);
+                outputBuilder.AppendLine($"平台类型: {platformType}, 供应商: {supplier}");
+
+                JsonElement pwdRules = ruleSet.GetProperty("pwdRules");
+                int idx = 0;
+                foreach (JsonElement patternElement in pwdRules.EnumerateArray())
+                {
+                    string pattern = patternElement.GetString();
+                    try
+                    {
+                        // 使用Regex类来编译正则表达式
+                        Regex compiledPattern = new Regex(pattern);
+                        if (compiledPattern.IsMatch(testText))
+                        {
+                            outputBuilder.AppendLine($"规则 {idx + 1}: 匹配\t{pattern}");
+                        }
+                        else
+                        {
+                            outputBuilder.AppendLine($"规则 {idx + 1}: 不匹配");
+                        }
+                    }
+                    catch (Exception e)
+                    {
+                        outputBuilder.AppendLine($"规则 {idx + 1}: 正则表达式错误 - {e.Message}\t{pattern}");
+                    }
+                    idx++;
+                }
+            }
+
+            outputBuilder.AppendLine("测试完成");
+
+            return Content(outputBuilder.ToString());
+        }
+
+
+        [HttpGet]
+        public async Task<ActionResult> ip(string ip)
+        {
+
+            string result = IP2RegionPlus.Search(ip);
+
+            return new APIResult(new
+            {
+                success = true,
+                message = result
+            });
+        }
+
+
+        [HttpPost]
+        public async Task<ActionResult> testreg([FromBody] JsonElement form)
+        {
+            var content = form.Read("s", string.Empty);
+
+            string shortLinkurl = AlimamaPlus.GetTaobaoLink(content);
+            bool is_tao_token = AlimamaPlus.MatchRegexes(content, []);
+            bool is_other = AlimamaPlus.MatchOtherInfo(content, []);
+
+            return new APIResult(new
+            {
+                shortLinkurl,
+                is_tao_token,
+                is_other
+            });
+
+
+        }
+        //[HttpGet]
+        //public async Task<ActionResult> comparison_tk([FromQuery] int count = 100)
+        //{
+        //    string cacheKey = ":lock_key:comparison_tk_logs";
+        //    int last_id = RedisHelper.Get<int>(cacheKey);
+
+        //    string filter = "id>@last_id";
+        //    var result = new DBContext.Table("comparison_tk_logs")
+        //        .Where(filter, new { last_id })
+        //        .Page(count, 1)
+        //        .Order("ID")
+        //        .PageList<TkDataDTO>(false);
+
+        //    var ip = "127.0.0.1";
+        //    var oaid = "test-comparison_tk_logs";
+        //    foreach (var item in result.List)
+        //    {
+        //        await UnionParseCore.TaobaoParseAsync(item.rawContent, ip, oaid);
+        //        RedisHelper.Set(cacheKey, item.id, 10 * 86400);
+        //    }
+        //    if (result.Count < count)
+        //    {
+        //        cacheKey = ":lock_key:start_comparison_tk";
+        //        RedisHelper.Set(cacheKey, 1, 600);
+        //    }
+        //    return new APIResult(new
+        //    {
+        //        success = true,
+        //        message = "ok"
+        //    });
+        //}
+
+        //[HttpGet]
+        //public async Task<ActionResult> comparison_tk_raw([FromQuery] int count = 100)
+        //{
+        //    string cacheKey = ":lock_key:comparison_tk_logs_raw";
+        //    int last_id = RedisHelper.Get<int>(cacheKey);
+
+        //    string filter = "id>@last_id";
+        //    var result = new DBContext.Table("comparison_tk_logs")
+        //        .Where(filter, new { last_id })
+        //        .Page(count, 1)
+        //        .Order("ID")
+        //        .PageList<TkDataDTO>(false);
+
+        //    var ip = "127.0.0.1";
+        //    var oaid = "test-comparison_tk_logs_raw";
+        //    foreach (var item in result.List)
+        //    {
+        //        await UnionParseCore.TaobaoParseAsync(item.rawContent, ip, oaid);
+        //        RedisHelper.Set(cacheKey, item.id, 10 * 86400);
+        //    }
+        //    if (result.Count < count)
+        //    {
+        //        cacheKey = ":lock_key:start_comparison_tk";
+        //        RedisHelper.Set(cacheKey, 1, 600);
+        //    }
+        //    return new APIResult(new
+        //    {
+        //        success = true,
+        //        message = "ok"
+        //    });
+        //}
+
+
+        [HttpGet]
+        public async Task<ActionResult> testTask()
+        {
+            string url = "https://www.taobao.com";
+            TkDataDTO result = new TkDataDTO();
+            result.message = "init";
+            result.success = true;
+
+
+            int timeout = 1000;
+            using var cts = new CancellationTokenSource();
+            cts.CancelAfter(timeout);
+
+
+            var requestTask = Task.Run(() => AlimamaPlus.testTaskAsync(result, cts.Token), cts.Token);
+
+
+            var delayTask = Task.Delay(timeout, cts.Token);
+            var completedTask = await Task.WhenAny(requestTask, delayTask);
+            if (completedTask == requestTask)
+            {
+                result = await requestTask;
+            }
+            else
+            {
+                cts.Cancel();
+                result.success = false;
+                result.message = "放弃转链";
+                result.reason = "请求超时";
+                result.itemName = "点击打开淘宝APP";
+            }
+
+            return new APIResult(new
+            {
+                success = true,
+                message = "ok",
+                result,
+            });
+        }
+
+        [HttpGet]
+        public async Task<ActionResult> testThread()
+        {
+            // 查看默认的最小和最大线程数
+            ThreadPool.GetMinThreads(out int defaultMinWorker, out int defaultMinIOC);
+            ThreadPool.GetMaxThreads(out int defaultMaxWorker, out int defaultMaxIOC);
+            string tmp = $"Default Min worker threads: {defaultMinWorker}, Min I/O completion threads: {defaultMinIOC}";
+            string tmp2 = $"Default Max worker threads: {defaultMaxWorker}, Max I/O completion threads: {defaultMaxIOC}";
+
+
+
+            // 获取当前线程池中可用的工作线程数和 I/O 完成端口线程数
+            ThreadPool.GetAvailableThreads(out int availableWorkerThreads, out int availableIOCompletionThreads);
+            string tmp3 = $"Current available worker threads: {availableWorkerThreads}, available I/O completion threads: {availableIOCompletionThreads}";
+
+            return new APIResult(new
+            {
+                tmp,
+                tmp2,
+                tmp3,
+            });
+        }
+
+        [HttpPost]
+        public async Task<ActionResult> test1([FromBody] JsonElement form)
+        {
+
+            NotifyCore.Notify(new NifyMessage
+            {
+                message = $"【淘宝联盟:test】cookie 掉线",
+                priority = NifyMessagePriority.high,
+                tags = ["red_circle"]
+            });
+
+
+            return new APIResult(new
+            {
+                success = true,
+                message = "ok"
+            });
+        }
+
+        [HttpGet]
+        public async Task<ActionResult> xxx()
+        {
+            var list = await TkPoolCore.ListAsync();
+            if (list == null) return new APIResult(new { success = false, message = "没有有效账号", });
+            string message = string.Empty;
+            foreach (var account in list)
+            {
+                try
+                {
+                    var alimama = new AlimamaPlus(account);
+                    alimama.RenewCookie();
+                }
+                catch (Exception ex)
+                {
+                    message = $"【cookie续期】xxxx\n{ex.Message}\n{ex.StackTrace}";
+                    NotifyCore.Notify(new NifyMessage
+                    {
+                        message = message,
+                        priority = NifyMessagePriority.high,
+                        tags = ["red_circle"]
+                    });
+                    continue;
+                }
+            }
+            return new APIResult(new { success = true, message = "ok" });
+        }
+
+
+
+    }
 }

+ 101 - 101
molilian.api/Controllers/public/TkActivityController.cs

@@ -1,102 +1,102 @@
-using molilian.core;
-using dodohold.core;
-using Microsoft.AspNetCore.Mvc;
-using Org.BouncyCastle.Ocsp;
-using System.Text.Json;
-using System.Runtime.InteropServices;
-using System.Net;
-using System.Security.Cryptography;
-using Microsoft.AspNetCore.Authentication;
-using TencentCloud.Ecm.V20190719.Models;
-using COSXML.Network;
-using System.Linq;
-using static dodohold.core.ZTOExpress.CreateOrderArgs;
-using MySqlX.XDevAPI;
-using TencentCloud.Ame.V20190916.Models;
-using YunhuiKit;
-
-namespace molilian.api.Controllers
-{
-    /// <summary>
-    /// 淘宝会场活动
-    /// </summary>
-    [ApiController]
-    [Route("[controller]/[action]")]
-    public class TkActivityController : ControllerBase
-    {
-
-        protected IHttpContextAccessor _accessor;
-        public TkActivityController(IHttpContextAccessor accessor)
-        {
-            _accessor = accessor;
-        }
-
-
-
-
-        [HttpPost]
-        public async Task<ActionResult> Parse([FromBody] JsonElement form, [FromQuery] string a = "", [FromQuery] int t = 0, [FromQuery] string sign = "")
-        {
-            //正文
-            var page_id = form.Read("page_id", string.Empty);
-            var item_id = form.Read("item_id", string.Empty);
-            var strategy_id = form.Read("strategy_id", "1");
-            var ip = form.Read("ip", string.Empty);
-            var oaid = form.Read("oaid", string.Empty);
-
-            //验证签名
-            if (string.IsNullOrEmpty(a) || string.IsNullOrEmpty(sign))
-            {
-                return new APIResult(new { success = false, message = "验证错误" }, APIResultCodeEnum.Unauthorized);
-            }
-
-            DateTime time1 = t.Convert2Datetime();
-            if (time1 < DateTime.Now.AddMinutes(-10))
-            {
-                return new APIResult(new { success = false, message = "验证错误2" }, APIResultCodeEnum.Unauthorized);
-            }
-
-            var account = await ApiAccountCore.GetOneAsync(a);
-            if (account == null)
-            {
-                return new APIResult(new { success = false, message = "验证错误3" }, APIResultCodeEnum.Unauthorized);
-            }
-            string app_sign = $"{account.api_secret}@{t}".MD5();
-            if (sign != app_sign)
-            {
-                return new APIResult(new { success = false, message = "验证错误4" }, APIResultCodeEnum.Unauthorized);
-            }
-
-            string prefix = page_id switch
-            {
-                "20150318020021983" or "20150318020010092" or "20150318020019405" => "rec",
-                _ => "top",
-            };
-
-            return await TkActivityCore.ParseAsync(page_id, prefix, item_id, strategy_id, ip, oaid);
-        }
-
-
-        [HttpPost]
-        public async Task<ActionResult> unsafeParse([FromBody] JsonElement form)
-        {
-            var page_id = form.Read("page_id", string.Empty);
-            var item_id = form.Read("item_id", string.Empty);
-            var ip = form.Read("ip", string.Empty);
-            var oaid = form.Read("oaid", string.Empty);
-            var strategy_id = form.Read("strategy_id", string.Empty);
-
-
-            string prefix = page_id switch
-            {
-                "20150318020021983" or "20150318020010092" or "20150318020019405" => "rec",
-                _ => "top",
-            };
-
-            return await TkActivityCore.ParseAsync(page_id, prefix, item_id, strategy_id, ip, oaid);
-        }
-
-
-
-    }
+using molilian.core;
+using dodohold.core;
+using Microsoft.AspNetCore.Mvc;
+using Org.BouncyCastle.Ocsp;
+using System.Text.Json;
+using System.Runtime.InteropServices;
+using System.Net;
+using System.Security.Cryptography;
+using Microsoft.AspNetCore.Authentication;
+using TencentCloud.Ecm.V20190719.Models;
+using COSXML.Network;
+using System.Linq;
+using static dodohold.core.ZTOExpress.CreateOrderArgs;
+using MySqlX.XDevAPI;
+using TencentCloud.Ame.V20190916.Models;
+using YunhuiKit;
+
+namespace molilian.api.Controllers
+{
+    /// <summary>
+    /// 淘宝会场活动
+    /// </summary>
+    [ApiController]
+    [Route("[controller]/[action]")]
+    public class TkActivityController : ControllerBase
+    {
+
+        protected IHttpContextAccessor _accessor;
+        public TkActivityController(IHttpContextAccessor accessor)
+        {
+            _accessor = accessor;
+        }
+
+
+
+
+        [HttpPost]
+        public async Task<ActionResult> Parse([FromBody] JsonElement form, [FromQuery] string a = "", [FromQuery] int t = 0, [FromQuery] string sign = "")
+        {
+            //正文
+            var page_id = form.Read("page_id", string.Empty);
+            var item_id = form.Read("item_id", string.Empty);
+            var strategy_id = form.Read("strategy_id", "1");
+            var ip = form.Read("ip", string.Empty);
+            var oaid = form.Read("oaid", string.Empty);
+
+            //验证签名
+            if (string.IsNullOrEmpty(a) || string.IsNullOrEmpty(sign))
+            {
+                return new APIResult(new { success = false, message = "验证错误" }, APIResultCodeEnum.Unauthorized);
+            }
+
+            DateTime time1 = t.Convert2Datetime();
+            if (time1 < DateTime.Now.AddMinutes(-10))
+            {
+                return new APIResult(new { success = false, message = "验证错误2" }, APIResultCodeEnum.Unauthorized);
+            }
+
+            var account = await ApiAccountCore.GetOneAsync(a);
+            if (account == null)
+            {
+                return new APIResult(new { success = false, message = "验证错误3" }, APIResultCodeEnum.Unauthorized);
+            }
+            string app_sign = $"{account.api_secret}@{t}".MD5();
+            if (sign != app_sign)
+            {
+                return new APIResult(new { success = false, message = "验证错误4" }, APIResultCodeEnum.Unauthorized);
+            }
+
+            string prefix = page_id switch
+            {
+                "20150318020021983" or "20150318020010092" or "20150318020019405" => "rec",
+                _ => "top",
+            };
+
+            return await TkActivityCore.ParseAsync(page_id, prefix, item_id, strategy_id, ip, oaid);
+        }
+
+
+        [HttpPost]
+        public async Task<ActionResult> unsafeParse([FromBody] JsonElement form)
+        {
+            var page_id = form.Read("page_id", string.Empty);
+            var item_id = form.Read("item_id", string.Empty);
+            var ip = form.Read("ip", string.Empty);
+            var oaid = form.Read("oaid", string.Empty);
+            var strategy_id = form.Read("strategy_id", string.Empty);
+
+
+            string prefix = page_id switch
+            {
+                "20150318020021983" or "20150318020010092" or "20150318020019405" => "rec",
+                _ => "top",
+            };
+
+            return await TkActivityCore.ParseAsync(page_id, prefix, item_id, strategy_id, ip, oaid);
+        }
+
+
+
+    }
 }

+ 610 - 610
molilian.api/Controllers/public/TkController.cs

@@ -1,610 +1,610 @@
-using molilian.core;
-using dodohold.core;
-using Microsoft.AspNetCore.Mvc;
-using System.Text.Json;
-using TencentCloud.Ecm.V20190719.Models;
-using YunhuiKit;
-
-namespace molilian.api.Controllers
-{
-    [ApiController]
-    [Route("[controller]/[action]")]
-    public class TkController : ControllerBase
-    {
-
-        protected IHttpContextAccessor _accessor;
-        public TkController(IHttpContextAccessor accessor)
-        {
-            _accessor = accessor;
-        }
-
-        /// <summary>
-        /// oppo调用
-        /// </summary>
-        /// <param name="s">正文</param>
-        /// <param name="c">渠道。tb/jd</param>
-        /// <param name="a">分配的客户端账号</param>
-        /// <param name="t">11位时间戳</param>
-        /// <param name="sign">签名</param>
-        /// <param name="ip">客户的IP</param>
-        /// <param name="oaid">客户的唯一ID</param>
-        /// <returns></returns>
-        [HttpPost]
-        public async Task<ActionResult> unionParse([FromBody] JsonElement form, [FromQuery] string a = "", [FromQuery] int t = 0, [FromQuery] string sign = "")
-        {
-            //正文
-            var content = form.Read("s", string.Empty);
-            var channel = form.Read("c", string.Empty);
-            var ip = form.Read("ip", string.Empty);
-            var oaid = form.Read("oaid", string.Empty);
-            var type = form.Read("type", string.Empty);
-            var clickId = form.Read("clickId", string.Empty);
-            var commerceType = form.Read<int>("commerceType", 0);
-            var riskStrategy = form.Read("riskStrategy", string.Empty);
-            var launchScene = form.Read<int>("launchScene", -1);
-            var special_text = form.Read<int>("special_text", -1);
-            var query_text = form.Read("query_text", string.Empty);
-
-
-            //验证签名
-            if (string.IsNullOrEmpty(a) || string.IsNullOrEmpty(sign))
-            {
-                return new APIResult(new { success = false, message = "验证错误" }, APIResultCodeEnum.Unauthorized);
-            }
-
-            DateTime time1 = t.Convert2Datetime();
-            if (time1 < DateTime.Now.AddMinutes(-10))
-            {
-                return new APIResult(new { success = false, message = "验证错误2" }, APIResultCodeEnum.Unauthorized);
-            }
-
-            var account = await ApiAccountCore.GetOneAsync(a);
-            if (account == null)
-            {
-                return new APIResult(new { success = false, message = "验证错误3" }, APIResultCodeEnum.Unauthorized);
-            }
-            string app_sign = $"{account.api_secret}@{t}".MD5();
-            if (sign != app_sign)
-            {
-                return new APIResult(new { success = false, message = "验证错误4" }, APIResultCodeEnum.Unauthorized);
-            }
-            var request = new UnionParseRequest
-            {
-                Content = content,
-                Channel = channel,
-                CommerceType = commerceType,
-                Ip = ip,
-                Oaid = oaid,
-                RiskStrategy = riskStrategy,
-                LaunchScene = launchScene,
-                AccountId = 0,
-                SpecialText = special_text,
-                QueryText = query_text,
-                ClickId = clickId,
-                Type = type
-            };
-            return await UnionParseCore.UnionParseAsync(request);
-        }
-
-        [HttpPost]
-        public async Task<ActionResult> unsafeParse([FromBody] JsonElement form)
-        {
-            var content = form.Read("s", string.Empty);
-            var channel = form.Read("c", string.Empty);
-            var ip = form.Read("ip", string.Empty);
-            var oaid = form.Read("oaid", string.Empty);
-            var clickId = form.Read("clickId", string.Empty);
-            var type = form.Read("type", string.Empty);
-            int accountid = form.Read("aid", 0);
-            var commerceType = form.Read<int>("commerceType", 0);
-            var riskStrategy = form.Read("riskStrategy", string.Empty);
-            var launchScene = form.Read<int>("launchScene", -1);
-            var special_text = form.Read<int>("special_text", -1);
-            var query_text = form.Read("query_text", string.Empty);
-
-#if DEBUG
-            //ip = "127.0.0.1";
-            //oaid = "test-oaid";
-#endif
-            var request = new UnionParseRequest
-            {
-                Content = content,
-                Channel = channel,
-                CommerceType = commerceType,
-                Ip = ip,
-                Oaid = oaid,
-                RiskStrategy = riskStrategy,
-                LaunchScene = launchScene,
-                AccountId = accountid,
-                SpecialText = special_text,
-                QueryText = query_text,
-                ClickId = clickId,
-                Type = type
-            };
-            return await UnionParseCore.UnionParseAsync(request);
-        }
-
-
-        [HttpPost]
-        public async Task<ActionResult> unsafeBatchParse([FromBody] JsonElement form)
-        {
-            var content = form.PathReadArray("s[]", string.Empty);
-            var channel = form.Read("c", string.Empty);
-            var ip = form.Read("ip", string.Empty);
-            var oaid = form.Read("oaid", string.Empty);
-            var clickId = form.Read("clickId", string.Empty);
-            var type = form.Read("type", string.Empty);
-            int accountid = form.Read("aid", 0);
-            int count = form.Read("count", 0);
-            var commerceType = form.Read<int>("commerceType", 0);
-            //入参参数。brw-浏览器,qapp-快应用
-            var riskStrategy = form.Read("riskStrategy", string.Empty);
-            var launchScene = form.Read<int>("launchScene", -1);
-            var special_text = form.Read<int>("special_text", -1);
-            var query_text = form.Read("query_text", string.Empty);
-
-#if DEBUG
-            //ip = "127.0.0.1";
-            //oaid = "test-oaid";
-#endif
-            for (int i = 0; i < count; i++)
-            {
-                var request = new UnionParseRequest
-                {
-                    Content = content[i],
-                    Channel = channel,
-                    CommerceType = commerceType,
-                    Ip = ip,
-                    Oaid = oaid,
-                    RiskStrategy = riskStrategy,
-                    LaunchScene = launchScene,
-                    AccountId = accountid,
-                    SpecialText = special_text,
-                    QueryText = query_text,
-                    ClickId = clickId,
-                    Type = type
-                };
-                _ = await UnionParseCore.UnionParseAsync(request);
-            }
-            return new APIResult(new { msg = "ok" });
-        }
-
-
-
-        [HttpPost]
-        public async Task<ActionResult> unionCoupon([FromBody] JsonElement form, [FromQuery] string a = "", [FromQuery] int t = 0, [FromQuery] string sign = "")
-        {
-            //正文
-            var content = form.Read("s", string.Empty);
-            var dplink = form.Read("dp", string.Empty);
-            var channel = form.Read("c", string.Empty);
-            var ip = form.Read("ip", string.Empty);
-            var oaid = form.Read("oaid", string.Empty);
-            var clickId = form.Read("clickId", string.Empty);
-            var style = form.Read("style", string.Empty);
-
-            //验证签名
-            if (string.IsNullOrEmpty(a) || string.IsNullOrEmpty(sign))
-            {
-                return new APIResult(new { success = false, message = "验证错误" }, APIResultCodeEnum.Unauthorized);
-            }
-
-            DateTime time1 = t.Convert2Datetime();
-            if (time1 < DateTime.Now.AddMinutes(-10))
-            {
-                return new APIResult(new { success = false, message = "验证错误2" }, APIResultCodeEnum.Unauthorized);
-            }
-
-            var account = await ApiAccountCore.GetOneAsync(a);
-            if (account == null)
-            {
-                return new APIResult(new { success = false, message = "验证错误3" }, APIResultCodeEnum.Unauthorized);
-            }
-            string app_sign = $"{account.api_secret}@{t}".MD5();
-            if (sign != app_sign)
-            {
-                return new APIResult(new { success = false, message = "验证错误4" }, APIResultCodeEnum.Unauthorized);
-            }
-            return await UnionCouponCore.UnionCouponAsync(content, dplink, channel, style, ip, oaid, clickId);
-        }
-
-
-        [HttpPost]
-        public async Task<ActionResult> unsafeCoupon([FromBody] JsonElement form)
-        {
-            var content = form.Read("s", string.Empty);
-            var dplink = form.Read("dp", string.Empty);
-            var channel = form.Read("c", string.Empty);
-            var ip = form.Read("ip", string.Empty);
-            var oaid = form.Read("oaid", string.Empty);
-            var style = form.Read("style", string.Empty);
-            var clickId = form.Read("clickId", string.Empty);
-#if DEBUG
-            ip = "127.0.0.1";
-            oaid = "test-oaid";
-#endif
-            return await UnionCouponCore.UnionCouponAsync(content, dplink, channel, style, ip, oaid, clickId);
-        }
-
-
-        [HttpGet]
-        public async Task<ActionResult> jd_parse(string s, string s2 = "", string ip = "", string oaid = "")
-        {
-            UnionParseRequest request = new()
-            {
-                Content = s,
-                Ip = ip,
-                Oaid = oaid
-            };
-
-            var result = JdUnionPlus.GetFormattedObject(request);
-            string body = "";
-            string content = s.UrlDecode();
-            string content2 = s2.UrlDecode();
-            try
-            {
-                if (AlimamaPlus.ShouldIgnoreRequest(ip, oaid, string.Empty, 0, out string reason))
-                {
-                    result.success = false;
-                    result.message = "放弃转链";
-                    result.reason = reason;
-                    result.rawContent = content;
-                    result.rawContent2 = content2;
-                    _ = TkLogCore.LogAsync(result);
-                    return new APIResult(result);
-                }
-
-                var api = DataokeCore.GetOne();
-                if (api == null)
-                {
-                    result.success = false;
-                    result.message = "放弃转链";
-                    result.reason = "没有匹配账号";
-                    result.rawContent = content;
-                    result.rawContent2 = content2;
-                    _ = TkLogCore.LogAsync(result);
-                    return new APIResult(result);
-                }
-
-                var account = await JdPoolCore.GetOneAsync();
-                if (account == null)
-                {
-                    result.success = false;
-                    result.message = "放弃转链";
-                    result.reason = "没有匹配账号";
-                    result.rawContent = content;
-                    result.rawContent2 = content2;
-                    _ = TkLogCore.LogAsync(result);
-                    return new APIResult(result);
-                }
-                result = DataokeCore.JdParse(result, api, account, content);
-            }
-            catch (Exception ex)
-            {
-
-                _ = new LoggerLibrary("api_error")
-                    .Info(s, s2)
-                    .Info("【jd】", body)
-                    .Info(ex.Message, ex.StackTrace)
-                    .SaveAsync();
-                NotifyCore.Notify(new NifyMessage
-                {
-                    message = $"【转链接口异常】\n{content}\n{content2}\n{body}\n\n{ex.Message}\n{ex.StackTrace}",
-                    priority = NifyMessagePriority.high,
-                    tags = ["red_circle"]
-                });
-                result.success = false;
-                result.message = "放弃转链";
-                result.reason = "转链接口异常";
-            }
-            result.rawContent = content;
-            result.rawContent2 = content2;
-            _ = TkLogCore.LogAsync(result);
-            return new APIResult(new
-            {
-                result.success,
-                result.message,
-                result.shortLinkurl,
-                result.itemId,
-                result.itemName,
-                result.deeplink_url,
-            });
-        }
-
-
-        [HttpGet]
-        public async Task<ActionResult> Reload()
-        {
-            await EndPointCore.NotifyReload();
-            return new APIResult(new
-            {
-                success = true,
-                message = "ok",
-            });
-        }
-
-        [HttpGet]
-        public async Task<ActionResult> dplist([FromQuery] string query = "", [FromQuery] string a = "", [FromQuery] int t = 0, [FromQuery] string sign = "")
-        {
-            var clientIp = _accessor.HttpContext.GetUserIp();
-            //验证签名
-            if (string.IsNullOrEmpty(a) || string.IsNullOrEmpty(sign))
-            {
-                _ = new LoggerLibrary("api", "dplist_error")
-                    .Info($"{clientIp}\t{query}")
-                    .Info($"{a}\t{sign}\t{t}")
-                    .Info("验证错误")
-                    .SaveAsync();
-
-                return new APIResult(new { success = false, message = "验证错误" }, APIResultCodeEnum.Unauthorized);
-            }
-
-            DateTime time1 = t.Convert2Datetime();
-            if (time1 < DateTime.Now.AddMinutes(-10))
-            {
-                _ = new LoggerLibrary("api", "dplist_error")
-                    .Info($"{clientIp}\t{query}")
-                    .Info($"{a}\t{sign}\t{t}")
-                    .Info("验证错误2")
-                    .SaveAsync();
-                return new APIResult(new { success = false, message = "验证错误2" }, APIResultCodeEnum.Unauthorized);
-            }
-
-            var account = await ApiAccountCore.GetOneAsync(a);
-            if (account == null)
-            {
-                _ = new LoggerLibrary("api", "dplist_error")
-                    .Info($"{clientIp}\t{query}")
-                    .Info($"{a}\t{sign}\t{t}")
-                    .Info("验证错误3")
-                    .SaveAsync();
-
-                return new APIResult(new { success = false, message = "验证错误3" }, APIResultCodeEnum.Unauthorized);
-            }
-            string app_sign = $"{account.api_secret}@{t}".MD5();
-            if (sign != app_sign)
-            {
-                _ = new LoggerLibrary("api", "dplist_error")
-                    .Info($"{clientIp}\t{query}")
-                    .Info($"{a}\t{sign}\t{t}")
-                    .Info("验证错误4")
-                    .SaveAsync();
-
-                return new APIResult(new { success = false, message = "验证错误4" }, APIResultCodeEnum.Unauthorized);
-            }
-
-            if (string.IsNullOrEmpty(query))
-            {
-                _ = new LoggerLibrary("api", "dplist_error")
-                    .Info($"{clientIp}\t{query}")
-                    .Info($"{a}\t{sign}\t{t}")
-                    .Info("查询条件不能为空")
-                    .SaveAsync();
-
-                return new APIResult(new { success = false, message = "查询条件不能为空" });
-            }
-            int[] report_account_ids = account.report_account_ids.TryToArray<int>(',');
-            int[] report_exclude_account_ids = account.report_exclude_account_ids.TryToArray<int>(',');
-
-            string filter = $"batchId=@query";
-            var account_list = await TkPoolCore.ListAsync();
-            List<int> account_ids = [];
-            if (account_list.Any())
-            {
-                account_ids = account_list.Where(e => e.enable_fake_click).Select(e => e.id).ToList();
-
-                if (report_account_ids.Length > 0)
-                {
-                    account_ids = account_ids.Intersect(report_account_ids).ToList();
-                }
-
-                if (report_exclude_account_ids.Length > 0)
-                {
-                    account_ids = account_ids.Except(report_exclude_account_ids).ToList();
-                }
-                filter += $" AND accountId IN @account_ids";
-            }
-            string orderBy = "paid_time ASC";
-
-            var data = new DBContext.Table("tk_order_tracking")
-                .Where(filter, new { query, account_ids })
-                .Order(orderBy)
-                .Select<TkOrderTrackingDTO>();
-
-            //fake_click_link_type
-
-
-            List<object> list = [];
-            foreach (var item in data)
-            {
-                list.Add(new { pid = item.accountId, url = item.deeplinkUrl, item.shortLinkUrl, count = item.click_num, time = item.exp_time });
-            }
-
-            var ids = data.Select(e => e.Id).ToArray();
-
-            _ = new LoggerLibrary("api", "dplist")
-                 .Info($"{clientIp}\t{query}\tcount:{list.Count}")
-                 .Info(string.Join(',', ids))
-                 .SaveAsync();
-
-            var update = new DBContext.Table("tk_order_tracking")
-                .Add("last_pull_time", DateTime.Now)
-                .Add("last_pull_account", account.name)
-                .Add("clicked_num:", "clicked_num+1")
-                .Where("id IN @ids", new { ids })
-                .Update();
-
-            return new APIResult(new
-            {
-                success = true,
-                message = list.Count == 0 ? "none" : "ok",
-                count = list.Count,
-                data = list,
-            });
-        }
-
-
-        [HttpGet]
-        public async Task<ActionResult> unsafeParseDpList([FromQuery] string a = "", [FromQuery] string query = "")
-        {
-            if (string.IsNullOrEmpty(a))
-            {
-                return new APIResult(new { success = false, message = "验证错误" }, APIResultCodeEnum.Unauthorized);
-            }
-
-            var account = await ApiAccountCore.GetOneAsync(a);
-            if (account == null || !account.enable_report)
-            {
-                return new APIResult(new { success = false, message = "验证错误3" }, APIResultCodeEnum.Unauthorized);
-            }
-
-            if (string.IsNullOrEmpty(query))
-            {
-                return new APIResult(new { success = false, message = "查询条件不能为空" });
-            }
-
-            int[] report_account_ids = account.report_account_ids.TryToArray<int>(',');
-            int[] report_exclude_account_ids = account.report_exclude_account_ids.TryToArray<int>(',');
-
-            string filter = $"batchId=@query";
-            var account_list = await TkPoolCore.ListAsync();
-            List<int> account_ids = [];
-            if (account_list.Any())
-            {
-                account_ids = account_list.Where(e => e.enable_fake_click).Select(e => e.id).ToList();
-
-                if (report_account_ids.Length > 0)
-                {
-                    account_ids = account_ids.Intersect(report_account_ids).ToList();
-                }
-
-                if (report_exclude_account_ids.Length > 0)
-                {
-                    account_ids = account_ids.Except(report_exclude_account_ids).ToList();
-                }
-                filter += $" AND accountId IN @account_ids";
-            }
-
-            string orderBy = "paid_time ASC";
-
-            var data = new DBContext.Table("tk_order_tracking")
-                .Where(filter, new { query, account_ids })
-                .Order(orderBy)
-                .Select<TkOrderTrackingDTO>();
-
-            List<object> list = [];
-            foreach (var item in data)
-            {
-                list.Add(new { pid = item.accountId, url = item.deeplinkUrl, item.shortLinkUrl, count = item.click_num, time = item.exp_time });
-            }
-            var clientIp = _accessor.HttpContext.GetUserIp();
-            LoggerLibrary log = new LoggerLibrary("api", "unsafeParseDpList");
-            log.Info($"{clientIp}\t{query}\tcount:{list.Count}");
-
-            var ids = data.Select(e => e.Id).ToArray();
-            log.Info(string.Join(',', ids));
-            _ = log.SaveAsync();
-
-            return new APIResult(new
-            {
-                success = true,
-                message = list.Count == 0 ? "none" : "ok",
-                count = list.Count,
-                data = list,
-            });
-        }
-
-
-
-        [HttpPost]
-        public async Task<ActionResult> dataReport([FromBody] JsonElement form, [FromQuery] string a = "", [FromQuery] int t = 0, [FromQuery] string sign = "")
-        {
-            bool debug = form.Read("debug", false);
-            var clientIp = _accessor.HttpContext.GetUserIp();
-            var log = new LoggerLibrary("ApiReportCore", "dataReport");
-            log.Info(clientIp);
-
-            //验证签名
-            if (string.IsNullOrEmpty(a) || string.IsNullOrEmpty(sign))
-            {
-                _ = log.Info($"a:{a}\tt:{t}\tsign:{sign}\t", $"message:验证错误")
-                    .Info(form.GetRawText())
-                    .SaveAsync();
-                return new APIResult(new { success = false, message = "验证错误" }, APIResultCodeEnum.Unauthorized);
-            }
-
-            DateTime time1 = t.Convert2Datetime();
-            if (time1 < DateTime.Now.AddMinutes(-10))
-            {
-                _ = log.Info($"a:{a}\tt:{t}\tsign:{sign}\t", $"message:验证错误2")
-                    .Info(form.GetRawText())
-                    .SaveAsync();
-                return new APIResult(new { success = false, message = "验证错误2" }, APIResultCodeEnum.Unauthorized);
-            }
-
-            var account = await ApiAccountCore.GetOneAsync(a);
-            if (account == null)
-            {
-                _ = log.Info($"a:{a}\tt:{t}\tsign:{sign}\t", $"message:验证错误3")
-                    .Info(form.GetRawText())
-                    .SaveAsync();
-                return new APIResult(new { success = false, message = "验证错误3" }, APIResultCodeEnum.Unauthorized);
-            }
-            string app_sign = $"{account.api_secret}@{t}".MD5();
-            if (sign != app_sign)
-            {
-                _ = log.Info($"a:{a}\tt:{t}\tsign:{sign}\tapp_sign:{app_sign}", $"message:验证错误4")
-                    .Info(form.GetRawText())
-                    .SaveAsync();
-                return new APIResult(new { success = false, message = "验证错误4" }, APIResultCodeEnum.Unauthorized);
-            }
-
-            try
-            {
-                //0:全分销类型;-1:各分销类型,1:goods 2: live 3:video 4: profile 5: unknown
-                int distributiontype = form.Read("distributiontype", 0);
-
-                //0:全平台;-1:各平台;1:淘宝 2:京东 2:多多
-                int platform = form.Read("platform", 0);
-
-                var data = await ApiReportCore.ReportDailysAsync(form, clientIp, debug);
-                _ = log.Info(form.GetRawText(), data.Convert2Json()).SaveAsync();
-                return new APIResult(new { success = true, platform, distributiontype, data });
-            }
-            catch (Exception ex)
-            {
-                _ = log.Info(form.GetRawText()).Info(ex.Message, ex.StackTrace).SaveAsync();
-                return new APIResult(new { success = false, message = "内部错误" });
-            }
-        }
-
-
-        [HttpPost]
-        public async Task<ActionResult> unsafeDataReport([FromBody] JsonElement form)
-        {
-            bool debug = form.Read("debug", false);
-            var clientIp = _accessor.HttpContext.GetUserIp();
-            var log = new LoggerLibrary("ApiReportCore", "unsafeDataReport");
-            log.Info(clientIp);
-
-            try
-            {
-                //0:全分销类型;-1:各分销类型,1:goods 2: live 3:video 4: profile 5: unknown
-                int distributiontype = form.Read("distributiontype", 0);
-
-                //0:全平台;-1:各平台;1:淘宝 2:京东
-                int platform = form.Read("platform", 0);
-
-                var data = await ApiReportCore.ReportDailysAsync(form, clientIp, debug);
-                _ = log.Info(form.GetRawText(), data.Convert2Json()).SaveAsync();
-                return new APIResult(new { success = true, platform, distributiontype, data });
-            }
-            catch (Exception ex)
-            {
-                _ = log.Info(form.GetRawText()).Info(ex.Message, ex.StackTrace).SaveAsync();
-                return new APIResult(new { success = false, message = "内部错误" });
-            }
-        }
-
-    }
-}
+using molilian.core;
+using dodohold.core;
+using Microsoft.AspNetCore.Mvc;
+using System.Text.Json;
+using TencentCloud.Ecm.V20190719.Models;
+using YunhuiKit;
+
+namespace molilian.api.Controllers
+{
+    [ApiController]
+    [Route("[controller]/[action]")]
+    public class TkController : ControllerBase
+    {
+
+        protected IHttpContextAccessor _accessor;
+        public TkController(IHttpContextAccessor accessor)
+        {
+            _accessor = accessor;
+        }
+
+        /// <summary>
+        /// oppo调用
+        /// </summary>
+        /// <param name="s">正文</param>
+        /// <param name="c">渠道。tb/jd</param>
+        /// <param name="a">分配的客户端账号</param>
+        /// <param name="t">11位时间戳</param>
+        /// <param name="sign">签名</param>
+        /// <param name="ip">客户的IP</param>
+        /// <param name="oaid">客户的唯一ID</param>
+        /// <returns></returns>
+        [HttpPost]
+        public async Task<ActionResult> unionParse([FromBody] JsonElement form, [FromQuery] string a = "", [FromQuery] int t = 0, [FromQuery] string sign = "")
+        {
+            //正文
+            var content = form.Read("s", string.Empty);
+            var channel = form.Read("c", string.Empty);
+            var ip = form.Read("ip", string.Empty);
+            var oaid = form.Read("oaid", string.Empty);
+            var type = form.Read("type", string.Empty);
+            var clickId = form.Read("clickId", string.Empty);
+            var commerceType = form.Read<int>("commerceType", 0);
+            var riskStrategy = form.Read("riskStrategy", string.Empty);
+            var launchScene = form.Read<int>("launchScene", -1);
+            var special_text = form.Read<int>("special_text", -1);
+            var query_text = form.Read("query_text", string.Empty);
+
+
+            //验证签名
+            if (string.IsNullOrEmpty(a) || string.IsNullOrEmpty(sign))
+            {
+                return new APIResult(new { success = false, message = "验证错误" }, APIResultCodeEnum.Unauthorized);
+            }
+
+            DateTime time1 = t.Convert2Datetime();
+            if (time1 < DateTime.Now.AddMinutes(-10))
+            {
+                return new APIResult(new { success = false, message = "验证错误2" }, APIResultCodeEnum.Unauthorized);
+            }
+
+            var account = await ApiAccountCore.GetOneAsync(a);
+            if (account == null)
+            {
+                return new APIResult(new { success = false, message = "验证错误3" }, APIResultCodeEnum.Unauthorized);
+            }
+            string app_sign = $"{account.api_secret}@{t}".MD5();
+            if (sign != app_sign)
+            {
+                return new APIResult(new { success = false, message = "验证错误4" }, APIResultCodeEnum.Unauthorized);
+            }
+            var request = new UnionParseRequest
+            {
+                Content = content,
+                Channel = channel,
+                CommerceType = commerceType,
+                Ip = ip,
+                Oaid = oaid,
+                RiskStrategy = riskStrategy,
+                LaunchScene = launchScene,
+                AccountId = 0,
+                SpecialText = special_text,
+                QueryText = query_text,
+                ClickId = clickId,
+                Type = type
+            };
+            return await UnionParseCore.UnionParseAsync(request);
+        }
+
+        [HttpPost]
+        public async Task<ActionResult> unsafeParse([FromBody] JsonElement form)
+        {
+            var content = form.Read("s", string.Empty);
+            var channel = form.Read("c", string.Empty);
+            var ip = form.Read("ip", string.Empty);
+            var oaid = form.Read("oaid", string.Empty);
+            var clickId = form.Read("clickId", string.Empty);
+            var type = form.Read("type", string.Empty);
+            int accountid = form.Read("aid", 0);
+            var commerceType = form.Read<int>("commerceType", 0);
+            var riskStrategy = form.Read("riskStrategy", string.Empty);
+            var launchScene = form.Read<int>("launchScene", -1);
+            var special_text = form.Read<int>("special_text", -1);
+            var query_text = form.Read("query_text", string.Empty);
+
+#if DEBUG
+            //ip = "127.0.0.1";
+            //oaid = "test-oaid";
+#endif
+            var request = new UnionParseRequest
+            {
+                Content = content,
+                Channel = channel,
+                CommerceType = commerceType,
+                Ip = ip,
+                Oaid = oaid,
+                RiskStrategy = riskStrategy,
+                LaunchScene = launchScene,
+                AccountId = accountid,
+                SpecialText = special_text,
+                QueryText = query_text,
+                ClickId = clickId,
+                Type = type
+            };
+            return await UnionParseCore.UnionParseAsync(request);
+        }
+
+
+        [HttpPost]
+        public async Task<ActionResult> unsafeBatchParse([FromBody] JsonElement form)
+        {
+            var content = form.PathReadArray("s[]", string.Empty);
+            var channel = form.Read("c", string.Empty);
+            var ip = form.Read("ip", string.Empty);
+            var oaid = form.Read("oaid", string.Empty);
+            var clickId = form.Read("clickId", string.Empty);
+            var type = form.Read("type", string.Empty);
+            int accountid = form.Read("aid", 0);
+            int count = form.Read("count", 0);
+            var commerceType = form.Read<int>("commerceType", 0);
+            //入参参数。brw-浏览器,qapp-快应用
+            var riskStrategy = form.Read("riskStrategy", string.Empty);
+            var launchScene = form.Read<int>("launchScene", -1);
+            var special_text = form.Read<int>("special_text", -1);
+            var query_text = form.Read("query_text", string.Empty);
+
+#if DEBUG
+            //ip = "127.0.0.1";
+            //oaid = "test-oaid";
+#endif
+            for (int i = 0; i < count; i++)
+            {
+                var request = new UnionParseRequest
+                {
+                    Content = content[i],
+                    Channel = channel,
+                    CommerceType = commerceType,
+                    Ip = ip,
+                    Oaid = oaid,
+                    RiskStrategy = riskStrategy,
+                    LaunchScene = launchScene,
+                    AccountId = accountid,
+                    SpecialText = special_text,
+                    QueryText = query_text,
+                    ClickId = clickId,
+                    Type = type
+                };
+                _ = await UnionParseCore.UnionParseAsync(request);
+            }
+            return new APIResult(new { msg = "ok" });
+        }
+
+
+
+        [HttpPost]
+        public async Task<ActionResult> unionCoupon([FromBody] JsonElement form, [FromQuery] string a = "", [FromQuery] int t = 0, [FromQuery] string sign = "")
+        {
+            //正文
+            var content = form.Read("s", string.Empty);
+            var dplink = form.Read("dp", string.Empty);
+            var channel = form.Read("c", string.Empty);
+            var ip = form.Read("ip", string.Empty);
+            var oaid = form.Read("oaid", string.Empty);
+            var clickId = form.Read("clickId", string.Empty);
+            var style = form.Read("style", string.Empty);
+
+            //验证签名
+            if (string.IsNullOrEmpty(a) || string.IsNullOrEmpty(sign))
+            {
+                return new APIResult(new { success = false, message = "验证错误" }, APIResultCodeEnum.Unauthorized);
+            }
+
+            DateTime time1 = t.Convert2Datetime();
+            if (time1 < DateTime.Now.AddMinutes(-10))
+            {
+                return new APIResult(new { success = false, message = "验证错误2" }, APIResultCodeEnum.Unauthorized);
+            }
+
+            var account = await ApiAccountCore.GetOneAsync(a);
+            if (account == null)
+            {
+                return new APIResult(new { success = false, message = "验证错误3" }, APIResultCodeEnum.Unauthorized);
+            }
+            string app_sign = $"{account.api_secret}@{t}".MD5();
+            if (sign != app_sign)
+            {
+                return new APIResult(new { success = false, message = "验证错误4" }, APIResultCodeEnum.Unauthorized);
+            }
+            return await UnionCouponCore.UnionCouponAsync(content, dplink, channel, style, ip, oaid, clickId);
+        }
+
+
+        [HttpPost]
+        public async Task<ActionResult> unsafeCoupon([FromBody] JsonElement form)
+        {
+            var content = form.Read("s", string.Empty);
+            var dplink = form.Read("dp", string.Empty);
+            var channel = form.Read("c", string.Empty);
+            var ip = form.Read("ip", string.Empty);
+            var oaid = form.Read("oaid", string.Empty);
+            var style = form.Read("style", string.Empty);
+            var clickId = form.Read("clickId", string.Empty);
+#if DEBUG
+            ip = "127.0.0.1";
+            oaid = "test-oaid";
+#endif
+            return await UnionCouponCore.UnionCouponAsync(content, dplink, channel, style, ip, oaid, clickId);
+        }
+
+
+        [HttpGet]
+        public async Task<ActionResult> jd_parse(string s, string s2 = "", string ip = "", string oaid = "")
+        {
+            UnionParseRequest request = new()
+            {
+                Content = s,
+                Ip = ip,
+                Oaid = oaid
+            };
+
+            var result = JdUnionPlus.GetFormattedObject(request);
+            string body = "";
+            string content = s.UrlDecode();
+            string content2 = s2.UrlDecode();
+            try
+            {
+                if (AlimamaPlus.ShouldIgnoreRequest(ip, oaid, string.Empty, 0, out string reason))
+                {
+                    result.success = false;
+                    result.message = "放弃转链";
+                    result.reason = reason;
+                    result.rawContent = content;
+                    result.rawContent2 = content2;
+                    _ = TkLogCore.LogAsync(result);
+                    return new APIResult(result);
+                }
+
+                var api = DataokeCore.GetOne();
+                if (api == null)
+                {
+                    result.success = false;
+                    result.message = "放弃转链";
+                    result.reason = "没有匹配账号";
+                    result.rawContent = content;
+                    result.rawContent2 = content2;
+                    _ = TkLogCore.LogAsync(result);
+                    return new APIResult(result);
+                }
+
+                var account = await JdPoolCore.GetOneAsync();
+                if (account == null)
+                {
+                    result.success = false;
+                    result.message = "放弃转链";
+                    result.reason = "没有匹配账号";
+                    result.rawContent = content;
+                    result.rawContent2 = content2;
+                    _ = TkLogCore.LogAsync(result);
+                    return new APIResult(result);
+                }
+                result = DataokeCore.JdParse(result, api, account, content);
+            }
+            catch (Exception ex)
+            {
+
+                _ = new LoggerLibrary("api_error")
+                    .Info(s, s2)
+                    .Info("【jd】", body)
+                    .Info(ex.Message, ex.StackTrace)
+                    .SaveAsync();
+                NotifyCore.Notify(new NifyMessage
+                {
+                    message = $"【转链接口异常】\n{content}\n{content2}\n{body}\n\n{ex.Message}\n{ex.StackTrace}",
+                    priority = NifyMessagePriority.high,
+                    tags = ["red_circle"]
+                });
+                result.success = false;
+                result.message = "放弃转链";
+                result.reason = "转链接口异常";
+            }
+            result.rawContent = content;
+            result.rawContent2 = content2;
+            _ = TkLogCore.LogAsync(result);
+            return new APIResult(new
+            {
+                result.success,
+                result.message,
+                result.shortLinkurl,
+                result.itemId,
+                result.itemName,
+                result.deeplink_url,
+            });
+        }
+
+
+        [HttpGet]
+        public async Task<ActionResult> Reload()
+        {
+            await EndPointCore.NotifyReload();
+            return new APIResult(new
+            {
+                success = true,
+                message = "ok",
+            });
+        }
+
+        [HttpGet]
+        public async Task<ActionResult> dplist([FromQuery] string query = "", [FromQuery] string a = "", [FromQuery] int t = 0, [FromQuery] string sign = "")
+        {
+            var clientIp = _accessor.HttpContext.GetUserIp();
+            //验证签名
+            if (string.IsNullOrEmpty(a) || string.IsNullOrEmpty(sign))
+            {
+                _ = new LoggerLibrary("api", "dplist_error")
+                    .Info($"{clientIp}\t{query}")
+                    .Info($"{a}\t{sign}\t{t}")
+                    .Info("验证错误")
+                    .SaveAsync();
+
+                return new APIResult(new { success = false, message = "验证错误" }, APIResultCodeEnum.Unauthorized);
+            }
+
+            DateTime time1 = t.Convert2Datetime();
+            if (time1 < DateTime.Now.AddMinutes(-10))
+            {
+                _ = new LoggerLibrary("api", "dplist_error")
+                    .Info($"{clientIp}\t{query}")
+                    .Info($"{a}\t{sign}\t{t}")
+                    .Info("验证错误2")
+                    .SaveAsync();
+                return new APIResult(new { success = false, message = "验证错误2" }, APIResultCodeEnum.Unauthorized);
+            }
+
+            var account = await ApiAccountCore.GetOneAsync(a);
+            if (account == null)
+            {
+                _ = new LoggerLibrary("api", "dplist_error")
+                    .Info($"{clientIp}\t{query}")
+                    .Info($"{a}\t{sign}\t{t}")
+                    .Info("验证错误3")
+                    .SaveAsync();
+
+                return new APIResult(new { success = false, message = "验证错误3" }, APIResultCodeEnum.Unauthorized);
+            }
+            string app_sign = $"{account.api_secret}@{t}".MD5();
+            if (sign != app_sign)
+            {
+                _ = new LoggerLibrary("api", "dplist_error")
+                    .Info($"{clientIp}\t{query}")
+                    .Info($"{a}\t{sign}\t{t}")
+                    .Info("验证错误4")
+                    .SaveAsync();
+
+                return new APIResult(new { success = false, message = "验证错误4" }, APIResultCodeEnum.Unauthorized);
+            }
+
+            if (string.IsNullOrEmpty(query))
+            {
+                _ = new LoggerLibrary("api", "dplist_error")
+                    .Info($"{clientIp}\t{query}")
+                    .Info($"{a}\t{sign}\t{t}")
+                    .Info("查询条件不能为空")
+                    .SaveAsync();
+
+                return new APIResult(new { success = false, message = "查询条件不能为空" });
+            }
+            int[] report_account_ids = account.report_account_ids.TryToArray<int>(',');
+            int[] report_exclude_account_ids = account.report_exclude_account_ids.TryToArray<int>(',');
+
+            string filter = $"batchId=@query";
+            var account_list = await TkPoolCore.ListAsync();
+            List<int> account_ids = [];
+            if (account_list.Any())
+            {
+                account_ids = account_list.Where(e => e.enable_fake_click).Select(e => e.id).ToList();
+
+                if (report_account_ids.Length > 0)
+                {
+                    account_ids = account_ids.Intersect(report_account_ids).ToList();
+                }
+
+                if (report_exclude_account_ids.Length > 0)
+                {
+                    account_ids = account_ids.Except(report_exclude_account_ids).ToList();
+                }
+                filter += $" AND accountId IN @account_ids";
+            }
+            string orderBy = "paid_time ASC";
+
+            var data = new DBContext.Table("tk_order_tracking")
+                .Where(filter, new { query, account_ids })
+                .Order(orderBy)
+                .Select<TkOrderTrackingDTO>();
+
+            //fake_click_link_type
+
+
+            List<object> list = [];
+            foreach (var item in data)
+            {
+                list.Add(new { pid = item.accountId, url = item.deeplinkUrl, item.shortLinkUrl, count = item.click_num, time = item.exp_time });
+            }
+
+            var ids = data.Select(e => e.Id).ToArray();
+
+            _ = new LoggerLibrary("api", "dplist")
+                 .Info($"{clientIp}\t{query}\tcount:{list.Count}")
+                 .Info(string.Join(',', ids))
+                 .SaveAsync();
+
+            var update = new DBContext.Table("tk_order_tracking")
+                .Add("last_pull_time", DateTime.Now)
+                .Add("last_pull_account", account.name)
+                .Add("clicked_num:", "clicked_num+1")
+                .Where("id IN @ids", new { ids })
+                .Update();
+
+            return new APIResult(new
+            {
+                success = true,
+                message = list.Count == 0 ? "none" : "ok",
+                count = list.Count,
+                data = list,
+            });
+        }
+
+
+        [HttpGet]
+        public async Task<ActionResult> unsafeParseDpList([FromQuery] string a = "", [FromQuery] string query = "")
+        {
+            if (string.IsNullOrEmpty(a))
+            {
+                return new APIResult(new { success = false, message = "验证错误" }, APIResultCodeEnum.Unauthorized);
+            }
+
+            var account = await ApiAccountCore.GetOneAsync(a);
+            if (account == null || !account.enable_report)
+            {
+                return new APIResult(new { success = false, message = "验证错误3" }, APIResultCodeEnum.Unauthorized);
+            }
+
+            if (string.IsNullOrEmpty(query))
+            {
+                return new APIResult(new { success = false, message = "查询条件不能为空" });
+            }
+
+            int[] report_account_ids = account.report_account_ids.TryToArray<int>(',');
+            int[] report_exclude_account_ids = account.report_exclude_account_ids.TryToArray<int>(',');
+
+            string filter = $"batchId=@query";
+            var account_list = await TkPoolCore.ListAsync();
+            List<int> account_ids = [];
+            if (account_list.Any())
+            {
+                account_ids = account_list.Where(e => e.enable_fake_click).Select(e => e.id).ToList();
+
+                if (report_account_ids.Length > 0)
+                {
+                    account_ids = account_ids.Intersect(report_account_ids).ToList();
+                }
+
+                if (report_exclude_account_ids.Length > 0)
+                {
+                    account_ids = account_ids.Except(report_exclude_account_ids).ToList();
+                }
+                filter += $" AND accountId IN @account_ids";
+            }
+
+            string orderBy = "paid_time ASC";
+
+            var data = new DBContext.Table("tk_order_tracking")
+                .Where(filter, new { query, account_ids })
+                .Order(orderBy)
+                .Select<TkOrderTrackingDTO>();
+
+            List<object> list = [];
+            foreach (var item in data)
+            {
+                list.Add(new { pid = item.accountId, url = item.deeplinkUrl, item.shortLinkUrl, count = item.click_num, time = item.exp_time });
+            }
+            var clientIp = _accessor.HttpContext.GetUserIp();
+            LoggerLibrary log = new LoggerLibrary("api", "unsafeParseDpList");
+            log.Info($"{clientIp}\t{query}\tcount:{list.Count}");
+
+            var ids = data.Select(e => e.Id).ToArray();
+            log.Info(string.Join(',', ids));
+            _ = log.SaveAsync();
+
+            return new APIResult(new
+            {
+                success = true,
+                message = list.Count == 0 ? "none" : "ok",
+                count = list.Count,
+                data = list,
+            });
+        }
+
+
+
+        [HttpPost]
+        public async Task<ActionResult> dataReport([FromBody] JsonElement form, [FromQuery] string a = "", [FromQuery] int t = 0, [FromQuery] string sign = "")
+        {
+            bool debug = form.Read("debug", false);
+            var clientIp = _accessor.HttpContext.GetUserIp();
+            var log = new LoggerLibrary("ApiReportCore", "dataReport");
+            log.Info(clientIp);
+
+            //验证签名
+            if (string.IsNullOrEmpty(a) || string.IsNullOrEmpty(sign))
+            {
+                _ = log.Info($"a:{a}\tt:{t}\tsign:{sign}\t", $"message:验证错误")
+                    .Info(form.GetRawText())
+                    .SaveAsync();
+                return new APIResult(new { success = false, message = "验证错误" }, APIResultCodeEnum.Unauthorized);
+            }
+
+            DateTime time1 = t.Convert2Datetime();
+            if (time1 < DateTime.Now.AddMinutes(-10))
+            {
+                _ = log.Info($"a:{a}\tt:{t}\tsign:{sign}\t", $"message:验证错误2")
+                    .Info(form.GetRawText())
+                    .SaveAsync();
+                return new APIResult(new { success = false, message = "验证错误2" }, APIResultCodeEnum.Unauthorized);
+            }
+
+            var account = await ApiAccountCore.GetOneAsync(a);
+            if (account == null)
+            {
+                _ = log.Info($"a:{a}\tt:{t}\tsign:{sign}\t", $"message:验证错误3")
+                    .Info(form.GetRawText())
+                    .SaveAsync();
+                return new APIResult(new { success = false, message = "验证错误3" }, APIResultCodeEnum.Unauthorized);
+            }
+            string app_sign = $"{account.api_secret}@{t}".MD5();
+            if (sign != app_sign)
+            {
+                _ = log.Info($"a:{a}\tt:{t}\tsign:{sign}\tapp_sign:{app_sign}", $"message:验证错误4")
+                    .Info(form.GetRawText())
+                    .SaveAsync();
+                return new APIResult(new { success = false, message = "验证错误4" }, APIResultCodeEnum.Unauthorized);
+            }
+
+            try
+            {
+                //0:全分销类型;-1:各分销类型,1:goods 2: live 3:video 4: profile 5: unknown
+                int distributiontype = form.Read("distributiontype", 0);
+
+                //0:全平台;-1:各平台;1:淘宝 2:京东 2:多多
+                int platform = form.Read("platform", 0);
+
+                var data = await ApiReportCore.ReportDailysAsync(form, clientIp, debug);
+                _ = log.Info(form.GetRawText(), data.Convert2Json()).SaveAsync();
+                return new APIResult(new { success = true, platform, distributiontype, data });
+            }
+            catch (Exception ex)
+            {
+                _ = log.Info(form.GetRawText()).Info(ex.Message, ex.StackTrace).SaveAsync();
+                return new APIResult(new { success = false, message = "内部错误" });
+            }
+        }
+
+
+        [HttpPost]
+        public async Task<ActionResult> unsafeDataReport([FromBody] JsonElement form)
+        {
+            bool debug = form.Read("debug", false);
+            var clientIp = _accessor.HttpContext.GetUserIp();
+            var log = new LoggerLibrary("ApiReportCore", "unsafeDataReport");
+            log.Info(clientIp);
+
+            try
+            {
+                //0:全分销类型;-1:各分销类型,1:goods 2: live 3:video 4: profile 5: unknown
+                int distributiontype = form.Read("distributiontype", 0);
+
+                //0:全平台;-1:各平台;1:淘宝 2:京东
+                int platform = form.Read("platform", 0);
+
+                var data = await ApiReportCore.ReportDailysAsync(form, clientIp, debug);
+                _ = log.Info(form.GetRawText(), data.Convert2Json()).SaveAsync();
+                return new APIResult(new { success = true, platform, distributiontype, data });
+            }
+            catch (Exception ex)
+            {
+                _ = log.Info(form.GetRawText()).Info(ex.Message, ex.StackTrace).SaveAsync();
+                return new APIResult(new { success = false, message = "内部错误" });
+            }
+        }
+
+    }
+}

+ 71 - 71
molilian.api/Controllers/public/TkEndpointController.cs

@@ -1,72 +1,72 @@
-using molilian.core;
-using dodohold.core;
-using Microsoft.AspNetCore.Mvc;
-using Org.BouncyCastle.Ocsp;
-using System.Text.Json;
-using System.Runtime.InteropServices;
-using System.Net;
-using System.Security.Cryptography;
-using Microsoft.AspNetCore.Authentication;
-using TencentCloud.Ecm.V20190719.Models;
-using COSXML.Network;
-using System.Linq;
-using static dodohold.core.ZTOExpress.CreateOrderArgs;
-using TencentCloud.Lighthouse.V20200324.Models;
-using TencentCloud.Ame.V20190916.Models;
-using TencentCloud.Omics.V20221128.Models;
-using System.Threading.Channels;
-
-namespace molilian.api.Controllers
-{
-    [ApiController]
-    [Route("api/[controller]/[action]")]
-    public class TkEndpointController : ControllerBase
-    {
-        protected IHttpContextAccessor _accessor;
-        TaokeOpenCore core;
-        public TkEndpointController(IHttpContextAccessor accessor)
-        {
-            _accessor = accessor;
-            core = new TaokeOpenCore();
-        }
-
-        [HttpPost]
-        public async Task<ActionResult> ChangeSuspend([FromForm] JsonElement body)
-        {
-            int accountId = body.Read<int>("accountId");
-            string endpoint = body.Read<string>("accountId");
-            bool release = body.Read<bool>("release");
-
-            if (release)
-            {
-                TkEndpointManager.ReleaseSuspend(accountId, endpoint);
-            }
-            else
-            {
-                await TkEndpointManager.SuspendAsync(accountId, endpoint, TkEndpointManager.SuspendReason.Active);
-            }
-
-            return new APIResult(new
-            {
-                success = true,
-                action = release ? "ReleaseSuspend" : "Suspend",
-            });
-        }
-
-
-        [HttpPost]
-        public ActionResult GetStatus([FromForm] JsonElement body)
-        {
-            var ids = body.PathReadArray<int>("accountId[]");
-            var result = TkEndpointManager.GetStatus(ids);
-
-            return new APIResult(new
-            {
-                success = true,
-                data = result
-            });
-        }
-
-
-    }
+using molilian.core;
+using dodohold.core;
+using Microsoft.AspNetCore.Mvc;
+using Org.BouncyCastle.Ocsp;
+using System.Text.Json;
+using System.Runtime.InteropServices;
+using System.Net;
+using System.Security.Cryptography;
+using Microsoft.AspNetCore.Authentication;
+using TencentCloud.Ecm.V20190719.Models;
+using COSXML.Network;
+using System.Linq;
+using static dodohold.core.ZTOExpress.CreateOrderArgs;
+using TencentCloud.Lighthouse.V20200324.Models;
+using TencentCloud.Ame.V20190916.Models;
+using TencentCloud.Omics.V20221128.Models;
+using System.Threading.Channels;
+
+namespace molilian.api.Controllers
+{
+    [ApiController]
+    [Route("api/[controller]/[action]")]
+    public class TkEndpointController : ControllerBase
+    {
+        protected IHttpContextAccessor _accessor;
+        TaokeOpenCore core;
+        public TkEndpointController(IHttpContextAccessor accessor)
+        {
+            _accessor = accessor;
+            core = new TaokeOpenCore();
+        }
+
+        [HttpPost]
+        public async Task<ActionResult> ChangeSuspend([FromForm] JsonElement body)
+        {
+            int accountId = body.Read<int>("accountId");
+            string endpoint = body.Read<string>("accountId");
+            bool release = body.Read<bool>("release");
+
+            if (release)
+            {
+                TkEndpointManager.ReleaseSuspend(accountId, endpoint);
+            }
+            else
+            {
+                await TkEndpointManager.SuspendAsync(accountId, endpoint, TkEndpointManager.SuspendReason.Active);
+            }
+
+            return new APIResult(new
+            {
+                success = true,
+                action = release ? "ReleaseSuspend" : "Suspend",
+            });
+        }
+
+
+        [HttpPost]
+        public ActionResult GetStatus([FromForm] JsonElement body)
+        {
+            var ids = body.PathReadArray<int>("accountId[]");
+            var result = TkEndpointManager.GetStatus(ids);
+
+            return new APIResult(new
+            {
+                success = true,
+                data = result
+            });
+        }
+
+
+    }
 }

+ 46 - 46
molilian.api/Controllers/public/TracksController.cs

@@ -1,24 +1,24 @@
-using molilian.core;
-using dodohold.core;
-using Microsoft.AspNetCore.Mvc;
-using System.Text.Json;
-using TencentCloud.Ecm.V20190719.Models;
-using YunhuiKit;
-
-namespace molilian.api.Controllers
-{
-    [ApiController]
-    [Route("[controller]/[action]")]
-    public class TracksController : ControllerBase
-    {
-
-        protected IHttpContextAccessor _accessor;
-        public TracksController(IHttpContextAccessor accessor)
-        {
-            _accessor = accessor;
-        }
-
-        [HttpGet]
+using molilian.core;
+using dodohold.core;
+using Microsoft.AspNetCore.Mvc;
+using System.Text.Json;
+using TencentCloud.Ecm.V20190719.Models;
+using YunhuiKit;
+
+namespace molilian.api.Controllers
+{
+    [ApiController]
+    [Route("[controller]/[action]")]
+    public class TracksController : ControllerBase
+    {
+
+        protected IHttpContextAccessor _accessor;
+        public TracksController(IHttpContextAccessor accessor)
+        {
+            _accessor = accessor;
+        }
+
+        [HttpGet]
         public async Task<ActionResult> Track([FromQuery] int track_id, [FromQuery] string unique_id)
         {
             if (track_id <= 0)
@@ -59,28 +59,28 @@ namespace molilian.api.Controllers
                 return new APIResult(new { success = false, message = "invalid params or eventType" });
             }
             //var request = _accessor.HttpContext!.Request;
-            //string url = $"{request.Scheme}://{request.Host}{link.path}";
-            return new APIResult(new { success = true, message = "ok", url = link.path });
-        }
-
-        [HttpGet]
-        public async Task<ActionResult> Daily([FromQuery] int daysAgo = 1, [FromQuery] string reportDate = "")
-        {
-            DateTime date;
-            if (!string.IsNullOrWhiteSpace(reportDate) && DateTime.TryParse(reportDate, out var parsed))
-            {
-                date = parsed.Date;
-            }
-            else
-            {
-                if (daysAgo <= 0) daysAgo = 1;
-                date = DateTime.Now.AddDays(-daysAgo).Date;
-            }
-
-            int rows = await TracksCore.FlushDailyAsync(DateTime.Now.Date);
-            rows += await TracksCore.FlushDailyAsync(date);
-            return new APIResult(new { success = true, message = "ok", rows });
-
-        }
-    }
-}
+            //string url = $"{request.Scheme}://{request.Host}{link.path}";
+            return new APIResult(new { success = true, message = "ok", url = link.path });
+        }
+
+        [HttpGet]
+        public async Task<ActionResult> Daily([FromQuery] int daysAgo = 1, [FromQuery] string reportDate = "")
+        {
+            DateTime date;
+            if (!string.IsNullOrWhiteSpace(reportDate) && DateTime.TryParse(reportDate, out var parsed))
+            {
+                date = parsed.Date;
+            }
+            else
+            {
+                if (daysAgo <= 0) daysAgo = 1;
+                date = DateTime.Now.AddDays(-daysAgo).Date;
+            }
+
+            int rows = await TracksCore.FlushDailyAsync(DateTime.Now.Date);
+            rows += await TracksCore.FlushDailyAsync(date);
+            return new APIResult(new { success = true, message = "ok", rows });
+
+        }
+    }
+}

+ 46 - 46
molilian.api/Dockerfile

@@ -1,46 +1,46 @@
-# 请参阅 https://aka.ms/customizecontainer 以了解如何自定义调试容器,以及 Visual Studio 如何使用此 Dockerfile 生成映像以更快地进行调试。
-
-# 此阶段用于在快速模式(默认为调试配置)下从 VS 运行时
-FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
-#USER $APP_UID
-USER root
-
-WORKDIR /app
-EXPOSE 8080
-EXPOSE 8081
-
-
-# 此阶段用于生成服务项目
-FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
-ARG BUILD_CONFIGURATION=Release
-WORKDIR /src
-COPY ["nuget.config", "."]
-COPY ["molilian.api/molilian.api.csproj", "molilian.api/"]
-COPY ["molilian.core/molilian.core.csproj", "molilian.core/"]
-COPY ["taobao-sdk-netcore-auto_1719572210878-20240628-source/topsdk-netcore.csproj", "taobao-sdk-netcore-auto_1719572210878-20240628-source/"]
-RUN dotnet restore "./molilian.api/molilian.api.csproj"
-#RUN dotnet restore "./molilian.api/./molilian.api.csproj" --configfile "./nuget.config"
-COPY . .
-WORKDIR "/src/molilian.api"
-RUN dotnet build "./molilian.api.csproj" -c $BUILD_CONFIGURATION -o /app/build
-
-# 此阶段用于发布要复制到最终阶段的服务项目
-FROM build AS publish
-ARG BUILD_CONFIGURATION=Release
-RUN dotnet publish "./molilian.api.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
-
-# 此阶段在生产中使用,或在常规模式下从 VS 运行时使用(在不使用调试配置时为默认值)
-FROM base AS final
-WORKDIR /app
-COPY --from=publish /app/publish .
-
-# 设置区域设置和字符编码
-ENV LANG=en_US.UTF-8
-ENV LANGUAGE=en_US:en
-ENV LC_ALL=en_US.UTF-8
-
-# 设置时区
-RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
-RUN echo "Asia/Shanghai" > /etc/timezone
-
-ENTRYPOINT ["dotnet", "molilian.api.dll"]
+# 请参阅 https://aka.ms/customizecontainer 以了解如何自定义调试容器,以及 Visual Studio 如何使用此 Dockerfile 生成映像以更快地进行调试。
+
+# 此阶段用于在快速模式(默认为调试配置)下从 VS 运行时
+FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
+#USER $APP_UID
+USER root
+
+WORKDIR /app
+EXPOSE 8080
+EXPOSE 8081
+
+
+# 此阶段用于生成服务项目
+FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
+ARG BUILD_CONFIGURATION=Release
+WORKDIR /src
+COPY ["nuget.config", "."]
+COPY ["molilian.api/molilian.api.csproj", "molilian.api/"]
+COPY ["molilian.core/molilian.core.csproj", "molilian.core/"]
+COPY ["taobao-sdk-netcore-auto_1719572210878-20240628-source/topsdk-netcore.csproj", "taobao-sdk-netcore-auto_1719572210878-20240628-source/"]
+RUN dotnet restore "./molilian.api/molilian.api.csproj"
+#RUN dotnet restore "./molilian.api/./molilian.api.csproj" --configfile "./nuget.config"
+COPY . .
+WORKDIR "/src/molilian.api"
+RUN dotnet build "./molilian.api.csproj" -c $BUILD_CONFIGURATION -o /app/build
+
+# 此阶段用于发布要复制到最终阶段的服务项目
+FROM build AS publish
+ARG BUILD_CONFIGURATION=Release
+RUN dotnet publish "./molilian.api.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
+
+# 此阶段在生产中使用,或在常规模式下从 VS 运行时使用(在不使用调试配置时为默认值)
+FROM base AS final
+WORKDIR /app
+COPY --from=publish /app/publish .
+
+# 设置区域设置和字符编码
+ENV LANG=en_US.UTF-8
+ENV LANGUAGE=en_US:en
+ENV LC_ALL=en_US.UTF-8
+
+# 设置时区
+RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
+RUN echo "Asia/Shanghai" > /etc/timezone
+
+ENTRYPOINT ["dotnet", "molilian.api.dll"]

+ 39 - 39
molilian.api/Dockerfile.original

@@ -1,40 +1,40 @@
-#See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging.
-
-FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
-USER root
-WORKDIR /app
-EXPOSE 8080
-EXPOSE 8081
-
-FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
-ARG BUILD_CONFIGURATION=Release
-WORKDIR /src
-
-COPY ["nuget.config", "."]
-COPY ["molilian.api/molilian.api.csproj", "molilian.api/"]
-RUN dotnet restore "./molilian.api/./molilian.api.csproj" --configfile "./nuget.config"
-
-
-COPY . .
-WORKDIR "/src/molilian.api"
-RUN dotnet build "./molilian.api.csproj" -c $BUILD_CONFIGURATION -o /app/build
-
-FROM build AS publish
-ARG BUILD_CONFIGURATION=Release
-RUN dotnet publish "./molilian.api.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
-
-FROM base AS final
-WORKDIR /app
-COPY --from=publish /app/publish .
-
-
-# 设置区域设置和字符编码
-ENV LANG en_US.UTF-8
-ENV LANGUAGE en_US:en
-ENV LC_ALL en_US.UTF-8
-
-# 设置时区
-RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
-RUN echo "Asia/Shanghai" > /etc/timezone
-
+#See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging.
+
+FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
+USER root
+WORKDIR /app
+EXPOSE 8080
+EXPOSE 8081
+
+FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
+ARG BUILD_CONFIGURATION=Release
+WORKDIR /src
+
+COPY ["nuget.config", "."]
+COPY ["molilian.api/molilian.api.csproj", "molilian.api/"]
+RUN dotnet restore "./molilian.api/./molilian.api.csproj" --configfile "./nuget.config"
+
+
+COPY . .
+WORKDIR "/src/molilian.api"
+RUN dotnet build "./molilian.api.csproj" -c $BUILD_CONFIGURATION -o /app/build
+
+FROM build AS publish
+ARG BUILD_CONFIGURATION=Release
+RUN dotnet publish "./molilian.api.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
+
+FROM base AS final
+WORKDIR /app
+COPY --from=publish /app/publish .
+
+
+# 设置区域设置和字符编码
+ENV LANG en_US.UTF-8
+ENV LANGUAGE en_US:en
+ENV LC_ALL en_US.UTF-8
+
+# 设置时区
+RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
+RUN echo "Asia/Shanghai" > /etc/timezone
+
 ENTRYPOINT ["dotnet", "molilian.api.dll"]

+ 109 - 109
molilian.api/Program.cs

@@ -1,109 +1,109 @@
-using molilian.core;
-using dodohold.core;
-using Microsoft.Extensions.DependencyInjection.Extensions;
-using Microsoft.OpenApi.Models;
-using System.Security.Cryptography.Xml;
-using Microsoft.Extensions.Diagnostics.HealthChecks;
-using Microsoft.AspNetCore.Diagnostics.HealthChecks;
-
-var builder = WebApplication.CreateBuilder(args);
-builder.Services.Init();
-
-builder.Services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
-builder.Services.AddControllers(option =>
-{
-    option.Filters.Add(typeof(ResponseExceptionFilterAttribute));
-    option.Filters.Add(typeof(MyAuthorizationFactory.MyActionFilter));
-});
-
-//builder.Services.AddHostedService<TaskWorker>();
-
-
-// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
-builder.Services.AddEndpointsApiExplorer();
-
-builder.Services.AddSwaggerGen(option =>
-{
-    option.AddSecurityDefinition("Authorization", new OpenApiSecurityScheme()
-    {
-        Description = "请输入 token",
-        Name = "Authorization",
-        In = ParameterLocation.Header,
-        Type = SecuritySchemeType.ApiKey
-    });
-
-    option.AddSecurityRequirement(new OpenApiSecurityRequirement
-    {
-        {
-            new OpenApiSecurityScheme
-            {
-                Reference = new OpenApiReference()
-                {
-                    Type = ReferenceType.SecurityScheme,
-                    Id = "Bearer"
-                }
-            }, Array.Empty<string>()
-        }
-    });
-});
-
-
-builder.Services.AddCors(options =>
-{
-    options.AddPolicy("AllowAll",
-        builder =>
-        {
-            builder.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader();
-        });
-});
-
-
-// 添加健康检查服务
-builder.Services.AddHealthChecks().AddCheck("self", () => HealthCheckResult.Healthy());
-
-
-var app = builder.Build();
-
-app.MapHealthChecks("/health", new HealthCheckOptions
-{
-    ResponseWriter = async (context, report) =>
-    {
-        context.Response.ContentType = "application/json";
-        await context.Response.WriteAsJsonAsync(new
-        {
-            status = report.Status.ToString(),
-            checks = report.Entries.Select(e => new
-            {
-                name = e.Key,
-                status = e.Value.Status.ToString(),
-                description = e.Value.Description
-            })
-        });
-    }
-});
-
-
-// Configure the HTTP request pipeline.
-if (app.Environment.IsDevelopment())
-{
-    app.UseSwagger();
-    app.UseSwaggerUI();
-}
-
-//app.UseSwagger();
-//app.UseSwaggerUI();
-
-app.UseRequestLocalization();
-
-// 添加下面这行
-System.Text.Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance);
-
-app.UseHttpsRedirection();
-
-app.UseAuthorization();
-
-app.MapControllers();
-
-app.UseCors("AllowAll"); // 启用 CORS 中间件
-
-app.Run();
+using molilian.core;
+using dodohold.core;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+using Microsoft.OpenApi.Models;
+using System.Security.Cryptography.Xml;
+using Microsoft.Extensions.Diagnostics.HealthChecks;
+using Microsoft.AspNetCore.Diagnostics.HealthChecks;
+
+var builder = WebApplication.CreateBuilder(args);
+builder.Services.Init();
+
+builder.Services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
+builder.Services.AddControllers(option =>
+{
+    option.Filters.Add(typeof(ResponseExceptionFilterAttribute));
+    option.Filters.Add(typeof(MyAuthorizationFactory.MyActionFilter));
+});
+
+//builder.Services.AddHostedService<TaskWorker>();
+
+
+// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
+builder.Services.AddEndpointsApiExplorer();
+
+builder.Services.AddSwaggerGen(option =>
+{
+    option.AddSecurityDefinition("Authorization", new OpenApiSecurityScheme()
+    {
+        Description = "请输入 token",
+        Name = "Authorization",
+        In = ParameterLocation.Header,
+        Type = SecuritySchemeType.ApiKey
+    });
+
+    option.AddSecurityRequirement(new OpenApiSecurityRequirement
+    {
+        {
+            new OpenApiSecurityScheme
+            {
+                Reference = new OpenApiReference()
+                {
+                    Type = ReferenceType.SecurityScheme,
+                    Id = "Bearer"
+                }
+            }, Array.Empty<string>()
+        }
+    });
+});
+
+
+builder.Services.AddCors(options =>
+{
+    options.AddPolicy("AllowAll",
+        builder =>
+        {
+            builder.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader();
+        });
+});
+
+
+// 添加健康检查服务
+builder.Services.AddHealthChecks().AddCheck("self", () => HealthCheckResult.Healthy());
+
+
+var app = builder.Build();
+
+app.MapHealthChecks("/health", new HealthCheckOptions
+{
+    ResponseWriter = async (context, report) =>
+    {
+        context.Response.ContentType = "application/json";
+        await context.Response.WriteAsJsonAsync(new
+        {
+            status = report.Status.ToString(),
+            checks = report.Entries.Select(e => new
+            {
+                name = e.Key,
+                status = e.Value.Status.ToString(),
+                description = e.Value.Description
+            })
+        });
+    }
+});
+
+
+// Configure the HTTP request pipeline.
+if (app.Environment.IsDevelopment())
+{
+    app.UseSwagger();
+    app.UseSwaggerUI();
+}
+
+//app.UseSwagger();
+//app.UseSwaggerUI();
+
+app.UseRequestLocalization();
+
+// 添加下面这行
+System.Text.Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance);
+
+app.UseHttpsRedirection();
+
+app.UseAuthorization();
+
+app.MapControllers();
+
+app.UseCors("AllowAll"); // 启用 CORS 中间件
+
+app.Run();

+ 17 - 17
molilian.api/Properties/PublishProfiles/latest.pubxml

@@ -1,18 +1,18 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-https://go.microsoft.com/fwlink/?LinkID=208121.
--->
-<Project>
-  <PropertyGroup>
-    <WebPublishMethod>Custom</WebPublishMethod>
-    <DockerPublish>true</DockerPublish>
-    <RegistryUrl>https://ccr.ccs.tencentyun.com/shaobin</RegistryUrl>
-    <PublishProvider>ContainerRegistry</PublishProvider>
-    <LastUsedBuildConfiguration>Release</LastUsedBuildConfiguration>
-    <LastUsedPlatform>Any CPU</LastUsedPlatform>
-    <ProjectGuid>5a0a088d-3cf8-4378-9139-68895e5c89df</ProjectGuid>
-    <_TargetId>DockerCustomContainerRegistry</_TargetId>
-    <PublishImageTag>latest</PublishImageTag>
-  
-</PropertyGroup>
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+https://go.microsoft.com/fwlink/?LinkID=208121.
+-->
+<Project>
+  <PropertyGroup>
+    <WebPublishMethod>Custom</WebPublishMethod>
+    <DockerPublish>true</DockerPublish>
+    <RegistryUrl>https://ccr.ccs.tencentyun.com/shaobin</RegistryUrl>
+    <PublishProvider>ContainerRegistry</PublishProvider>
+    <LastUsedBuildConfiguration>Release</LastUsedBuildConfiguration>
+    <LastUsedPlatform>Any CPU</LastUsedPlatform>
+    <ProjectGuid>5a0a088d-3cf8-4378-9139-68895e5c89df</ProjectGuid>
+    <_TargetId>DockerCustomContainerRegistry</_TargetId>
+    <PublishImageTag>latest</PublishImageTag>
+  
+</PropertyGroup>
 </Project>

Різницю між файлами не показано, бо вона завелика
+ 0 - 7
molilian.api/Properties/PublishProfiles/latest.pubxml.user


+ 14 - 14
molilian.api/Properties/PublishProfiles/tester.pubxml

@@ -1,17 +1,17 @@
-<?xml version="1.0" encoding="utf-8"?>
+<?xml version="1.0" encoding="utf-8"?>
 <!--
 https://go.microsoft.com/fwlink/?LinkID=208121.
--->
-<Project>
-  <PropertyGroup>
-    <WebPublishMethod>Custom</WebPublishMethod>
-    <DockerPublish>true</DockerPublish>
-    <RegistryUrl>https://ccr.ccs.tencentyun.com/shaobin</RegistryUrl>
-    <PublishImageTag>tester</PublishImageTag>
-    <PublishProvider>ContainerRegistry</PublishProvider>
-    <LastUsedBuildConfiguration>Release</LastUsedBuildConfiguration>
-    <LastUsedPlatform>Any CPU</LastUsedPlatform>
-    <ProjectGuid>5a0a088d-3cf8-4378-9139-68895e5c89df</ProjectGuid>
-    <_TargetId>DockerCustomContainerRegistry</_TargetId>
-  </PropertyGroup>
+-->
+<Project>
+  <PropertyGroup>
+    <WebPublishMethod>Custom</WebPublishMethod>
+    <DockerPublish>true</DockerPublish>
+    <RegistryUrl>https://ccr.ccs.tencentyun.com/shaobin</RegistryUrl>
+    <PublishImageTag>tester</PublishImageTag>
+    <PublishProvider>ContainerRegistry</PublishProvider>
+    <LastUsedBuildConfiguration>Release</LastUsedBuildConfiguration>
+    <LastUsedPlatform>Any CPU</LastUsedPlatform>
+    <ProjectGuid>5a0a088d-3cf8-4378-9139-68895e5c89df</ProjectGuid>
+    <_TargetId>DockerCustomContainerRegistry</_TargetId>
+  </PropertyGroup>
 </Project>

Різницю між файлами не показано, бо вона завелика
+ 0 - 7
molilian.api/Properties/PublishProfiles/tester.pubxml.user


+ 80 - 80
molilian.api/Properties/launchSettings.json

@@ -1,81 +1,81 @@
-{
-  "profiles": {
-    "http": {
-      "commandName": "Project",
-      "launchBrowser": true,
-      "launchUrl": "swagger",
-      "environmentVariables2": {
-        "ASPNETCORE_ENVIRONMENT": "Development",
-        "EndPoint": "coupon",
-        "NtfyServer": "https://ntfy.yunhui800.com/vozQub8a78ABLqqY",
-        "ANPush": "",
-        "QYWeixinPush": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=6e745143-62b1-4d33-a85f-9a76e6c2c239",
-        "DBType": "MySQL",
-        "DBConfig": "Server=rm-2ze01a91isr5k08382o.mysql.rds.aliyuncs.com; Port=3306; Database=coupon; Uid=coupon; Pwd=67ktWBmw5G4yMs4J;SslMode=None;CharSet=utf8mb4;ConnectionTimeout=60;",
-        "RedisConfig": "47.94.39.2:6379,password=pKBiS4ka2IpXay123,defaultDatabase=0,idleTimeout=20000,preheat=3,tryit=2,ssl=false,prefix=coupon",
-        "CenterDB": "",
-        "CenterRedis": ""
-      },
-      "environmentVariables": {
-        "ASPNETCORE_ENVIRONMENT": "Development",
-        "EndPoint": "bj",
-        "NtfyServer": "https://ntfy.yunhui800.com/5Sq9BytXXM5WDY3G",
-        "QYWeixinPush": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=6e745143-62b1-4d33-a85f-9a76e6c2c239",
-        "ANPush": "",
-        "DBType": "MySQL",
-        "DBConfig": "Server=rm-2ze49fcn8e28e6gzu3o.rwlb.rds.aliyuncs.com; Port=3306; Database=taoke; Uid=taoke; Pwd=67ktWBmw5G4yMs4J;SslMode=None;CharSet=utf8mb4;ConnectionTimeout=60",
-        "RedisConfig": "101.200.152.61:6379,password=pKBiS4ka2IpXayIdcx00,defaultDatabase=0,idleTimeout=20000,preheat=3,tryit=2,ssl=false,prefix=webhook",
-        "CenterDB": "",
-        "CenterRedis": ""
-      }, 
-      "environmentVariables1": {
-        "ASPNETCORE_ENVIRONMENT": "Development",
-        "EndPoint": "cadmin",
-        "NtfyServer": "https://ntfy.yunhui800.com/5Sq9BytXXM5WDY3G",
-        "QYWeixinPush": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=6e745143-62b1-4d33-a85f-9a76e6c2c239",
-        "ANPush": "",
-        "DBType": "MySQL",
-        "DBConfig": "Server=rm-2ze49fcn8e28e6gzu3o.rwlb.rds.aliyuncs.com; Port=3306; Database=coupon; Uid=coupon; Pwd=67ktWBmw5G4yMs4J;SslMode=None;CharSet=utf8mb4;ConnectionTimeout=60;",
-        "RedisConfig": "8.140.49.136:6379,password=pKBiS4ka2IpXayIdcx00,defaultDatabase=0,idleTimeout=20000,preheat=3,tryit=2,ssl=false,prefix=coupon",
-        "CenterDB": "Server=rm-2ze49fcn8e28e6gzu3o.rwlb.rds.aliyuncs.com; Port=3306; Database=taoke; Uid=taoke; Pwd=67ktWBmw5G4yMs4J;SslMode=None;CharSet=utf8mb4;ConnectionTimeout=60",
-        "CenterRedis": "101.200.152.61:6379,password=pKBiS4ka2IpXayIdcx00,defaultDatabase=0,idleTimeout=20000,preheat=3,tryit=2,ssl=false,prefix=webhook"
-      },
-      "dotnetRunMessages": true,
-      "applicationUrl": "http://localhost:8186"
-    },
-    "https": {
-      "commandName": "Project",
-      "launchBrowser": true,
-      "launchUrl": "swagger",
-      "environmentVariables": {
-        "ASPNETCORE_ENVIRONMENT": "Development"
-      },
-      "dotnetRunMessages": true,
-      "applicationUrl": "https://localhost:7159;http://localhost:8186"
-    },
-    "IIS Express": {
-      "commandName": "IISExpress",
-      "launchBrowser": true,
-      "launchUrl": "swagger",
-      "environmentVariables": {
-        "ASPNETCORE_ENVIRONMENT": "Development"
-      }
-    },
-    "Docker": {
-      "commandName": "Docker",
-      "launchBrowser": true,
-      "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/swagger",
-      "publishAllPorts": true,
-      "useSSL": true
-    }
-  },
-  "$schema": "http://json.schemastore.org/launchsettings.json",
-  "iisSettings": {
-    "windowsAuthentication": false,
-    "anonymousAuthentication": true,
-    "iisExpress": {
-      "applicationUrl": "http://localhost:16009",
-      "sslPort": 44353
-    }
-  }
+{
+  "profiles": {
+    "http": {
+      "commandName": "Project",
+      "launchBrowser": true,
+      "launchUrl": "swagger",
+      "environmentVariables": {
+        "ASPNETCORE_ENVIRONMENT": "Development",
+        "EndPoint": "coupon",
+        "NtfyServer": "https://ntfy.yunhui800.com/vozQub8a78ABLqqY",
+        "ANPush": "",
+        "QYWeixinPush": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=6e745143-62b1-4d33-a85f-9a76e6c2c239",
+        "DBType": "MySQL",
+        "DBConfig": "Server=rm-2ze01a91isr5k08382o.mysql.rds.aliyuncs.com; Port=3306; Database=coupon; Uid=coupon; Pwd=67ktWBmw5G4yMs4J;SslMode=None;CharSet=utf8mb4;ConnectionTimeout=60;",
+        "RedisConfig": "47.94.39.2:6379,password=pKBiS4ka2IpXay123,defaultDatabase=0,idleTimeout=20000,preheat=3,tryit=2,ssl=false,prefix=coupon",
+        "CenterDB": "",
+        "CenterRedis": ""
+      },
+      "environmentVariables2": {
+        "ASPNETCORE_ENVIRONMENT": "Development",
+        "EndPoint": "bj",
+        "NtfyServer": "https://ntfy.yunhui800.com/5Sq9BytXXM5WDY3G",
+        "QYWeixinPush": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=6e745143-62b1-4d33-a85f-9a76e6c2c239",
+        "ANPush": "",
+        "DBType": "MySQL",
+        "DBConfig": "Server=rm-2ze49fcn8e28e6gzu3o.rwlb.rds.aliyuncs.com; Port=3306; Database=taoke; Uid=taoke; Pwd=67ktWBmw5G4yMs4J;SslMode=None;CharSet=utf8mb4;ConnectionTimeout=60",
+        "RedisConfig": "101.200.152.61:6379,password=pKBiS4ka2IpXayIdcx00,defaultDatabase=0,idleTimeout=20000,preheat=3,tryit=2,ssl=false,prefix=webhook",
+        "CenterDB": "",
+        "CenterRedis": ""
+      }, 
+      "environmentVariables1": {
+        "ASPNETCORE_ENVIRONMENT": "Development",
+        "EndPoint": "cadmin",
+        "NtfyServer": "https://ntfy.yunhui800.com/5Sq9BytXXM5WDY3G",
+        "QYWeixinPush": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=6e745143-62b1-4d33-a85f-9a76e6c2c239",
+        "ANPush": "",
+        "DBType": "MySQL",
+        "DBConfig": "Server=rm-2ze49fcn8e28e6gzu3o.rwlb.rds.aliyuncs.com; Port=3306; Database=coupon; Uid=coupon; Pwd=67ktWBmw5G4yMs4J;SslMode=None;CharSet=utf8mb4;ConnectionTimeout=60;",
+        "RedisConfig": "8.140.49.136:6379,password=pKBiS4ka2IpXayIdcx00,defaultDatabase=0,idleTimeout=20000,preheat=3,tryit=2,ssl=false,prefix=coupon",
+        "CenterDB": "Server=rm-2ze49fcn8e28e6gzu3o.rwlb.rds.aliyuncs.com; Port=3306; Database=taoke; Uid=taoke; Pwd=67ktWBmw5G4yMs4J;SslMode=None;CharSet=utf8mb4;ConnectionTimeout=60",
+        "CenterRedis": "101.200.152.61:6379,password=pKBiS4ka2IpXayIdcx00,defaultDatabase=0,idleTimeout=20000,preheat=3,tryit=2,ssl=false,prefix=webhook"
+      },
+      "dotnetRunMessages": true,
+      "applicationUrl": "http://localhost:8186"
+    },
+    "https": {
+      "commandName": "Project",
+      "launchBrowser": true,
+      "launchUrl": "swagger",
+      "environmentVariables": {
+        "ASPNETCORE_ENVIRONMENT": "Development"
+      },
+      "dotnetRunMessages": true,
+      "applicationUrl": "https://localhost:7159;http://localhost:8186"
+    },
+    "IIS Express": {
+      "commandName": "IISExpress",
+      "launchBrowser": true,
+      "launchUrl": "swagger",
+      "environmentVariables": {
+        "ASPNETCORE_ENVIRONMENT": "Development"
+      }
+    },
+    "Docker": {
+      "commandName": "Docker",
+      "launchBrowser": true,
+      "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/swagger",
+      "publishAllPorts": true,
+      "useSSL": true
+    }
+  },
+  "$schema": "http://json.schemastore.org/launchsettings.json",
+  "iisSettings": {
+    "windowsAuthentication": false,
+    "anonymousAuthentication": true,
+    "iisExpress": {
+      "applicationUrl": "http://localhost:16009",
+      "sslPort": 44353
+    }
+  }
 }

+ 28 - 28
molilian.api/docker-compose.yml

@@ -1,29 +1,29 @@
-#docker login -u dodohold -p u9BkH5pxkACn43et docker.yunhui800.com
-version: "3"
-services:
-    molilian:
-        container_name: molilian
-        image: docker.yunhui800.com/molilian:latest
-        restart: always
-        ports:
-            - 5002:8080
-        volumes:
-            - ./logs:/app/log
-            - /etc/localtime:/etc/localtime:ro
-            - /var/run/docker.sock:/var/run/docker.sock # 挂载宿主机的 Docker UNIX 套接字
-        devices:
-            - /dev/rtc:/dev/rtc:ro
-        environment:
-            # 通知服务器
-            - NtfyServer=
-            - ANPush=
-            
-            # 数据库
-            - DBConfig=Server=host.docker.internal; Port=3306; Database=taoke; Uid=taoke; Pwd=67ktWBmw5G4yMs4J;SslMode=None;CharSet=utf8mb4;ConnectionTimeout=60;
-            - DBType=MySQL
-
-            # redis 内存数据库
-            - RedisConfig=host.docker.internal:6379,password=ef4f629b9,defaultDatabase=0,idleTimeout=20000,preheat=3,tryit=2,ssl=false,prefix=webhook
-            
-            - CenterDB=
+#docker login -u dodohold -p u9BkH5pxkACn43et docker.yunhui800.com
+version: "3"
+services:
+    molilian:
+        container_name: molilian
+        image: docker.yunhui800.com/molilian:latest
+        restart: always
+        ports:
+            - 5002:8080
+        volumes:
+            - ./logs:/app/log
+            - /etc/localtime:/etc/localtime:ro
+            - /var/run/docker.sock:/var/run/docker.sock # 挂载宿主机的 Docker UNIX 套接字
+        devices:
+            - /dev/rtc:/dev/rtc:ro
+        environment:
+            # 通知服务器
+            - NtfyServer=
+            - ANPush=
+            
+            # 数据库
+            - DBConfig=Server=host.docker.internal; Port=3306; Database=taoke; Uid=taoke; Pwd=67ktWBmw5G4yMs4J;SslMode=None;CharSet=utf8mb4;ConnectionTimeout=60;
+            - DBType=MySQL
+
+            # redis 内存数据库
+            - RedisConfig=host.docker.internal:6379,password=ef4f629b9,defaultDatabase=0,idleTimeout=20000,preheat=3,tryit=2,ssl=false,prefix=webhook
+            
+            - CenterDB=
             - CenterRedis=

+ 24 - 24
molilian.api/molilian.api.csproj

@@ -1,24 +1,24 @@
-<Project Sdk="Microsoft.NET.Sdk.Web">
-
-  <PropertyGroup>
-    <TargetFramework>net8.0</TargetFramework>
-    <Nullable>enable</Nullable>
-    <ImplicitUsings>enable</ImplicitUsings>
-    <UserSecretsId>c3335e1a-a292-4152-b069-20530f5a6b83</UserSecretsId>
-    <DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
-    <ContainerImageTags>$(Version);latest</ContainerImageTags>
-  </PropertyGroup>
-
-  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'" />
-
-  <ItemGroup>
-    <PackageReference Include="dodohold.core" Version="1.0.1.27" />
-    <PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.20.0-Preview.1" />
-    <PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
-  </ItemGroup>
-
-  <ItemGroup>
-    <ProjectReference Include="..\molilian.core\molilian.core.csproj" />
-  </ItemGroup>
-
-</Project>
+<Project Sdk="Microsoft.NET.Sdk.Web">
+
+  <PropertyGroup>
+    <TargetFramework>net8.0</TargetFramework>
+    <Nullable>enable</Nullable>
+    <ImplicitUsings>enable</ImplicitUsings>
+    <UserSecretsId>c3335e1a-a292-4152-b069-20530f5a6b83</UserSecretsId>
+    <DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
+    <ContainerImageTags>$(Version);latest</ContainerImageTags>
+  </PropertyGroup>
+
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'" />
+
+  <ItemGroup>
+    <PackageReference Include="dodohold.core" Version="1.0.1.27" />
+    <PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.20.0-Preview.1" />
+    <PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\molilian.core\molilian.core.csproj" />
+  </ItemGroup>
+
+</Project>

+ 9 - 9
molilian.api/molilian.api.csproj.user

@@ -1,10 +1,10 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
-  <PropertyGroup>
-    <ActiveDebugProfile>http</ActiveDebugProfile>
-    <NameOfLastUsedPublishProfile>E:\project\2024\molilian\server\molilian.api\Properties\PublishProfiles\latest.pubxml</NameOfLastUsedPublishProfile>
-  </PropertyGroup>
-  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
-    <DebuggerFlavor>ProjectDebugger</DebuggerFlavor>
-  </PropertyGroup>
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <PropertyGroup>
+    <ActiveDebugProfile>http</ActiveDebugProfile>
+    <NameOfLastUsedPublishProfile>E:\project\2024\molilian\server\molilian.api\Properties\PublishProfiles\latest.pubxml</NameOfLastUsedPublishProfile>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
+    <DebuggerFlavor>ProjectDebugger</DebuggerFlavor>
+  </PropertyGroup>
 </Project>

+ 6 - 6
molilian.api/molilian.api.http

@@ -1,6 +1,6 @@
-@molilian.api_HostAddress = http://localhost:5186
-
-GET {{molilian.api_HostAddress}}/weatherforecast/
-Accept: application/json
-
-###
+@molilian.api_HostAddress = http://localhost:5186
+
+GET {{molilian.api_HostAddress}}/weatherforecast/
+Accept: application/json
+
+###

+ 30 - 30
molilian.core/Authorization/AccessTokenCore.cs

@@ -1,30 +1,30 @@
-using dodohold.core;
-
-
-namespace molilian.core
-{
-    public class AccessTokenCore
-    {
-        public AccessTokenDTO Get(string domain, string ticket)
-        {
-            string key = $"token:{ticket}";
-            
-            return CenterHub.Redis.Get<AccessTokenDTO>(key);
-        }
-
-        public int Delete(string domain, string ticket)
-        {
-
-            string key = $"token:{ticket}";
-            return Convert.ToInt32(CenterHub.Redis.Del(key));
-        }
-
-        internal bool Create(AccessTokenDTO token)
-        {
-            string key = $"token:{token.AccessTicket}";
-            return CenterHub.Redis.Set(key, token, 86400 * 30);
-        }
-
-
-    }
-}
+using dodohold.core;
+
+
+namespace molilian.core
+{
+    public class AccessTokenCore
+    {
+        public AccessTokenDTO Get(string domain, string ticket)
+        {
+            string key = $"token:{ticket}";
+            
+            return CenterHub.Redis.Get<AccessTokenDTO>(key);
+        }
+
+        public int Delete(string domain, string ticket)
+        {
+
+            string key = $"token:{ticket}";
+            return Convert.ToInt32(CenterHub.Redis.Del(key));
+        }
+
+        internal bool Create(AccessTokenDTO token)
+        {
+            string key = $"token:{token.AccessTicket}";
+            return CenterHub.Redis.Set(key, token, 86400 * 30);
+        }
+
+
+    }
+}

+ 19 - 19
molilian.core/Authorization/AdminPermissionCore.cs

@@ -1,19 +1,19 @@
-using Microsoft.AspNetCore.Http;
-using Microsoft.AspNetCore.Mvc.Controllers;
-using Microsoft.AspNetCore.Mvc.Filters;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using dodohold.core;
-
-
-namespace molilian.core
-{
-    public class AdminPermissionCore
-    {
-        /*
-        accessToken.Permissions = permissionCore.GetList(new { id = roleIds.ConvertToIntArray() })?.List;
-         */
-    }
-}
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc.Controllers;
+using Microsoft.AspNetCore.Mvc.Filters;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using dodohold.core;
+
+
+namespace molilian.core
+{
+    public class AdminPermissionCore
+    {
+        /*
+        accessToken.Permissions = permissionCore.GetList(new { id = roleIds.ConvertToIntArray() })?.List;
+         */
+    }
+}

+ 89 - 89
molilian.core/Authorization/Provider/APIProvider.cs

@@ -1,89 +1,89 @@
-using dodohold.core;
-using Microsoft.AspNetCore.Http;
-using Microsoft.AspNetCore.Mvc.Filters;
-
-namespace molilian.core
-{
-    /// <summary>
-    /// Authorization 授权
-    /// </summary>
-    public class APIProvider : IAuthorizationProvider
-    {
-        public string Domain => "api";
-
-        private static string _consumerKey = "consumerkey";
-        private static string _consumerSecret = "0512ef0d33f0f7d6";
-        AccessTokenCore tokenCore = new AccessTokenCore();
-
-
-
-        /// <summary>
-        /// 验证是否未授权
-        /// </summary>
-        /// <param name="request">客户请求的token和scope(域)</param>
-        /// <param name="logicAttr">受访问的权限属性</param>
-        /// <returns></returns>
-        public bool Verified(ActionExecutingContext context, (string ticket, string domain) request, MyAuthorizeAttribute logicAttr)
-        {
-            if (string.IsNullOrEmpty(request.ticket)) { return false; }
-            if (!string.IsNullOrEmpty(logicAttr.Domain) && !logicAttr.Domain.Equals(request.domain)) { return false; }
-
-            IAccessTokenDTO accessToken = Get(request.domain, request.ticket);
-            if (accessToken == null) { return false; }
-            if (accessToken.ExpiresIn == 0) { return true; }
-            DateTime expiresTime = (accessToken.create_time + accessToken.ExpiresIn).Convert2Datetime();
-            if (expiresTime > DateTime.Now) { return true; }
-            return false;
-        }
-
-
-        public IAccessTokenDTO Get(HttpContext context)
-        {
-            var (accessToken, domain) = context.GetAccessToken();
-            return Get(domain, accessToken);
-        }
-
-        /// <summary>
-        /// 获取token缓存(从内存缓存中)
-        /// </summary>
-        /// <param name="token">客户请求的token</param>
-        /// <returns></returns>
-        public IAccessTokenDTO Get(string domain, string ticket)
-        {
-            var accessToken = tokenCore.Get(domain, ticket);
-            return accessToken;
-        }
-        public int Revoke(string domain, string ticket)
-        {
-            return tokenCore.Delete(domain, ticket);
-        }
-
-        public IAccessTokenDTO Create(long id, string key, string secret, string roles, string state, string domain, string clientIp)
-        {
-            //TODO  暂时就一个客户端授权码,以后做成数据库的
-            if (string.IsNullOrEmpty(key) || !key.Equals(_consumerKey)) { return null; }
-            if (string.IsNullOrEmpty(secret) || !secret.Equals(_consumerSecret)) { return null; }
-
-            AccessTokenDTO token = Generate(id, key, state, domain, clientIp);
-            tokenCore.Create(token);
-            return token;
-        }
-
-        public AccessTokenDTO Generate(long id, string accessKey, string state, string domain, string clientIp)
-        {
-            DateTime now = DateTime.Now;
-            AccessTokenDTO token = new AccessTokenDTO
-            {
-                AccessId = id,
-                AccessKey = accessKey,
-                create_time = now.Convert2UnixTimestamp(),
-                ExpiresIn = 0,
-                AccessState = state,
-                Domain = domain,
-                ClientIp = clientIp,
-                AccessTicket = $"{accessKey}|{domain}|{now}|{clientIp}".ToAES256()
-            };
-            return token;
-        }
-    }
-}
+using dodohold.core;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc.Filters;
+
+namespace molilian.core
+{
+    /// <summary>
+    /// Authorization 授权
+    /// </summary>
+    public class APIProvider : IAuthorizationProvider
+    {
+        public string Domain => "api";
+
+        private static string _consumerKey = "consumerkey";
+        private static string _consumerSecret = "0512ef0d33f0f7d6";
+        AccessTokenCore tokenCore = new AccessTokenCore();
+
+
+
+        /// <summary>
+        /// 验证是否未授权
+        /// </summary>
+        /// <param name="request">客户请求的token和scope(域)</param>
+        /// <param name="logicAttr">受访问的权限属性</param>
+        /// <returns></returns>
+        public bool Verified(ActionExecutingContext context, (string ticket, string domain) request, MyAuthorizeAttribute logicAttr)
+        {
+            if (string.IsNullOrEmpty(request.ticket)) { return false; }
+            if (!string.IsNullOrEmpty(logicAttr.Domain) && !logicAttr.Domain.Equals(request.domain)) { return false; }
+
+            IAccessTokenDTO accessToken = Get(request.domain, request.ticket);
+            if (accessToken == null) { return false; }
+            if (accessToken.ExpiresIn == 0) { return true; }
+            DateTime expiresTime = (accessToken.create_time + accessToken.ExpiresIn).Convert2Datetime();
+            if (expiresTime > DateTime.Now) { return true; }
+            return false;
+        }
+
+
+        public IAccessTokenDTO Get(HttpContext context)
+        {
+            var (accessToken, domain) = context.GetAccessToken();
+            return Get(domain, accessToken);
+        }
+
+        /// <summary>
+        /// 获取token缓存(从内存缓存中)
+        /// </summary>
+        /// <param name="token">客户请求的token</param>
+        /// <returns></returns>
+        public IAccessTokenDTO Get(string domain, string ticket)
+        {
+            var accessToken = tokenCore.Get(domain, ticket);
+            return accessToken;
+        }
+        public int Revoke(string domain, string ticket)
+        {
+            return tokenCore.Delete(domain, ticket);
+        }
+
+        public IAccessTokenDTO Create(long id, string key, string secret, string roles, string state, string domain, string clientIp)
+        {
+            //TODO  暂时就一个客户端授权码,以后做成数据库的
+            if (string.IsNullOrEmpty(key) || !key.Equals(_consumerKey)) { return null; }
+            if (string.IsNullOrEmpty(secret) || !secret.Equals(_consumerSecret)) { return null; }
+
+            AccessTokenDTO token = Generate(id, key, state, domain, clientIp);
+            tokenCore.Create(token);
+            return token;
+        }
+
+        public AccessTokenDTO Generate(long id, string accessKey, string state, string domain, string clientIp)
+        {
+            DateTime now = DateTime.Now;
+            AccessTokenDTO token = new AccessTokenDTO
+            {
+                AccessId = id,
+                AccessKey = accessKey,
+                create_time = now.Convert2UnixTimestamp(),
+                ExpiresIn = 0,
+                AccessState = state,
+                Domain = domain,
+                ClientIp = clientIp,
+                AccessTicket = $"{accessKey}|{domain}|{now}|{clientIp}".ToAES256()
+            };
+            return token;
+        }
+    }
+}

+ 142 - 142
molilian.core/Authorization/Provider/AdminProvider.cs

@@ -1,142 +1,142 @@
-using Microsoft.AspNetCore.Http;
-using Microsoft.AspNetCore.Mvc.Controllers;
-using Microsoft.AspNetCore.Mvc.Filters;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using dodohold.core;
-
-
-namespace molilian.core
-{
-    public class AdminProvider : IAuthorizationProvider
-    {
-        public string Domain => "admin";
-
-        AccessTokenCore tokenCore = new AccessTokenCore();
-        AdminPermissionCore permissionCore = new AdminPermissionCore();
-
-
-        /// <summary>
-        /// 验证是否未授权
-        /// </summary>
-        /// <param name="request">客户请求的token和scope(域)</param>
-        /// <param name="logicAttr">受访问的权限属性</param>
-        /// <returns></returns>
-        public bool Verified(ActionExecutingContext context, (string ticket, string domain) request, MyAuthorizeAttribute logicAttr)
-        {
-            //tikect 为空,直接返回 false
-            if (string.IsNullOrEmpty(request.ticket)) { return false; }
-
-            //如果接口里面要求验证domain,就判断是否匹配当前domain
-            if (!string.IsNullOrEmpty(logicAttr.Domain) && !logicAttr.Domain.Equals(request.domain)) { return false; }
-
-
-            AccessTokenDTO accessTicket = (AccessTokenDTO)Get(request.domain, request.ticket);
-            //从内存中获取ticket,没有就返回失败
-            if (accessTicket == null) { return false; }
-
-            //tikcet 设置永不过期,就直接返回 true
-            if (accessTicket.ExpiresIn == 0) { return true; }
-            DateTime expiresTime = (accessTicket.create_time + accessTicket.ExpiresIn).Convert2Datetime();
-
-            //ticket 有效期还没到也返回true
-            if (expiresTime > DateTime.Now) { return true; }
-
-            //这里拿到 actionName 和 actionMethod ,但是实际上并没有去验证
-            string actionName = null;
-            if (context.ActionDescriptor is ControllerActionDescriptor actionDescriptor)
-            {
-                actionName = actionDescriptor.ActionName;
-            }
-            string actionMethod = context.HttpContext.Request.Method;
-
-
-            //permission == null 就是没授权咯
-            var permission = accessTicket.Permissions.Where(s => s.ResourceFlag.Equals(actionName) && s.ActionFlag.Equals(actionMethod)).FirstOrDefault();
-            if (permission == null) { return false; }
-
-            return false;
-        }
-
-
-        public IAccessTokenDTO Get(HttpContext context)
-        {
-            var (accessToken, domain) = context.GetAccessToken();
-            return Get(domain, accessToken);
-        }
-
-        /// <summary>
-        /// 获取token缓存(从内存缓存中)
-        /// </summary>
-        /// <param name="token">客户请求的token</param>
-        /// <returns></returns>
-        public IAccessTokenDTO Get(string domain, string accessTicket)
-        {
-            var accessToken = tokenCore.Get(domain, accessTicket);
-            if (accessToken == null) { return null; }
-
-            //todo 这里除了获取accesstoken以外 还得获取一下授权
-            string roleIds = accessToken.AccessRoles;
-            //if (!string.IsNullOrEmpty(roleIds))
-            //{
-            //    accessToken.Permissions = permissionCore.GetList(new { id = roleIds.ConvertToIntArray() })?.List;
-            //}
-            return accessToken;
-        }
-
-        /// <summary>
-        /// 注销授权
-        /// </summary>
-        /// <param name="accessTicket"></param>
-        /// <returns></returns>
-        public int Revoke(string doamin, string accessTicket)
-        {
-            return tokenCore.Delete(doamin, accessTicket);
-        }
-
-        /// <summary>
-        /// 创建一个授权
-        /// </summary>
-        /// <param name="key"></param>
-        /// <param name="secret"></param>
-        /// <param name="roles"></param>
-        /// <param name="domain"></param>
-        /// <param name="clientIp"></param>
-        /// <returns></returns>
-        public IAccessTokenDTO Create(long id, string key, string secret, string roles, string state, string domain, string clientIp)
-        {
-            AccessTokenDTO token = Generate(id, key, secret, roles, state, domain, clientIp);
-            tokenCore.Create(token);
-            return token;
-        }
-
-        /// <summary>
-        /// 工具类 ,产生一个 token
-        /// </summary>
-        /// <param name="accessKey"></param>
-        /// <param name="secret"></param>
-        /// <param name="accessRoles"></param>
-        /// <param name="domain"></param>
-        /// <param name="clientIp"></param>
-        /// <returns></returns>
-        public AccessTokenDTO Generate(long id, string accessKey, string secret, string roles, string state, string domain, string clientIp)
-        {
-            DateTime now = DateTime.Now;
-            AccessTokenDTO token = new AccessTokenDTO
-            {
-                AccessId = id,
-                AccessKey = accessKey,
-                AccessRoles = roles,
-                create_time = now.Convert2UnixTimestamp(),
-                ExpiresIn = 0,
-                AccessState = state,
-                Domain = domain,
-                ClientIp = clientIp,
-                AccessTicket = $"{accessKey}|{secret}|{roles}|{domain}|{now}|{clientIp}".ToAES256()
-            };
-            return token;
-        }
-    }
-}
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc.Controllers;
+using Microsoft.AspNetCore.Mvc.Filters;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using dodohold.core;
+
+
+namespace molilian.core
+{
+    public class AdminProvider : IAuthorizationProvider
+    {
+        public string Domain => "admin";
+
+        AccessTokenCore tokenCore = new AccessTokenCore();
+        AdminPermissionCore permissionCore = new AdminPermissionCore();
+
+
+        /// <summary>
+        /// 验证是否未授权
+        /// </summary>
+        /// <param name="request">客户请求的token和scope(域)</param>
+        /// <param name="logicAttr">受访问的权限属性</param>
+        /// <returns></returns>
+        public bool Verified(ActionExecutingContext context, (string ticket, string domain) request, MyAuthorizeAttribute logicAttr)
+        {
+            //tikect 为空,直接返回 false
+            if (string.IsNullOrEmpty(request.ticket)) { return false; }
+
+            //如果接口里面要求验证domain,就判断是否匹配当前domain
+            if (!string.IsNullOrEmpty(logicAttr.Domain) && !logicAttr.Domain.Equals(request.domain)) { return false; }
+
+
+            AccessTokenDTO accessTicket = (AccessTokenDTO)Get(request.domain, request.ticket);
+            //从内存中获取ticket,没有就返回失败
+            if (accessTicket == null) { return false; }
+
+            //tikcet 设置永不过期,就直接返回 true
+            if (accessTicket.ExpiresIn == 0) { return true; }
+            DateTime expiresTime = (accessTicket.create_time + accessTicket.ExpiresIn).Convert2Datetime();
+
+            //ticket 有效期还没到也返回true
+            if (expiresTime > DateTime.Now) { return true; }
+
+            //这里拿到 actionName 和 actionMethod ,但是实际上并没有去验证
+            string actionName = null;
+            if (context.ActionDescriptor is ControllerActionDescriptor actionDescriptor)
+            {
+                actionName = actionDescriptor.ActionName;
+            }
+            string actionMethod = context.HttpContext.Request.Method;
+
+
+            //permission == null 就是没授权咯
+            var permission = accessTicket.Permissions.Where(s => s.ResourceFlag.Equals(actionName) && s.ActionFlag.Equals(actionMethod)).FirstOrDefault();
+            if (permission == null) { return false; }
+
+            return false;
+        }
+
+
+        public IAccessTokenDTO Get(HttpContext context)
+        {
+            var (accessToken, domain) = context.GetAccessToken();
+            return Get(domain, accessToken);
+        }
+
+        /// <summary>
+        /// 获取token缓存(从内存缓存中)
+        /// </summary>
+        /// <param name="token">客户请求的token</param>
+        /// <returns></returns>
+        public IAccessTokenDTO Get(string domain, string accessTicket)
+        {
+            var accessToken = tokenCore.Get(domain, accessTicket);
+            if (accessToken == null) { return null; }
+
+            //todo 这里除了获取accesstoken以外 还得获取一下授权
+            string roleIds = accessToken.AccessRoles;
+            //if (!string.IsNullOrEmpty(roleIds))
+            //{
+            //    accessToken.Permissions = permissionCore.GetList(new { id = roleIds.ConvertToIntArray() })?.List;
+            //}
+            return accessToken;
+        }
+
+        /// <summary>
+        /// 注销授权
+        /// </summary>
+        /// <param name="accessTicket"></param>
+        /// <returns></returns>
+        public int Revoke(string doamin, string accessTicket)
+        {
+            return tokenCore.Delete(doamin, accessTicket);
+        }
+
+        /// <summary>
+        /// 创建一个授权
+        /// </summary>
+        /// <param name="key"></param>
+        /// <param name="secret"></param>
+        /// <param name="roles"></param>
+        /// <param name="domain"></param>
+        /// <param name="clientIp"></param>
+        /// <returns></returns>
+        public IAccessTokenDTO Create(long id, string key, string secret, string roles, string state, string domain, string clientIp)
+        {
+            AccessTokenDTO token = Generate(id, key, secret, roles, state, domain, clientIp);
+            tokenCore.Create(token);
+            return token;
+        }
+
+        /// <summary>
+        /// 工具类 ,产生一个 token
+        /// </summary>
+        /// <param name="accessKey"></param>
+        /// <param name="secret"></param>
+        /// <param name="accessRoles"></param>
+        /// <param name="domain"></param>
+        /// <param name="clientIp"></param>
+        /// <returns></returns>
+        public AccessTokenDTO Generate(long id, string accessKey, string secret, string roles, string state, string domain, string clientIp)
+        {
+            DateTime now = DateTime.Now;
+            AccessTokenDTO token = new AccessTokenDTO
+            {
+                AccessId = id,
+                AccessKey = accessKey,
+                AccessRoles = roles,
+                create_time = now.Convert2UnixTimestamp(),
+                ExpiresIn = 0,
+                AccessState = state,
+                Domain = domain,
+                ClientIp = clientIp,
+                AccessTicket = $"{accessKey}|{secret}|{roles}|{domain}|{now}|{clientIp}".ToAES256()
+            };
+            return token;
+        }
+    }
+}

+ 140 - 140
molilian.core/Authorization/Provider/UserProvider.cs

@@ -1,140 +1,140 @@
-using Microsoft.AspNetCore.Http;
-using Microsoft.AspNetCore.Mvc.Controllers;
-using Microsoft.AspNetCore.Mvc.Filters;
-using dodohold.core;
-
-
-namespace molilian.core
-{
-    public class AppProvider : IAuthorizationProvider
-    {
-        public string Domain => "wxa";
-
-        //状态缓存在内存
-        private List<AccessTokenDTO> _accessToken_cache = new List<AccessTokenDTO>();
-        AccessTokenCore tokenCore = new AccessTokenCore();
-
-
-        /// <summary>
-        /// 验证是否未授权
-        /// </summary>
-        /// <param name="request">客户请求的token和scope(域)</param>
-        /// <param name="logicAttr">受访问的权限属性</param>
-        /// <returns></returns>
-        public bool Verified(ActionExecutingContext context, (string ticket, string domain) request, MyAuthorizeAttribute logicAttr)
-        {
-            //tikect 为空,直接返回 false
-            if (string.IsNullOrEmpty(request.ticket)) { return false; }
-
-            //如果接口里面要求验证domain,就判断是否匹配当前domain
-            if (!string.IsNullOrEmpty(logicAttr.Domain) && !logicAttr.Domain.Equals(request.domain)) { return false; }
-
-
-            AccessTokenDTO accessTicket = (AccessTokenDTO)Get(request.domain, request.ticket);
-            //从内存中获取ticket,没有就返回失败
-            if (accessTicket == null) { return false; }
-
-            //tikcet 设置永不过期,就直接返回 true
-            if (accessTicket.ExpiresIn == 0) { return true; }
-            DateTime expiresTime = (accessTicket.create_time + accessTicket.ExpiresIn).Convert2Datetime();
-
-            //ticket 有效期还没到也返回true
-            if (expiresTime > DateTime.Now) { return true; }
-
-            //这里拿到 actionName 和 actionMethod ,但是实际上并没有去验证
-            string actionName = null;
-            if (context.ActionDescriptor is ControllerActionDescriptor actionDescriptor)
-            {
-                actionName = actionDescriptor.ActionName;
-            }
-            string actionMethod = context.HttpContext.Request.Method;
-
-
-            //permission == null 就是没授权咯
-            var permission = accessTicket.Permissions.Where(s => s.ResourceFlag.Equals(actionName) && s.ActionFlag.Equals(actionMethod)).FirstOrDefault();
-            if (permission == null) { return false; }
-            return false;
-        }
-
-        public IAccessTokenDTO Get(HttpContext context)
-        {
-            var (accessToken, domain) = context.GetAccessToken();
-            return Get(domain, accessToken);
-        }
-
-        /// <summary>
-        /// 获取token缓存(从内存缓存中)
-        /// </summary>
-        /// <param name="token">客户请求的token</param>
-        /// <returns></returns>
-        public IAccessTokenDTO Get(string domain, string accessTicket)
-        {
-            var accessToken = tokenCore.Get(domain, accessTicket);
-            if (accessToken == null) { return null; }
-
-            //todo 这里除了获取accesstoken以外 还得获取一下授权
-            string roleIds = accessToken.AccessRoles;
-            //if (!string.IsNullOrEmpty(roleIds))
-            //{
-            //    accessToken.Permissions = permissionCore.GetList(new { id = roleIds.ConvertToIntArray() })?.List;
-            //}
-            return accessToken;
-        }
-
-        /// <summary>
-        /// 注销授权
-        /// </summary>
-        /// <param name="accessTicket"></param>
-        /// <returns></returns>
-        public int Revoke(string domain, string accessTicket)
-        {
-            var accessToken = _accessToken_cache.Where(s => s.AccessTicket.Equals(accessTicket)).FirstOrDefault();
-            if (accessToken != null) { _accessToken_cache.Remove(accessToken); }
-            return tokenCore.Delete(accessToken.Domain, accessTicket);
-        }
-
-        /// <summary>
-        /// 创建一个授权
-        /// </summary>
-        /// <param name="key"></param>
-        /// <param name="secret"></param>
-        /// <param name="roles"></param>
-        /// <param name="domain"></param>
-        /// <param name="clientIp"></param>
-        /// <returns></returns>
-        public IAccessTokenDTO Create(long id, string key, string secret, string roles, string state, string domain, string clientIp)
-        {
-            AccessTokenDTO token = Generate(id, key, secret, roles, state, this.Domain, clientIp);
-            tokenCore.Create(token);
-            return token;
-        }
-
-        /// <summary>
-        /// 工具类 ,产生一个 token
-        /// </summary>
-        /// <param name="accessKey"></param>
-        /// <param name="secret"></param>
-        /// <param name="accessRoles"></param>
-        /// <param name="domain"></param>
-        /// <param name="clientIp"></param>
-        /// <returns></returns>
-        public AccessTokenDTO Generate(long id, string accessKey, string secret, string accessRoles, string state, string domain, string clientIp)
-        {
-            DateTime now = DateTime.Now;
-            AccessTokenDTO token = new AccessTokenDTO
-            {
-                AccessId = id,
-                AccessKey = accessKey,
-                AccessRoles = accessRoles,
-                create_time = now.Convert2UnixTimestamp(),
-                ExpiresIn = 0,
-                AccessState = state,
-                Domain = domain,
-                ClientIp = clientIp,
-                AccessTicket = $"{accessKey}|{secret}|{accessRoles}|{domain}|{now}|{clientIp}".ToAES256()
-            };
-            return token;
-        }
-
-    }
-}
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc.Controllers;
+using Microsoft.AspNetCore.Mvc.Filters;
+using dodohold.core;
+
+
+namespace molilian.core
+{
+    public class AppProvider : IAuthorizationProvider
+    {
+        public string Domain => "wxa";
+
+        //状态缓存在内存
+        private List<AccessTokenDTO> _accessToken_cache = new List<AccessTokenDTO>();
+        AccessTokenCore tokenCore = new AccessTokenCore();
+
+
+        /// <summary>
+        /// 验证是否未授权
+        /// </summary>
+        /// <param name="request">客户请求的token和scope(域)</param>
+        /// <param name="logicAttr">受访问的权限属性</param>
+        /// <returns></returns>
+        public bool Verified(ActionExecutingContext context, (string ticket, string domain) request, MyAuthorizeAttribute logicAttr)
+        {
+            //tikect 为空,直接返回 false
+            if (string.IsNullOrEmpty(request.ticket)) { return false; }
+
+            //如果接口里面要求验证domain,就判断是否匹配当前domain
+            if (!string.IsNullOrEmpty(logicAttr.Domain) && !logicAttr.Domain.Equals(request.domain)) { return false; }
+
+
+            AccessTokenDTO accessTicket = (AccessTokenDTO)Get(request.domain, request.ticket);
+            //从内存中获取ticket,没有就返回失败
+            if (accessTicket == null) { return false; }
+
+            //tikcet 设置永不过期,就直接返回 true
+            if (accessTicket.ExpiresIn == 0) { return true; }
+            DateTime expiresTime = (accessTicket.create_time + accessTicket.ExpiresIn).Convert2Datetime();
+
+            //ticket 有效期还没到也返回true
+            if (expiresTime > DateTime.Now) { return true; }
+
+            //这里拿到 actionName 和 actionMethod ,但是实际上并没有去验证
+            string actionName = null;
+            if (context.ActionDescriptor is ControllerActionDescriptor actionDescriptor)
+            {
+                actionName = actionDescriptor.ActionName;
+            }
+            string actionMethod = context.HttpContext.Request.Method;
+
+
+            //permission == null 就是没授权咯
+            var permission = accessTicket.Permissions.Where(s => s.ResourceFlag.Equals(actionName) && s.ActionFlag.Equals(actionMethod)).FirstOrDefault();
+            if (permission == null) { return false; }
+            return false;
+        }
+
+        public IAccessTokenDTO Get(HttpContext context)
+        {
+            var (accessToken, domain) = context.GetAccessToken();
+            return Get(domain, accessToken);
+        }
+
+        /// <summary>
+        /// 获取token缓存(从内存缓存中)
+        /// </summary>
+        /// <param name="token">客户请求的token</param>
+        /// <returns></returns>
+        public IAccessTokenDTO Get(string domain, string accessTicket)
+        {
+            var accessToken = tokenCore.Get(domain, accessTicket);
+            if (accessToken == null) { return null; }
+
+            //todo 这里除了获取accesstoken以外 还得获取一下授权
+            string roleIds = accessToken.AccessRoles;
+            //if (!string.IsNullOrEmpty(roleIds))
+            //{
+            //    accessToken.Permissions = permissionCore.GetList(new { id = roleIds.ConvertToIntArray() })?.List;
+            //}
+            return accessToken;
+        }
+
+        /// <summary>
+        /// 注销授权
+        /// </summary>
+        /// <param name="accessTicket"></param>
+        /// <returns></returns>
+        public int Revoke(string domain, string accessTicket)
+        {
+            var accessToken = _accessToken_cache.Where(s => s.AccessTicket.Equals(accessTicket)).FirstOrDefault();
+            if (accessToken != null) { _accessToken_cache.Remove(accessToken); }
+            return tokenCore.Delete(accessToken.Domain, accessTicket);
+        }
+
+        /// <summary>
+        /// 创建一个授权
+        /// </summary>
+        /// <param name="key"></param>
+        /// <param name="secret"></param>
+        /// <param name="roles"></param>
+        /// <param name="domain"></param>
+        /// <param name="clientIp"></param>
+        /// <returns></returns>
+        public IAccessTokenDTO Create(long id, string key, string secret, string roles, string state, string domain, string clientIp)
+        {
+            AccessTokenDTO token = Generate(id, key, secret, roles, state, this.Domain, clientIp);
+            tokenCore.Create(token);
+            return token;
+        }
+
+        /// <summary>
+        /// 工具类 ,产生一个 token
+        /// </summary>
+        /// <param name="accessKey"></param>
+        /// <param name="secret"></param>
+        /// <param name="accessRoles"></param>
+        /// <param name="domain"></param>
+        /// <param name="clientIp"></param>
+        /// <returns></returns>
+        public AccessTokenDTO Generate(long id, string accessKey, string secret, string accessRoles, string state, string domain, string clientIp)
+        {
+            DateTime now = DateTime.Now;
+            AccessTokenDTO token = new AccessTokenDTO
+            {
+                AccessId = id,
+                AccessKey = accessKey,
+                AccessRoles = accessRoles,
+                create_time = now.Convert2UnixTimestamp(),
+                ExpiresIn = 0,
+                AccessState = state,
+                Domain = domain,
+                ClientIp = clientIp,
+                AccessTicket = $"{accessKey}|{secret}|{accessRoles}|{domain}|{now}|{clientIp}".ToAES256()
+            };
+            return token;
+        }
+
+    }
+}

+ 129 - 129
molilian.core/Core/API/ApiAccountCore.cs

@@ -1,129 +1,129 @@
-using Microsoft.AspNetCore.Http;
-using Microsoft.AspNetCore.Mvc.Controllers;
-using Microsoft.AspNetCore.Mvc.Filters;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using dodohold.core;
-using Dataoke;
-using System.Xml.Linq;
-using System.Data;
-using YunhuiKit;
-
-
-namespace molilian.core
-{
-    public partial class ApiAccountCore
-    {
-        private static readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
-        private static IEnumerable<ApiAccountDTO> _cached;
-        public static async Task<ApiAccountDTO> GetOneAsync(string api_key)
-        {
-            var list = await ListAsync();
-            if (!list.Any()) return null;
-            return list.Where(e => e.api_key == api_key).FirstOrDefault();
-        }
-
-
-        //public static async Task<IEnumerable<ApiAccountDTO>> ListAsync(bool force = false)
-        //{
-        //    if (!force && _cached != null) return _cached;
-
-        //    string cache_key = $"cache:api_key";
-        //    var list = await RedisHelper.GetAsync<IEnumerable<ApiAccountDTO>>(cache_key);
-        //    if (force || list == null)
-        //    {
-        //        lock (_lockObj)
-        //        {
-        //            list = new DBContext.Table("api_account").Select<ApiAccountDTO>();
-        //            if (list == null) return default;
-        //            _ = RedisHelper.SetAsync(cache_key, list, 30 * 86400);
-        //        }
-        //    }
-        //    _cached = list;
-        //    return list;
-        //}
-
-        public static async Task<IEnumerable<ApiAccountDTO>> ListAsync(bool force = false)
-        {
-            if (!force && _cached != null) return _cached;
-
-            try
-            {
-                await _semaphore.WaitAsync();
-
-                string cache_key = $"cache:api_key";
-                var list = await RedisHelper.GetAsync<IEnumerable<ApiAccountDTO>>(cache_key);
-
-                if (force || list == null)
-                {
-                    list = new DBContext.Table("api_account").Select<ApiAccountDTO>();
-                    if (list == null) return default;
-
-                    // 等待 Redis 写入完成
-                    await RedisHelper.SetAsync(cache_key, list, 30 * 86400);
-                }
-
-                _cached = list;
-                return list;
-            }
-            catch (Exception)
-            {
-                // 发生异常时返回上一次的缓存,如果没有则返回默认值
-                return _cached ?? default;
-            }
-            finally
-            {
-                _semaphore.Release();
-            }
-        }
-
-        public static void Refresh()
-        {
-            _ = ListAsync(true);
-        }
-
-        public static void Disabled(string name)
-        {
-            string cache_key = $"cache:api_key:{name}:disabled";
-            long count = RedisHelper.IncrBy(cache_key);
-            RedisHelper.Expire(cache_key, 10);
-            if (count > 1) return;
-
-            new DBContext.Table("api_account")
-                .Add("status", 0)
-                .Where("name=@name", new { name })
-                .Update();
-            _ = ListAsync(true);
-            EndPointCore.NotifyReload();
-        }
-
-
-        public static int Update(ApiAccountDTO data, IDbConnection conn)
-        {
-            var result = (int)conn.Update<ApiAccountDTO>(data, new { data.id });
-            _ = ListAsync(true);
-#if DEBUG
-#else
-            EndPointCore.NotifyReload();
-#endif
-            return result;
-        }
-
-        public static int Create(ApiAccountDTO data, IDbConnection conn)
-        {
-            var result = (int)conn.Insert(data);
-            _ = ListAsync(true);
-#if DEBUG
-#else
-            EndPointCore.NotifyReload();
-#endif
-            return result;
-        }
-
-
-    }
-
-
-}
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc.Controllers;
+using Microsoft.AspNetCore.Mvc.Filters;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using dodohold.core;
+using Dataoke;
+using System.Xml.Linq;
+using System.Data;
+using YunhuiKit;
+
+
+namespace molilian.core
+{
+    public partial class ApiAccountCore
+    {
+        private static readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
+        private static IEnumerable<ApiAccountDTO> _cached;
+        public static async Task<ApiAccountDTO> GetOneAsync(string api_key)
+        {
+            var list = await ListAsync();
+            if (!list.Any()) return null;
+            return list.Where(e => e.api_key == api_key).FirstOrDefault();
+        }
+
+
+        //public static async Task<IEnumerable<ApiAccountDTO>> ListAsync(bool force = false)
+        //{
+        //    if (!force && _cached != null) return _cached;
+
+        //    string cache_key = $"cache:api_key";
+        //    var list = await RedisHelper.GetAsync<IEnumerable<ApiAccountDTO>>(cache_key);
+        //    if (force || list == null)
+        //    {
+        //        lock (_lockObj)
+        //        {
+        //            list = new DBContext.Table("api_account").Select<ApiAccountDTO>();
+        //            if (list == null) return default;
+        //            _ = RedisHelper.SetAsync(cache_key, list, 30 * 86400);
+        //        }
+        //    }
+        //    _cached = list;
+        //    return list;
+        //}
+
+        public static async Task<IEnumerable<ApiAccountDTO>> ListAsync(bool force = false)
+        {
+            if (!force && _cached != null) return _cached;
+
+            try
+            {
+                await _semaphore.WaitAsync();
+
+                string cache_key = $"cache:api_key";
+                var list = await RedisHelper.GetAsync<IEnumerable<ApiAccountDTO>>(cache_key);
+
+                if (force || list == null)
+                {
+                    list = new DBContext.Table("api_account").Select<ApiAccountDTO>();
+                    if (list == null) return default;
+
+                    // 等待 Redis 写入完成
+                    await RedisHelper.SetAsync(cache_key, list, 30 * 86400);
+                }
+
+                _cached = list;
+                return list;
+            }
+            catch (Exception)
+            {
+                // 发生异常时返回上一次的缓存,如果没有则返回默认值
+                return _cached ?? default;
+            }
+            finally
+            {
+                _semaphore.Release();
+            }
+        }
+
+        public static void Refresh()
+        {
+            _ = ListAsync(true);
+        }
+
+        public static void Disabled(string name)
+        {
+            string cache_key = $"cache:api_key:{name}:disabled";
+            long count = RedisHelper.IncrBy(cache_key);
+            RedisHelper.Expire(cache_key, 10);
+            if (count > 1) return;
+
+            new DBContext.Table("api_account")
+                .Add("status", 0)
+                .Where("name=@name", new { name })
+                .Update();
+            _ = ListAsync(true);
+            EndPointCore.NotifyReload();
+        }
+
+
+        public static int Update(ApiAccountDTO data, IDbConnection conn)
+        {
+            var result = (int)conn.Update<ApiAccountDTO>(data, new { data.id });
+            _ = ListAsync(true);
+#if DEBUG
+#else
+            EndPointCore.NotifyReload();
+#endif
+            return result;
+        }
+
+        public static int Create(ApiAccountDTO data, IDbConnection conn)
+        {
+            var result = (int)conn.Insert(data);
+            _ = ListAsync(true);
+#if DEBUG
+#else
+            EndPointCore.NotifyReload();
+#endif
+            return result;
+        }
+
+
+    }
+
+
+}

+ 347 - 347
molilian.core/Core/API/ApiReportCore.cs

@@ -1,347 +1,347 @@
-
-
-using dodohold.core;
-using System.Text.Json;
-
-namespace molilian.core
-{
-    public partial class ApiReportCore
-    {
-        public class RecordDTO
-        {
-            public int id { get; set; }
-            public int platform { get; set; }
-            public int data_type { get; set; }
-            public DateTime report_date { get; set; }
-            public int uclk_uv { get; set; } = 0;
-            public int uclk_pv { get; set; } = 0;
-            public int pay_ord_num { get; set; } = 0;
-            public int order_ord_num { get; set; } = 0;
-            public decimal pay_ord_amt { get; set; } = 0;
-            public decimal pay_tk_disp_tfee { get; set; } = 0;
-            public int pay_ord_uv { get; set; } = 0;
-            public int eff_ord_num { get; set; } = 0;
-            public decimal eff_ord_amt { get; set; } = 0;
-            public decimal eff_tk_disp_tfee { get; set; } = 0;
-            public int total_count { get; set; } = 0;
-            public int abandon_count { get; set; } = 0;
-            public int success_count { get; set; } = 0;
-            public int req_other_aff_num { get; set; } = 0;
-            public int req_goods_num { get; set; } = 0;
-            public int req_live_num { get; set; } = 0;
-            public int req_video_num { get; set; } = 0;
-            public int req_shop_num { get; set; } = 0;
-            public int req_base_num { get; set; } = 0;
-            public int req_note_num { get; set; } = 0;
-            public int resp_deeplink_num { get; set; } = 0;
-
-        }
-
-
-
-        public static async Task<List<dynamic>> ReportDailysAsync(JsonElement form, string clientIp, bool debug = false)
-        {
-            List<dynamic> list = [];
-            //那就是入参日期和type
-            //我们给出指定日期的:点击 付款 有效付款  转链API调用
-            //type可以不入参,留空就两个场景的数据都显示就行了
-            //先只要成功的就行了,,到时候他们要明细再加就行了
-
-            //0:全分销类型;-1:各分销类型,1:goods 2: live 3:video 4: profile 5: unknown
-            int distributiontype = form.Read("distributiontype", 0);
-
-            //0:全平台;-1:各平台;1:淘宝 2:京东 3:多多
-            int platform = form.Read("platform", 0);
-
-            int data_type = form.Read("type", 0);
-            var report_date = form.PathReadArray<string>("query_date[]");
-
-            DateTime stime = DateTime.Now.AddDays(-1).Date, etime = stime;
-            if (report_date.Count == 2)
-            {
-                if (DateTime.TryParse(report_date[0], out stime) && DateTime.TryParse(report_date[1], out etime))
-                {
-                    stime = stime.Date;
-                    etime = etime.Date;
-                }
-            }
-            switch (data_type)
-            {
-                case 1:
-                case 2:
-                case 3:
-                    {
-                        var data = await DataTypeReportDailysAsync(platform, distributiontype, data_type, stime, etime, clientIp, debug);
-                        list.AddRange(data);
-
-                    }
-                    break;
-                default:
-                    {
-                        int[] types = { 1, 2, 3 };
-                        foreach (var type in types)
-                        {
-                            var data = await DataTypeReportDailysAsync(platform, distributiontype, type, stime, etime, clientIp, debug);
-                            list.AddRange(data);
-                        }
-                    }
-                    break;
-            }
-            return list;
-        }
-
-        public static async Task<List<dynamic>> DataTypeReportDailysAsync(int platform, int distributiontype, int data_type, DateTime stime, DateTime etime, string clientIp, bool debug = false)
-        {
-            string filter = "`is_show`=@is_show AND data_type=@data_type AND report_date>=@stime AND report_date<=@etime";
-            if (platform > 0)
-            {
-                filter = "`is_show`=@is_show AND data_type=@data_type AND platform=@platform AND report_date>=@stime AND report_date<=@etime";
-                platform -= 1;
-            }
-            string orderBy = "report_date DESC";
-
-            var list = new DBContext.Table("api_call_report_dailys")
-                        .Where(filter, new { is_show = true, platform, data_type, stime, etime })
-                        .Order(orderBy)
-                        .Select<RecordDTO>().ToList();
-
-            if (!debug)
-            {
-                if (list.Count != 0)
-                {
-                    var ids = list.Select(o => o.id).ToList();
-
-                    new DBContext.Table("api_call_report_dailys")
-                        .Add("read_num:", "read_num+1")
-                        .Where("id IN @ids", new { ids })
-                        .Update();
-                }
-            }
-
-            List<dynamic> data;
-
-
-
-            if (distributiontype == 0)
-            {
-
-                data = list.Select(e => new
-                {
-                    platform = e.platform + 1,
-                    report_date = e.report_date.ToString("yyyy-MM-dd"),
-                    type = e.data_type,
-                    e.uclk_uv,
-                    e.uclk_pv,
-                    e.pay_ord_num,
-                    e.order_ord_num,
-                    pay_ord_amt = e.pay_ord_amt * 100,
-                    pay_tk_disp_tfee = e.pay_tk_disp_tfee * 100,
-                    e.pay_ord_uv,
-                    e.eff_ord_num,
-                    eff_ord_amt = e.eff_ord_amt * 100,
-                    eff_tk_disp_tfee = e.eff_tk_disp_tfee * 100,
-                    e.total_count,
-                    //e.abandon_count,
-                    e.success_count,
-                    e.req_other_aff_num,
-                    e.req_goods_num,
-                    e.req_live_num,
-                    e.req_video_num,
-                    e.req_shop_num,
-                    e.req_base_num,
-                    e.req_note_num,
-                    e.resp_deeplink_num
-                }).ToList<dynamic>();
-
-            }
-            else
-            {
-
-                data = list.SelectMany(e =>
-                {
-                    var results = new List<dynamic>();
-
-                    if (distributiontype == -1 || distributiontype == 1) // 全分类或 goods
-                    {
-                        results.Add(new
-                        {
-                            platform = e.platform + 1,
-                            //metric_type = "req_goods_num",
-                            //metric_value = e.req_goods_num,
-                            report_date = e.report_date.ToString("yyyy-MM-dd"),
-                            type = e.data_type,
-                            e.uclk_uv,
-                            e.uclk_pv,
-                            e.pay_ord_num,
-                            e.order_ord_num,
-                            pay_ord_amt = e.pay_ord_amt * 100,
-                            pay_tk_disp_tfee = e.pay_tk_disp_tfee * 100,
-                            e.pay_ord_uv,
-                            e.eff_ord_num,
-                            eff_ord_amt = e.eff_ord_amt * 100,
-                            eff_tk_disp_tfee = e.eff_tk_disp_tfee * 100,
-                            e.total_count,
-                            e.success_count,
-                            e.req_goods_num,
-                            e.resp_deeplink_num
-                        });
-                    }
-
-                    if (distributiontype == -1 || distributiontype == 2) // 全分类或 live
-                    {
-                        results.Add(new
-                        {
-                            platform = e.platform + 1,
-                            report_date = e.report_date.ToString("yyyy-MM-dd"),
-                            type = e.data_type,
-                            e.uclk_uv,
-                            e.uclk_pv,
-                            e.pay_ord_num,
-                            e.order_ord_num,
-                            pay_ord_amt = e.pay_ord_amt * 100,
-                            pay_tk_disp_tfee = e.pay_tk_disp_tfee * 100,
-                            e.pay_ord_uv,
-                            e.eff_ord_num,
-                            eff_ord_amt = e.eff_ord_amt * 100,
-                            eff_tk_disp_tfee = e.eff_tk_disp_tfee * 100,
-                            e.total_count,
-                            e.success_count,
-                            e.req_live_num,
-                            e.resp_deeplink_num
-                        });
-                    }
-
-                    if (distributiontype == -1 || distributiontype == 3) // 全分类或 video
-                    {
-                        results.Add(new
-                        {
-                            platform = e.platform + 1,
-                            report_date = e.report_date.ToString("yyyy-MM-dd"),
-                            type = e.data_type,
-                            e.uclk_uv,
-                            e.uclk_pv,
-                            e.pay_ord_num,
-                            e.order_ord_num,
-                            pay_ord_amt = e.pay_ord_amt * 100,
-                            pay_tk_disp_tfee = e.pay_tk_disp_tfee * 100,
-                            e.pay_ord_uv,
-                            e.eff_ord_num,
-                            eff_ord_amt = e.eff_ord_amt * 100,
-                            eff_tk_disp_tfee = e.eff_tk_disp_tfee * 100,
-                            e.total_count,
-                            e.success_count,
-                            e.req_video_num,
-                            e.resp_deeplink_num
-                        });
-                    }
-
-                    if (distributiontype == -1 || distributiontype == 4) // 全分类或 req_shop_num
-                    {
-                        results.Add(new
-                        {
-                            platform = e.platform + 1,
-                            report_date = e.report_date.ToString("yyyy-MM-dd"),
-                            type = e.data_type,
-                            e.uclk_uv,
-                            e.uclk_pv,
-                            e.pay_ord_num,
-                            e.order_ord_num,
-                            pay_ord_amt = e.pay_ord_amt * 100,
-                            pay_tk_disp_tfee = e.pay_tk_disp_tfee * 100,
-                            e.pay_ord_uv,
-                            e.eff_ord_num,
-                            eff_ord_amt = e.eff_ord_amt * 100,
-                            eff_tk_disp_tfee = e.eff_tk_disp_tfee * 100,
-                            e.total_count,
-                            e.success_count,
-                            e.req_shop_num,
-                            e.resp_deeplink_num
-                        });
-                    }
-
-                    if (distributiontype == -1 || distributiontype == 5) // 全分类或 req_base_num
-                    {
-                        results.Add(new
-                        {
-                            platform = e.platform + 1,
-                            report_date = e.report_date.ToString("yyyy-MM-dd"),
-                            type = e.data_type,
-                            e.uclk_uv,
-                            e.uclk_pv,
-                            e.pay_ord_num,
-                            e.order_ord_num,
-                            pay_ord_amt = e.pay_ord_amt * 100,
-                            pay_tk_disp_tfee = e.pay_tk_disp_tfee * 100,
-                            e.pay_ord_uv,
-                            e.eff_ord_num,
-                            eff_ord_amt = e.eff_ord_amt * 100,
-                            eff_tk_disp_tfee = e.eff_tk_disp_tfee * 100,
-                            e.total_count,
-                            e.success_count,
-                            e.req_base_num,
-                            e.resp_deeplink_num
-                        });
-                    }
-
-
-                    if (distributiontype == -1 || distributiontype == 6) // 全分类或 req_other_aff_num
-                    {
-                        results.Add(new
-                        {
-                            platform = e.platform + 1,
-                            report_date = e.report_date.ToString("yyyy-MM-dd"),
-                            type = e.data_type,
-                            e.uclk_uv,
-                            e.uclk_pv,
-                            e.pay_ord_num,
-                            e.order_ord_num,
-                            pay_ord_amt = e.pay_ord_amt * 100,
-                            pay_tk_disp_tfee = e.pay_tk_disp_tfee * 100,
-                            e.pay_ord_uv,
-                            e.eff_ord_num,
-                            eff_ord_amt = e.eff_ord_amt * 100,
-                            eff_tk_disp_tfee = e.eff_tk_disp_tfee * 100,
-                            e.total_count,
-                            e.success_count,
-                            e.req_other_aff_num,
-                            e.resp_deeplink_num
-                        });
-                    }
-
-
-
-                    if (distributiontype == -1 || distributiontype == 7) // 全分类或 req_note_num
-                    {
-                        results.Add(new
-                        {
-                            platform = e.platform + 1,
-                            report_date = e.report_date.ToString("yyyy-MM-dd"),
-                            type = e.data_type,
-                            e.uclk_uv,
-                            e.uclk_pv,
-                            e.pay_ord_num,
-                            e.order_ord_num,
-                            pay_ord_amt = e.pay_ord_amt * 100,
-                            pay_tk_disp_tfee = e.pay_tk_disp_tfee * 100,
-                            e.pay_ord_uv,
-                            e.eff_ord_num,
-                            eff_ord_amt = e.eff_ord_amt * 100,
-                            eff_tk_disp_tfee = e.eff_tk_disp_tfee * 100,
-                            e.total_count,
-                            e.success_count,
-                            e.req_note_num,
-                            e.resp_deeplink_num
-                        });
-                    }
-
-
-                    return results;
-                }).ToList<dynamic>();
-            }
-            return data;
-        }
-
-    }
-
-
-}
+
+
+using dodohold.core;
+using System.Text.Json;
+
+namespace molilian.core
+{
+    public partial class ApiReportCore
+    {
+        public class RecordDTO
+        {
+            public int id { get; set; }
+            public int platform { get; set; }
+            public int data_type { get; set; }
+            public DateTime report_date { get; set; }
+            public int uclk_uv { get; set; } = 0;
+            public int uclk_pv { get; set; } = 0;
+            public int pay_ord_num { get; set; } = 0;
+            public int order_ord_num { get; set; } = 0;
+            public decimal pay_ord_amt { get; set; } = 0;
+            public decimal pay_tk_disp_tfee { get; set; } = 0;
+            public int pay_ord_uv { get; set; } = 0;
+            public int eff_ord_num { get; set; } = 0;
+            public decimal eff_ord_amt { get; set; } = 0;
+            public decimal eff_tk_disp_tfee { get; set; } = 0;
+            public int total_count { get; set; } = 0;
+            public int abandon_count { get; set; } = 0;
+            public int success_count { get; set; } = 0;
+            public int req_other_aff_num { get; set; } = 0;
+            public int req_goods_num { get; set; } = 0;
+            public int req_live_num { get; set; } = 0;
+            public int req_video_num { get; set; } = 0;
+            public int req_shop_num { get; set; } = 0;
+            public int req_base_num { get; set; } = 0;
+            public int req_note_num { get; set; } = 0;
+            public int resp_deeplink_num { get; set; } = 0;
+
+        }
+
+
+
+        public static async Task<List<dynamic>> ReportDailysAsync(JsonElement form, string clientIp, bool debug = false)
+        {
+            List<dynamic> list = [];
+            //那就是入参日期和type
+            //我们给出指定日期的:点击 付款 有效付款  转链API调用
+            //type可以不入参,留空就两个场景的数据都显示就行了
+            //先只要成功的就行了,,到时候他们要明细再加就行了
+
+            //0:全分销类型;-1:各分销类型,1:goods 2: live 3:video 4: profile 5: unknown
+            int distributiontype = form.Read("distributiontype", 0);
+
+            //0:全平台;-1:各平台;1:淘宝 2:京东 3:多多
+            int platform = form.Read("platform", 0);
+
+            int data_type = form.Read("type", 0);
+            var report_date = form.PathReadArray<string>("query_date[]");
+
+            DateTime stime = DateTime.Now.AddDays(-1).Date, etime = stime;
+            if (report_date.Count == 2)
+            {
+                if (DateTime.TryParse(report_date[0], out stime) && DateTime.TryParse(report_date[1], out etime))
+                {
+                    stime = stime.Date;
+                    etime = etime.Date;
+                }
+            }
+            switch (data_type)
+            {
+                case 1:
+                case 2:
+                case 3:
+                    {
+                        var data = await DataTypeReportDailysAsync(platform, distributiontype, data_type, stime, etime, clientIp, debug);
+                        list.AddRange(data);
+
+                    }
+                    break;
+                default:
+                    {
+                        int[] types = { 1, 2, 3 };
+                        foreach (var type in types)
+                        {
+                            var data = await DataTypeReportDailysAsync(platform, distributiontype, type, stime, etime, clientIp, debug);
+                            list.AddRange(data);
+                        }
+                    }
+                    break;
+            }
+            return list;
+        }
+
+        public static async Task<List<dynamic>> DataTypeReportDailysAsync(int platform, int distributiontype, int data_type, DateTime stime, DateTime etime, string clientIp, bool debug = false)
+        {
+            string filter = "`is_show`=@is_show AND data_type=@data_type AND report_date>=@stime AND report_date<=@etime";
+            if (platform > 0)
+            {
+                filter = "`is_show`=@is_show AND data_type=@data_type AND platform=@platform AND report_date>=@stime AND report_date<=@etime";
+                platform -= 1;
+            }
+            string orderBy = "report_date DESC";
+
+            var list = new DBContext.Table("api_call_report_dailys")
+                        .Where(filter, new { is_show = true, platform, data_type, stime, etime })
+                        .Order(orderBy)
+                        .Select<RecordDTO>().ToList();
+
+            if (!debug)
+            {
+                if (list.Count != 0)
+                {
+                    var ids = list.Select(o => o.id).ToList();
+
+                    new DBContext.Table("api_call_report_dailys")
+                        .Add("read_num:", "read_num+1")
+                        .Where("id IN @ids", new { ids })
+                        .Update();
+                }
+            }
+
+            List<dynamic> data;
+
+
+
+            if (distributiontype == 0)
+            {
+
+                data = list.Select(e => new
+                {
+                    platform = e.platform + 1,
+                    report_date = e.report_date.ToString("yyyy-MM-dd"),
+                    type = e.data_type,
+                    e.uclk_uv,
+                    e.uclk_pv,
+                    e.pay_ord_num,
+                    e.order_ord_num,
+                    pay_ord_amt = e.pay_ord_amt * 100,
+                    pay_tk_disp_tfee = e.pay_tk_disp_tfee * 100,
+                    e.pay_ord_uv,
+                    e.eff_ord_num,
+                    eff_ord_amt = e.eff_ord_amt * 100,
+                    eff_tk_disp_tfee = e.eff_tk_disp_tfee * 100,
+                    e.total_count,
+                    //e.abandon_count,
+                    e.success_count,
+                    e.req_other_aff_num,
+                    e.req_goods_num,
+                    e.req_live_num,
+                    e.req_video_num,
+                    e.req_shop_num,
+                    e.req_base_num,
+                    e.req_note_num,
+                    e.resp_deeplink_num
+                }).ToList<dynamic>();
+
+            }
+            else
+            {
+
+                data = list.SelectMany(e =>
+                {
+                    var results = new List<dynamic>();
+
+                    if (distributiontype == -1 || distributiontype == 1) // 全分类或 goods
+                    {
+                        results.Add(new
+                        {
+                            platform = e.platform + 1,
+                            //metric_type = "req_goods_num",
+                            //metric_value = e.req_goods_num,
+                            report_date = e.report_date.ToString("yyyy-MM-dd"),
+                            type = e.data_type,
+                            e.uclk_uv,
+                            e.uclk_pv,
+                            e.pay_ord_num,
+                            e.order_ord_num,
+                            pay_ord_amt = e.pay_ord_amt * 100,
+                            pay_tk_disp_tfee = e.pay_tk_disp_tfee * 100,
+                            e.pay_ord_uv,
+                            e.eff_ord_num,
+                            eff_ord_amt = e.eff_ord_amt * 100,
+                            eff_tk_disp_tfee = e.eff_tk_disp_tfee * 100,
+                            e.total_count,
+                            e.success_count,
+                            e.req_goods_num,
+                            e.resp_deeplink_num
+                        });
+                    }
+
+                    if (distributiontype == -1 || distributiontype == 2) // 全分类或 live
+                    {
+                        results.Add(new
+                        {
+                            platform = e.platform + 1,
+                            report_date = e.report_date.ToString("yyyy-MM-dd"),
+                            type = e.data_type,
+                            e.uclk_uv,
+                            e.uclk_pv,
+                            e.pay_ord_num,
+                            e.order_ord_num,
+                            pay_ord_amt = e.pay_ord_amt * 100,
+                            pay_tk_disp_tfee = e.pay_tk_disp_tfee * 100,
+                            e.pay_ord_uv,
+                            e.eff_ord_num,
+                            eff_ord_amt = e.eff_ord_amt * 100,
+                            eff_tk_disp_tfee = e.eff_tk_disp_tfee * 100,
+                            e.total_count,
+                            e.success_count,
+                            e.req_live_num,
+                            e.resp_deeplink_num
+                        });
+                    }
+
+                    if (distributiontype == -1 || distributiontype == 3) // 全分类或 video
+                    {
+                        results.Add(new
+                        {
+                            platform = e.platform + 1,
+                            report_date = e.report_date.ToString("yyyy-MM-dd"),
+                            type = e.data_type,
+                            e.uclk_uv,
+                            e.uclk_pv,
+                            e.pay_ord_num,
+                            e.order_ord_num,
+                            pay_ord_amt = e.pay_ord_amt * 100,
+                            pay_tk_disp_tfee = e.pay_tk_disp_tfee * 100,
+                            e.pay_ord_uv,
+                            e.eff_ord_num,
+                            eff_ord_amt = e.eff_ord_amt * 100,
+                            eff_tk_disp_tfee = e.eff_tk_disp_tfee * 100,
+                            e.total_count,
+                            e.success_count,
+                            e.req_video_num,
+                            e.resp_deeplink_num
+                        });
+                    }
+
+                    if (distributiontype == -1 || distributiontype == 4) // 全分类或 req_shop_num
+                    {
+                        results.Add(new
+                        {
+                            platform = e.platform + 1,
+                            report_date = e.report_date.ToString("yyyy-MM-dd"),
+                            type = e.data_type,
+                            e.uclk_uv,
+                            e.uclk_pv,
+                            e.pay_ord_num,
+                            e.order_ord_num,
+                            pay_ord_amt = e.pay_ord_amt * 100,
+                            pay_tk_disp_tfee = e.pay_tk_disp_tfee * 100,
+                            e.pay_ord_uv,
+                            e.eff_ord_num,
+                            eff_ord_amt = e.eff_ord_amt * 100,
+                            eff_tk_disp_tfee = e.eff_tk_disp_tfee * 100,
+                            e.total_count,
+                            e.success_count,
+                            e.req_shop_num,
+                            e.resp_deeplink_num
+                        });
+                    }
+
+                    if (distributiontype == -1 || distributiontype == 5) // 全分类或 req_base_num
+                    {
+                        results.Add(new
+                        {
+                            platform = e.platform + 1,
+                            report_date = e.report_date.ToString("yyyy-MM-dd"),
+                            type = e.data_type,
+                            e.uclk_uv,
+                            e.uclk_pv,
+                            e.pay_ord_num,
+                            e.order_ord_num,
+                            pay_ord_amt = e.pay_ord_amt * 100,
+                            pay_tk_disp_tfee = e.pay_tk_disp_tfee * 100,
+                            e.pay_ord_uv,
+                            e.eff_ord_num,
+                            eff_ord_amt = e.eff_ord_amt * 100,
+                            eff_tk_disp_tfee = e.eff_tk_disp_tfee * 100,
+                            e.total_count,
+                            e.success_count,
+                            e.req_base_num,
+                            e.resp_deeplink_num
+                        });
+                    }
+
+
+                    if (distributiontype == -1 || distributiontype == 6) // 全分类或 req_other_aff_num
+                    {
+                        results.Add(new
+                        {
+                            platform = e.platform + 1,
+                            report_date = e.report_date.ToString("yyyy-MM-dd"),
+                            type = e.data_type,
+                            e.uclk_uv,
+                            e.uclk_pv,
+                            e.pay_ord_num,
+                            e.order_ord_num,
+                            pay_ord_amt = e.pay_ord_amt * 100,
+                            pay_tk_disp_tfee = e.pay_tk_disp_tfee * 100,
+                            e.pay_ord_uv,
+                            e.eff_ord_num,
+                            eff_ord_amt = e.eff_ord_amt * 100,
+                            eff_tk_disp_tfee = e.eff_tk_disp_tfee * 100,
+                            e.total_count,
+                            e.success_count,
+                            e.req_other_aff_num,
+                            e.resp_deeplink_num
+                        });
+                    }
+
+
+
+                    if (distributiontype == -1 || distributiontype == 7) // 全分类或 req_note_num
+                    {
+                        results.Add(new
+                        {
+                            platform = e.platform + 1,
+                            report_date = e.report_date.ToString("yyyy-MM-dd"),
+                            type = e.data_type,
+                            e.uclk_uv,
+                            e.uclk_pv,
+                            e.pay_ord_num,
+                            e.order_ord_num,
+                            pay_ord_amt = e.pay_ord_amt * 100,
+                            pay_tk_disp_tfee = e.pay_tk_disp_tfee * 100,
+                            e.pay_ord_uv,
+                            e.eff_ord_num,
+                            eff_ord_amt = e.eff_ord_amt * 100,
+                            eff_tk_disp_tfee = e.eff_tk_disp_tfee * 100,
+                            e.total_count,
+                            e.success_count,
+                            e.req_note_num,
+                            e.resp_deeplink_num
+                        });
+                    }
+
+
+                    return results;
+                }).ToList<dynamic>();
+            }
+            return data;
+        }
+
+    }
+
+
+}

+ 83 - 83
molilian.core/Core/ApiTokenCore.cs

@@ -1,83 +1,83 @@
-using Microsoft.AspNetCore.Http;
-using Microsoft.AspNetCore.Mvc.Controllers;
-using Microsoft.AspNetCore.Mvc.Filters;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using dodohold.core;
-using Dataoke;
-
-
-namespace molilian.core
-{
-    public class ApiTokenDTO
-    {
-        public int id { get; set; }
-        public string name { get; set; } = string.Empty;
-        public string token { get; set; } = string.Empty;
-        public string description { get; set; } = string.Empty;
-        public DateTime create_time { get; set; }
-        public DateTime last_time { get; set; }
-        public bool status { get; set; }
-        public int interval { get; set; }
-
-    }
-    public partial class ApiTokenCore
-    {
-        private static readonly object _lockObj = new();
-        private static IEnumerable<ApiTokenDTO> _cached;
-        public static ApiTokenDTO? GetOne(string token)
-        {
-            var list = List();
-            if (!list.Any()) return null;
-            return list.Where(e => e.token == token).FirstOrDefault();
-
-            //            var token = new DBContext.Table("api_token").Get<dynamic>("token=@token", new { token = tokenString });
-
-
-        }
-
-        public static IEnumerable<ApiTokenDTO> List(bool force = false)
-        {
-            if (!force && _cached != null) return _cached;
-
-            string cache_key = $"cache:api_token";
-            var list = RedisHelper.Get<IEnumerable<ApiTokenDTO>>(cache_key);
-            if (force || list == null)
-            {
-                lock (_lockObj)
-                {
-                    list = new DBContext.Table("api_token").Select<ApiTokenDTO>();
-                    if (list == null) return default;
-                    RedisHelper.Set(cache_key, list, 1 * 86400);
-                }
-            }
-            _cached = list;
-            return list;
-        }
-        public static void Refresh()
-        {
-            _cached = null;
-            _ = List(true);
-        }
-
-        public static void Disabled(string name)
-        {
-            string cache_key = $"cache:api_token:{name}:disabled";
-            long count = RedisHelper.IncrBy(cache_key);
-            RedisHelper.Expire(cache_key, 10);
-            if (count > 1) return;
-
-            new DBContext.Table("api_token")
-                .Add("status", 0)
-                .Where("name=@name", new { name })
-                .Update();
-            _ = List(true);
-        }
-
-
-    }
-
-
-}
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc.Controllers;
+using Microsoft.AspNetCore.Mvc.Filters;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using dodohold.core;
+using Dataoke;
+
+
+namespace molilian.core
+{
+    public class ApiTokenDTO
+    {
+        public int id { get; set; }
+        public string name { get; set; } = string.Empty;
+        public string token { get; set; } = string.Empty;
+        public string description { get; set; } = string.Empty;
+        public DateTime create_time { get; set; }
+        public DateTime last_time { get; set; }
+        public bool status { get; set; }
+        public int interval { get; set; }
+
+    }
+    public partial class ApiTokenCore
+    {
+        private static readonly object _lockObj = new();
+        private static IEnumerable<ApiTokenDTO> _cached;
+        public static ApiTokenDTO? GetOne(string token)
+        {
+            var list = List();
+            if (!list.Any()) return null;
+            return list.Where(e => e.token == token).FirstOrDefault();
+
+            //            var token = new DBContext.Table("api_token").Get<dynamic>("token=@token", new { token = tokenString });
+
+
+        }
+
+        public static IEnumerable<ApiTokenDTO> List(bool force = false)
+        {
+            if (!force && _cached != null) return _cached;
+
+            string cache_key = $"cache:api_token";
+            var list = RedisHelper.Get<IEnumerable<ApiTokenDTO>>(cache_key);
+            if (force || list == null)
+            {
+                lock (_lockObj)
+                {
+                    list = new DBContext.Table("api_token").Select<ApiTokenDTO>();
+                    if (list == null) return default;
+                    RedisHelper.Set(cache_key, list, 1 * 86400);
+                }
+            }
+            _cached = list;
+            return list;
+        }
+        public static void Refresh()
+        {
+            _cached = null;
+            _ = List(true);
+        }
+
+        public static void Disabled(string name)
+        {
+            string cache_key = $"cache:api_token:{name}:disabled";
+            long count = RedisHelper.IncrBy(cache_key);
+            RedisHelper.Expire(cache_key, 10);
+            if (count > 1) return;
+
+            new DBContext.Table("api_token")
+                .Add("status", 0)
+                .Where("name=@name", new { name })
+                .Update();
+            _ = List(true);
+        }
+
+
+    }
+
+
+}

+ 43 - 43
molilian.core/Core/CenterHub.cs

@@ -1,43 +1,43 @@
-using CSRedis;
-using dodohold.core;
-using MySql.Data.MySqlClient;
-using System.Data;
-
-
-namespace molilian.core
-{
-    public partial class CenterHub
-    {
-        private static CSRedisClient _redis;
-        private static bool _is_center = true;
-        private static string _centerDB;
-        public static bool IsCenter { get => _is_center; }
-        static CenterHub()
-        {
-            string centerRedis = Environment.GetEnvironmentVariable("CenterRedis");
-            string centerDB = Environment.GetEnvironmentVariable("CenterDB");
-            if (string.IsNullOrEmpty(centerRedis))
-            {
-                _is_center = true;
-                _redis = RedisHelper.Instance;
-            }
-            else
-            {
-                _is_center = false;
-                _redis = RedisClientManager.GetRedisClient(centerRedis);
-            }
-        }
-
-        public static CSRedisClient Redis => _redis;
-
-        public static IDbConnection GetOpenConnection()
-        {
-            if (_is_center)
-            {
-                return DBContext.GetOpenConnection();
-            }
-            return DBContext.GetOpenConnection("CenterDB");
-        }
-    }
-}
-
+using CSRedis;
+using dodohold.core;
+using MySql.Data.MySqlClient;
+using System.Data;
+
+
+namespace molilian.core
+{
+    public partial class CenterHub
+    {
+        private static CSRedisClient _redis;
+        private static bool _is_center = true;
+        private static string _centerDB;
+        public static bool IsCenter { get => _is_center; }
+        static CenterHub()
+        {
+            string centerRedis = Environment.GetEnvironmentVariable("CenterRedis");
+            string centerDB = Environment.GetEnvironmentVariable("CenterDB");
+            if (string.IsNullOrEmpty(centerRedis))
+            {
+                _is_center = true;
+                _redis = RedisHelper.Instance;
+            }
+            else
+            {
+                _is_center = false;
+                _redis = RedisClientManager.GetRedisClient(centerRedis);
+            }
+        }
+
+        public static CSRedisClient Redis => _redis;
+
+        public static IDbConnection GetOpenConnection()
+        {
+            if (_is_center)
+            {
+                return DBContext.GetOpenConnection();
+            }
+            return DBContext.GetOpenConnection("CenterDB");
+        }
+    }
+}
+

+ 47 - 47
molilian.core/Core/ConfigCore.cs

@@ -1,47 +1,47 @@
-using Microsoft.AspNetCore.Http;
-using Microsoft.AspNetCore.Mvc.Controllers;
-using Microsoft.AspNetCore.Mvc.Filters;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using dodohold.core;
-using System.Xml.Linq;
-
-
-namespace molilian.core
-{
-    public partial class TkConfigCore
-    {
-        private static readonly object _lockObj = new();
-        private static TkConfigDTO _cached;
-
-
-        public static TkConfigDTO Get(bool force = false)
-        {
-            if (!force && _cached != null) return _cached;
-
-            string cache_key = $"cache:tk_config";
-
-            var item = CenterHub.Redis.Get<TkConfigDTO>(cache_key);
-            if (force || item == null)
-            {
-                lock (_lockObj)
-                {
-                    using var conn = CenterHub.GetOpenConnection();
-                    item = new DBContext.Table(conn, "tk_config").Get<TkConfigDTO>("id>0", new { });
-                    if (item == null) return default;
-                    CenterHub.Redis.Set(cache_key, item, 30 * 86400);
-                }
-            }
-            _cached = item;
-            return item;
-        }
-        public static void Refresh()
-        {
-            _ = Get(true);
-        }
-
-    }
-
-}
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc.Controllers;
+using Microsoft.AspNetCore.Mvc.Filters;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using dodohold.core;
+using System.Xml.Linq;
+
+
+namespace molilian.core
+{
+    public partial class TkConfigCore
+    {
+        private static readonly object _lockObj = new();
+        private static TkConfigDTO _cached;
+
+
+        public static TkConfigDTO Get(bool force = false)
+        {
+            if (!force && _cached != null) return _cached;
+
+            string cache_key = $"cache:tk_config";
+
+            var item = CenterHub.Redis.Get<TkConfigDTO>(cache_key);
+            if (force || item == null)
+            {
+                lock (_lockObj)
+                {
+                    using var conn = CenterHub.GetOpenConnection();
+                    item = new DBContext.Table(conn, "tk_config").Get<TkConfigDTO>("id>0", new { });
+                    if (item == null) return default;
+                    CenterHub.Redis.Set(cache_key, item, 30 * 86400);
+                }
+            }
+            _cached = item;
+            return item;
+        }
+        public static void Refresh()
+        {
+            _ = Get(true);
+        }
+
+    }
+
+}

+ 299 - 0
molilian.core/Core/CouponCenterCore.cs

@@ -0,0 +1,299 @@
+using dodohold.core;
+
+namespace molilian.core
+{
+    public partial class CouponCenterCore
+    {
+        public const string Version = "0.0.1";
+        private const string CacheKey = "cache:coupon_center:activities:v1";
+        private const int CacheExpireSeconds = 600;
+        private const int EmptyCacheExpireSeconds = 60;
+
+        private static readonly object _lockObj = new();
+        private static CouponCenterActivitiesResponseDTO? _cached;
+        private static DateTime _cachedExpireAt = DateTime.MinValue;
+
+        public static CouponCenterActivitiesResponseDTO GetActivities(string channel = "", bool force = false)
+        {
+            var snapshot = GetSnapshot(force);
+            var platformId = NormalizePlatformId(channel);
+            return BuildResponse(snapshot, platformId);
+        }
+
+        public static void Refresh()
+        {
+            _ = GetSnapshot(true);
+        }
+
+        private static CouponCenterActivitiesResponseDTO GetSnapshot(bool force = false)
+        {
+            if (!force && _cached != null && _cachedExpireAt > DateTime.Now)
+            {
+                return _cached;
+            }
+
+            if (!force)
+            {
+                try
+                {
+                    var cached = RedisHelper.Get<CouponCenterActivitiesResponseDTO>(CacheKey);
+                    if (cached != null)
+                    {
+                        SetLocalCache(cached, CacheExpireSeconds);
+                        return cached;
+                    }
+                }
+                catch
+                {
+                }
+            }
+
+            lock (_lockObj)
+            {
+                if (!force && _cached != null && _cachedExpireAt > DateTime.Now)
+                {
+                    return _cached;
+                }
+
+                var snapshot = LoadFromDb();
+                var cacheSeconds = HasData(snapshot) ? CacheExpireSeconds : EmptyCacheExpireSeconds;
+                SetLocalCache(snapshot, cacheSeconds);
+
+                if (HasData(snapshot))
+                {
+                    try
+                    {
+                        RedisHelper.Set(CacheKey, snapshot, CacheExpireSeconds);
+                    }
+                    catch
+                    {
+                    }
+                }
+
+                return snapshot;
+            }
+        }
+
+        private static CouponCenterActivitiesResponseDTO LoadFromDb()
+        {
+            try
+            {
+                var platformEntities = new DBContext.Table("coupon_center_platforms")
+                    .Where("status=@status", new { status = 1 })
+                    .Select<CouponCenterPlatformEntityDTO>()?
+                    .OrderBy(item => item.sort)
+                    .ThenBy(item => item.id)
+                    .ToList() ?? [];
+
+                var platformIds = platformEntities
+                    .Where(item => !string.IsNullOrWhiteSpace(item.id))
+                    .Select(item => item.id)
+                    .ToHashSet(StringComparer.OrdinalIgnoreCase);
+
+                var activityEntities = new DBContext.Table("coupon_center_activities")
+                    .Where("status=@status", new { status = 1 })
+                    .Select<CouponCenterActivityEntityDTO>()?
+                    .Where(item => !string.IsNullOrWhiteSpace(item.id) && platformIds.Contains(item.platform_id))
+                    .OrderBy(item => item.sort)
+                    .ThenBy(item => item.id)
+                    .ToList() ?? [];
+
+                var activityIds = activityEntities
+                    .Select(item => item.id)
+                    .ToHashSet(StringComparer.OrdinalIgnoreCase);
+
+                var couponLookup = (new DBContext.Table("coupon_center_coupons")
+                    .Where("status=@status", new { status = 1 })
+                    .Select<CouponCenterCouponEntityDTO>()?
+                    .Where(item => !string.IsNullOrWhiteSpace(item.activity_id) && activityIds.Contains(item.activity_id))
+                    .OrderBy(item => item.sort)
+                    .ThenBy(item => item.id)
+                    .GroupBy(item => item.activity_id, StringComparer.OrdinalIgnoreCase)
+                    .ToDictionary(
+                        group => group.Key,
+                        group => group.Select(MapCoupon).ToList(),
+                        StringComparer.OrdinalIgnoreCase)) ?? new Dictionary<string, List<CouponCenterCouponDTO>>(StringComparer.OrdinalIgnoreCase);
+
+                return new CouponCenterActivitiesResponseDTO
+                {
+                    version = Version,
+                    platforms = platformEntities.Select(MapPlatform).ToList(),
+                    activities = activityEntities.Select(item => MapActivity(item, couponLookup)).ToList(),
+                    pagination = new CouponCenterPaginationDTO()
+                };
+            }
+            catch (Exception ex)
+            {
+                _ = new LoggerLibrary("CouponCenterCore", "LoadFromDb")
+                    .Info(ex.Message, ex.StackTrace)
+                    .SaveAsync();
+
+                return new CouponCenterActivitiesResponseDTO
+                {
+                    version = Version,
+                    pagination = new CouponCenterPaginationDTO()
+                };
+            }
+        }
+
+        private static CouponCenterActivitiesResponseDTO BuildResponse(CouponCenterActivitiesResponseDTO snapshot, string platformId)
+        {
+            if (snapshot == null)
+            {
+                return new CouponCenterActivitiesResponseDTO
+                {
+                    version = Version,
+                    pagination = new CouponCenterPaginationDTO()
+                };
+            }
+
+            if (string.IsNullOrWhiteSpace(platformId))
+            {
+                return CloneResponse(snapshot);
+            }
+
+            var platforms = snapshot.platforms ?? [];
+            var activities = snapshot.activities ?? [];
+
+            return new CouponCenterActivitiesResponseDTO
+            {
+                version = snapshot.version,
+                platforms = platforms
+                    .Where(item => item.id.Equals(platformId, StringComparison.OrdinalIgnoreCase))
+                    .Select(ClonePlatform)
+                    .ToList(),
+                activities = activities
+                    .Where(item => item.platformId.Equals(platformId, StringComparison.OrdinalIgnoreCase))
+                    .Select(CloneActivity)
+                    .ToList(),
+                pagination = new CouponCenterPaginationDTO()
+            };
+        }
+
+        private static CouponCenterActivitiesResponseDTO CloneResponse(CouponCenterActivitiesResponseDTO source)
+        {
+            var platforms = source.platforms ?? [];
+            var activities = source.activities ?? [];
+            var pagination = source.pagination ?? new CouponCenterPaginationDTO();
+
+            return new CouponCenterActivitiesResponseDTO
+            {
+                version = source.version,
+                platforms = platforms.Select(ClonePlatform).ToList(),
+                activities = activities.Select(CloneActivity).ToList(),
+                pagination = new CouponCenterPaginationDTO
+                {
+                    hasMore = pagination.hasMore,
+                    nextCursor = pagination.nextCursor
+                }
+            };
+        }
+
+        private static CouponCenterPlatformDTO ClonePlatform(CouponCenterPlatformDTO item)
+        {
+            return new CouponCenterPlatformDTO
+            {
+                id = item.id,
+                name = item.name,
+                iconUrl = item.iconUrl
+            };
+        }
+
+        private static CouponCenterActivityDTO CloneActivity(CouponCenterActivityDTO item)
+        {
+            var coupons = item.coupons ?? [];
+            return new CouponCenterActivityDTO
+            {
+                id = item.id,
+                platformId = item.platformId,
+                name = item.name,
+                shortDesc = item.shortDesc,
+                deeplink = item.deeplink,
+                coupons = coupons.Select(CloneCoupon).ToList()
+            };
+        }
+
+        private static CouponCenterCouponDTO CloneCoupon(CouponCenterCouponDTO item)
+        {
+            return new CouponCenterCouponDTO
+            {
+                id = item.id,
+                value = item.value,
+                expireTime = item.expireTime,
+                category = item.category,
+                threshold = item.threshold,
+                status = item.status
+            };
+        }
+
+        private static CouponCenterPlatformDTO MapPlatform(CouponCenterPlatformEntityDTO item)
+        {
+            return new CouponCenterPlatformDTO
+            {
+                id = item.id,
+                name = item.name,
+                iconUrl = item.icon_url
+            };
+        }
+
+        private static CouponCenterActivityDTO MapActivity(CouponCenterActivityEntityDTO item, IReadOnlyDictionary<string, List<CouponCenterCouponDTO>> couponLookup)
+        {
+            return new CouponCenterActivityDTO
+            {
+                id = item.id,
+                platformId = item.platform_id,
+                name = item.name,
+                shortDesc = item.short_desc,
+                deeplink = string.IsNullOrWhiteSpace(item.deeplink)
+                    ? $"magicchain://coupon-center/activity?activityId={item.id}"
+                    : item.deeplink,
+                coupons = couponLookup.TryGetValue(item.id, out var coupons)
+                    ? coupons.Select(CloneCoupon).ToList()
+                    : []
+            };
+        }
+
+        private static CouponCenterCouponDTO MapCoupon(CouponCenterCouponEntityDTO item)
+        {
+            return new CouponCenterCouponDTO
+            {
+                id = item.id,
+                value = item.value,
+                expireTime = item.expire_time,
+                category = item.category,
+                threshold = item.threshold,
+                status = string.IsNullOrWhiteSpace(item.coupon_status) ? "available" : item.coupon_status
+            };
+        }
+
+        private static bool HasData(CouponCenterActivitiesResponseDTO snapshot)
+        {
+            if (snapshot == null)
+            {
+                return false;
+            }
+
+            return (snapshot.platforms?.Count ?? 0) > 0 || (snapshot.activities?.Count ?? 0) > 0;
+        }
+
+        private static void SetLocalCache(CouponCenterActivitiesResponseDTO snapshot, int cacheSeconds)
+        {
+            _cached = snapshot;
+            _cachedExpireAt = DateTime.Now.AddSeconds(cacheSeconds);
+        }
+
+        private static string NormalizePlatformId(string channel)
+        {
+            channel = channel?.Trim().ToLowerInvariant() ?? string.Empty;
+            return channel switch
+            {
+                "taobao" => "tb",
+                "vip" => "vipshop",
+                "tbflash" => "tb_flash",
+                "taobaoflash" => "tb_flash",
+                "淘宝闪购" => "tb_flash",
+                _ => channel,
+            };
+        }
+    }
+}

+ 282 - 282
molilian.core/Core/EndPointCore.cs

@@ -1,282 +1,282 @@
-using dodohold.core;
-using MySql.Data.MySqlClient;
-using System.Data;
-
-
-namespace molilian.core
-{
-    public partial class EndPointCore
-    {
-        private static readonly object _lockObj = new();
-        private static IEnumerable<EndPointDTO> _cached;
-
-        public static string GetRedisServer(EndPointDTO node)
-        {
-#if DEBUG
-            return node.name switch
-            {
-                "bj" => "101.200.152.61:6379,password=pKBiS4ka2IpXayIdcx00,defaultDatabase=0,idleTimeout=20000,preheat=3,tryit=2,ssl=false,prefix=webhook",
-                "gz" => "8.138.110.158:6379,password=pKBiS4ka2IpXayIdcx00,defaultDatabase=0,idleTimeout=20000,preheat=3,tryit=2,ssl=false,prefix=webhook",
-                "coupon1" => "123.56.185.166:6379,password=pKBiS4ka2IpXayIdcx00,defaultDatabase=0,idleTimeout=20000,preheat=3,tryit=2,ssl=false,prefix=coupon",
-                _ => string.Empty
-            };
-#else
-    return node.redis_server;
-#endif
-        }
-
-        public static string CurrentEndPoint { get; set; }
-        static EndPointCore()
-        {
-            CurrentEndPoint = Environment.GetEnvironmentVariable("EndPoint");
-        }
-
-
-
-        public static IDbConnection GetDbConnection(string connString)
-        {
-            var conn = new MySqlConnection(connString);
-            return conn;
-        }
-
-        public static EndPointDTO GetOne(string node)
-        {
-            var list = List();
-            if (!list.Any()) return null;
-            var item = list.Where(s => s.name == node).FirstOrDefault();
-            return item;
-        }
-
-
-        public static EndPointDTO GetCouponAdmin()
-        {
-            var list = List();
-            if (!list.Any()) return null;
-            var item = list.Where(s => s.is_coupon_api && !s.is_public_api).FirstOrDefault();
-
-
-#if DEBUG
-            // item.db_server = "Server= rm-2ze49fcn8e28e6gzu3o.rwlb.rds.aliyuncs.com; Port=3306; Database=coupon; Uid=coupon; Pwd=67ktWBmw5G4yMs4J;SslMode=None;CharSet=utf8mb4;ConnectionTimeout=60;";
-#endif
-            /*   
-Server=rm-2zey1jqxnoqy9mcc0zo.rwlb.rds.aliyuncs.com; Port=3306; Database=taoke; Uid=taoke; Pwd=67ktWBmw5G4yMs4J;SslMode=None;CharSet=utf8mb4;ConnectionTimeout=60
-Server=rm-2zey1jqxnoqy9mcc0zo.rwlb.rds.aliyuncs.com; Port=3306; Database=taoke; Uid=taoke; Pwd=67ktWBmw5G4yMs4J;SslMode=None;CharSet=utf8mb4;ConnectionTimeout=60
-Server=rm-2zey1jqxnoqy9mcc0zo.rwlb.rds.aliyuncs.com; Port=3306; Database=taoke; Uid=taoke; Pwd=67ktWBmw5G4yMs4J;SslMode=None;CharSet=utf8mb4;ConnectionTimeout=60
-Server=rm-2ze74506m3gfsqe7mco.rwlb.rds.aliyuncs.com; Port=3306; Database=coupon; Uid=coupon; Pwd=67ktWBmw5G4yMs4J;SslMode=None;CharSet=utf8mb4;ConnectionTimeout=60;
-             
-             */
-            return item;
-        }
-
-        public static EndPointDTO GetParseAdmin()
-        {
-            var list = List();
-            if (!list.Any()) return null;
-            var item = list.Where(s => !s.is_coupon_api && !s.is_public_api).FirstOrDefault();
-
-#if DEBUG
-            item.db_server = "Server=rm-2ze49fcn8e28e6gzu3o.rwlb.rds.aliyuncs.com; Port=3306; Database=taoke; Uid=taoke; Pwd=67ktWBmw5G4yMs4J;SslMode=None;CharSet=utf8mb4;ConnectionTimeout=60;";
-#endif
-            return item;
-        }
-
-        public static IEnumerable<EndPointDTO> List(bool force = false)
-        {
-            if (!force && _cached != null) return _cached;
-
-            string cache_key = $"cache:end_point";
-            var list = RedisHelper.Get<IEnumerable<EndPointDTO>>(cache_key);
-            if (force || list == null)
-            {
-                lock (_lockObj)
-                {
-                    list = new DBContext.Table("end_point")
-                        .Where("status=@status", new { status = 1 })
-                        .Select<EndPointDTO>();
-                    if (list == null) return default;
-                    RedisHelper.Set(cache_key, list, 30 * 86400);
-                }
-            }
-            _cached = list;
-            return list;
-        }
-        public static void Refresh()
-        {
-            _ = List(true);
-        }
-
-        public static List<T> ProcessEndPointNodes<T>(Func<EndPointDTO, T> nodeAction, bool force = false)
-        {
-            var resultList = new List<T>();
-            var list = List(force);
-            foreach (var node in list)
-            {
-                var result = nodeAction(node);
-                if (result != null) resultList.Add(result);
-            }
-            return resultList;
-        }
-        public static async Task<List<T>> ProcessEndPointNodesTaskAsync<T>(Func<EndPointDTO, Task<T>> nodeAction, bool force = false)
-        {
-            var resultList = new List<T>();
-            var list = List(force);
-
-            var tasks = list.Select(node => nodeAction(node));
-            var results = await Task.WhenAll(tasks);
-
-            return results.Where(result => result != null).ToList();
-
-            //foreach (var node in list)
-            //{
-            //    var result = await nodeAction(node);
-            //    if (result != null) resultList.Add(result);
-            //}
-            //return resultList;
-        }
-
-        public static async Task ProcessEndPointNodesAsync(Func<EndPointDTO, Task> nodeAction, bool force = false)
-        {
-            var list = List(force);
-            var tasks = list.Select(node => nodeAction(node));
-            await Task.WhenAll(tasks);
-        }
-
-
-        public static async Task NotifyReload(bool onlyAccount = false, CancellationToken cancellationToken = default)
-        {
-            await ProcessEndPointNodesAsync(async node =>
-            {
-                try
-                {
-                    string url = $"{node.api_server}Task_70160bd632/reload";
-                    if (onlyAccount) url = $"{node.api_server}Task_70160bd632/reloadAccount";
-                    await new WebClientUtility().RequestAsync(url, "GET", cancellationToken);
-                }
-                catch (Exception ex)
-                {
-                    _ = new LoggerLibrary("NotifyReload", "error")
-                    .Info(node.Convert2Json())
-                    .Info(ex.Message, ex.StackTrace)
-                    .SaveAsync();
-
-                    NotifyCore.Notify(new NifyMessage
-                    {
-                        message = $"【NotifyReload异常】{node.description}\n{ex.Message}\n{ex.StackTrace}",
-                        priority = NifyMessagePriority.high,
-                        tags = ["red_circle"]
-                    });
-                }
-            });
-        }
-
-        public static List<(string, string)> NotifyCheckDeepUrl(int accountid, CancellationToken cancellationToken = default)
-        {
-            var result = ProcessEndPointNodes<(string, string)>(node =>
-            {
-                try
-                {
-                    if (!node.is_public_api) return (node.name, "break");
-                    if (!node.status) return (node.name, "账号下线");
-
-                    string url = $"{node.api_server}Task_70160bd632/CheckDeepUrl?accountid={accountid}";
-                    var result = new WebClientUtility().Request(url, "GET");
-                    string body = result.Body();
-                    var root = body.Convert2JsonElement();
-                    var message = root.Read<string>("message", string.Empty);
-                    return (node.name, message);
-
-
-                }
-                catch (Exception ex)
-                {
-                    _ = new LoggerLibrary("CheckDeepUrl", "error")
-                    .Info(node.Convert2Json(), $"accountid:\t{accountid}")
-                    .Info(ex.Message, ex.StackTrace)
-                    .SaveAsync();
-
-                    NotifyCore.Notify(new NifyMessage
-                    {
-                        message = $"【CheckDeepUrl异常】{node.description}\n{ex.Message}\n{ex.StackTrace}",
-                        priority = NifyMessagePriority.high,
-                        tags = ["red_circle"]
-                    });
-                    return (node.name, ex.Message);
-                }
-            });
-            return result;
-        }
-
-
-        public static async Task NotifyChangeSuspend(int accountId, string endpoint, bool release = false, CancellationToken cancellationToken = default)
-        {
-            await ProcessEndPointNodesAsync(async node =>
-            {
-                string post = string.Empty;
-                try
-                {
-                    string url = $"{node.api_server}api/TkEndpoint/ChangeSuspend";
-                    post = new
-                    {
-                        accountId,
-                        endpoint,
-                        release
-                    }.Convert2Json();
-                    await new WebClientUtility().Post(post).RequestAsync(url, "POST", cancellationToken);
-                }
-                catch (Exception ex)
-                {
-                    _ = new LoggerLibrary("TkEndpoint", "ChangeSuspend_error")
-                    .Info(node.Convert2Json(), post)
-                    .Info(ex.Message, ex.StackTrace)
-                    .SaveAsync();
-
-                    NotifyCore.Notify(new NifyMessage
-                    {
-                        message = $"【TkEndpoint异常】ChangeSuspend\t{node.description}\n{post}\n{ex.Message}\n{ex.StackTrace}",
-                        priority = NifyMessagePriority.high,
-                        tags = ["red_circle"]
-                    });
-                }
-            });
-        }
-
-
-        public static List<(string, string)> TkEndpointGetStatus(int accountid, CancellationToken cancellationToken = default)
-        {
-            var result = ProcessEndPointNodes<(string, string)>(node =>
-            {
-                try
-                {
-                    if (!node.is_public_api) return (node.name, "break");
-                    if (!node.status) return (node.name, "账号下线");
-
-                    string url = $"{node.api_server}api/TkEndpoint/GetStatus";
-                    var result = new WebClientUtility().Request(url, "POST");
-                    string body = result.Body();
-                    var root = body.Convert2JsonElement();
-                    var message = root.Read<string>("message", string.Empty);
-                    return (node.name, message);
-                }
-                catch (Exception ex)
-                {
-                    _ = new LoggerLibrary("GetStatus", "error")
-                    .Info(node.Convert2Json(), $"accountid:\t{accountid}")
-                    .Info(ex.Message, ex.StackTrace)
-                    .SaveAsync();
-
-                    NotifyCore.Notify(new NifyMessage
-                    {
-                        message = $"【TkEndpoint异常】GetStatus\t{node.description}\n{ex.Message}\n{ex.StackTrace}",
-                        priority = NifyMessagePriority.high,
-                        tags = ["red_circle"]
-                    });
-                    return (node.name, ex.Message);
-                }
-            });
-            return result;
-        }
-
-
-
-    }
-}
+using dodohold.core;
+using MySql.Data.MySqlClient;
+using System.Data;
+
+
+namespace molilian.core
+{
+    public partial class EndPointCore
+    {
+        private static readonly object _lockObj = new();
+        private static IEnumerable<EndPointDTO> _cached;
+
+        public static string GetRedisServer(EndPointDTO node)
+        {
+#if DEBUG
+            return node.name switch
+            {
+                "bj" => "101.200.152.61:6379,password=pKBiS4ka2IpXayIdcx00,defaultDatabase=0,idleTimeout=20000,preheat=3,tryit=2,ssl=false,prefix=webhook",
+                "gz" => "8.138.110.158:6379,password=pKBiS4ka2IpXayIdcx00,defaultDatabase=0,idleTimeout=20000,preheat=3,tryit=2,ssl=false,prefix=webhook",
+                "coupon1" => "123.56.185.166:6379,password=pKBiS4ka2IpXayIdcx00,defaultDatabase=0,idleTimeout=20000,preheat=3,tryit=2,ssl=false,prefix=coupon",
+                _ => string.Empty
+            };
+#else
+    return node.redis_server;
+#endif
+        }
+
+        public static string CurrentEndPoint { get; set; }
+        static EndPointCore()
+        {
+            CurrentEndPoint = Environment.GetEnvironmentVariable("EndPoint");
+        }
+
+
+
+        public static IDbConnection GetDbConnection(string connString)
+        {
+            var conn = new MySqlConnection(connString);
+            return conn;
+        }
+
+        public static EndPointDTO GetOne(string node)
+        {
+            var list = List();
+            if (!list.Any()) return null;
+            var item = list.Where(s => s.name == node).FirstOrDefault();
+            return item;
+        }
+
+
+        public static EndPointDTO GetCouponAdmin()
+        {
+            var list = List();
+            if (!list.Any()) return null;
+            var item = list.Where(s => s.is_coupon_api && !s.is_public_api).FirstOrDefault();
+
+
+#if DEBUG
+            // item.db_server = "Server= rm-2ze49fcn8e28e6gzu3o.rwlb.rds.aliyuncs.com; Port=3306; Database=coupon; Uid=coupon; Pwd=67ktWBmw5G4yMs4J;SslMode=None;CharSet=utf8mb4;ConnectionTimeout=60;";
+#endif
+            /*   
+Server=rm-2zey1jqxnoqy9mcc0zo.rwlb.rds.aliyuncs.com; Port=3306; Database=taoke; Uid=taoke; Pwd=67ktWBmw5G4yMs4J;SslMode=None;CharSet=utf8mb4;ConnectionTimeout=60
+Server=rm-2zey1jqxnoqy9mcc0zo.rwlb.rds.aliyuncs.com; Port=3306; Database=taoke; Uid=taoke; Pwd=67ktWBmw5G4yMs4J;SslMode=None;CharSet=utf8mb4;ConnectionTimeout=60
+Server=rm-2zey1jqxnoqy9mcc0zo.rwlb.rds.aliyuncs.com; Port=3306; Database=taoke; Uid=taoke; Pwd=67ktWBmw5G4yMs4J;SslMode=None;CharSet=utf8mb4;ConnectionTimeout=60
+Server=rm-2ze74506m3gfsqe7mco.rwlb.rds.aliyuncs.com; Port=3306; Database=coupon; Uid=coupon; Pwd=67ktWBmw5G4yMs4J;SslMode=None;CharSet=utf8mb4;ConnectionTimeout=60;
+             
+             */
+            return item;
+        }
+
+        public static EndPointDTO GetParseAdmin()
+        {
+            var list = List();
+            if (!list.Any()) return null;
+            var item = list.Where(s => !s.is_coupon_api && !s.is_public_api).FirstOrDefault();
+
+#if DEBUG
+            item.db_server = "Server=rm-2ze49fcn8e28e6gzu3o.rwlb.rds.aliyuncs.com; Port=3306; Database=taoke; Uid=taoke; Pwd=67ktWBmw5G4yMs4J;SslMode=None;CharSet=utf8mb4;ConnectionTimeout=60;";
+#endif
+            return item;
+        }
+
+        public static IEnumerable<EndPointDTO> List(bool force = false)
+        {
+            if (!force && _cached != null) return _cached;
+
+            string cache_key = $"cache:end_point";
+            var list = RedisHelper.Get<IEnumerable<EndPointDTO>>(cache_key);
+            if (force || list == null)
+            {
+                lock (_lockObj)
+                {
+                    list = new DBContext.Table("end_point")
+                        .Where("status=@status", new { status = 1 })
+                        .Select<EndPointDTO>();
+                    if (list == null) return default;
+                    RedisHelper.Set(cache_key, list, 30 * 86400);
+                }
+            }
+            _cached = list;
+            return list;
+        }
+        public static void Refresh()
+        {
+            _ = List(true);
+        }
+
+        public static List<T> ProcessEndPointNodes<T>(Func<EndPointDTO, T> nodeAction, bool force = false)
+        {
+            var resultList = new List<T>();
+            var list = List(force);
+            foreach (var node in list)
+            {
+                var result = nodeAction(node);
+                if (result != null) resultList.Add(result);
+            }
+            return resultList;
+        }
+        public static async Task<List<T>> ProcessEndPointNodesTaskAsync<T>(Func<EndPointDTO, Task<T>> nodeAction, bool force = false)
+        {
+            var resultList = new List<T>();
+            var list = List(force);
+
+            var tasks = list.Select(node => nodeAction(node));
+            var results = await Task.WhenAll(tasks);
+
+            return results.Where(result => result != null).ToList();
+
+            //foreach (var node in list)
+            //{
+            //    var result = await nodeAction(node);
+            //    if (result != null) resultList.Add(result);
+            //}
+            //return resultList;
+        }
+
+        public static async Task ProcessEndPointNodesAsync(Func<EndPointDTO, Task> nodeAction, bool force = false)
+        {
+            var list = List(force);
+            var tasks = list.Select(node => nodeAction(node));
+            await Task.WhenAll(tasks);
+        }
+
+
+        public static async Task NotifyReload(bool onlyAccount = false, CancellationToken cancellationToken = default)
+        {
+            await ProcessEndPointNodesAsync(async node =>
+            {
+                try
+                {
+                    string url = $"{node.api_server}Task_70160bd632/reload";
+                    if (onlyAccount) url = $"{node.api_server}Task_70160bd632/reloadAccount";
+                    await new WebClientUtility().RequestAsync(url, "GET", cancellationToken);
+                }
+                catch (Exception ex)
+                {
+                    _ = new LoggerLibrary("NotifyReload", "error")
+                    .Info(node.Convert2Json())
+                    .Info(ex.Message, ex.StackTrace)
+                    .SaveAsync();
+
+                    NotifyCore.Notify(new NifyMessage
+                    {
+                        message = $"【NotifyReload异常】{node.description}\n{ex.Message}\n{ex.StackTrace}",
+                        priority = NifyMessagePriority.high,
+                        tags = ["red_circle"]
+                    });
+                }
+            });
+        }
+
+        public static List<(string, string)> NotifyCheckDeepUrl(int accountid, CancellationToken cancellationToken = default)
+        {
+            var result = ProcessEndPointNodes<(string, string)>(node =>
+            {
+                try
+                {
+                    if (!node.is_public_api) return (node.name, "break");
+                    if (!node.status) return (node.name, "账号下线");
+
+                    string url = $"{node.api_server}Task_70160bd632/CheckDeepUrl?accountid={accountid}";
+                    var result = new WebClientUtility().Request(url, "GET");
+                    string body = result.Body();
+                    var root = body.Convert2JsonElement();
+                    var message = root.Read<string>("message", string.Empty);
+                    return (node.name, message);
+
+
+                }
+                catch (Exception ex)
+                {
+                    _ = new LoggerLibrary("CheckDeepUrl", "error")
+                    .Info(node.Convert2Json(), $"accountid:\t{accountid}")
+                    .Info(ex.Message, ex.StackTrace)
+                    .SaveAsync();
+
+                    NotifyCore.Notify(new NifyMessage
+                    {
+                        message = $"【CheckDeepUrl异常】{node.description}\n{ex.Message}\n{ex.StackTrace}",
+                        priority = NifyMessagePriority.high,
+                        tags = ["red_circle"]
+                    });
+                    return (node.name, ex.Message);
+                }
+            });
+            return result;
+        }
+
+
+        public static async Task NotifyChangeSuspend(int accountId, string endpoint, bool release = false, CancellationToken cancellationToken = default)
+        {
+            await ProcessEndPointNodesAsync(async node =>
+            {
+                string post = string.Empty;
+                try
+                {
+                    string url = $"{node.api_server}api/TkEndpoint/ChangeSuspend";
+                    post = new
+                    {
+                        accountId,
+                        endpoint,
+                        release
+                    }.Convert2Json();
+                    await new WebClientUtility().Post(post).RequestAsync(url, "POST", cancellationToken);
+                }
+                catch (Exception ex)
+                {
+                    _ = new LoggerLibrary("TkEndpoint", "ChangeSuspend_error")
+                    .Info(node.Convert2Json(), post)
+                    .Info(ex.Message, ex.StackTrace)
+                    .SaveAsync();
+
+                    NotifyCore.Notify(new NifyMessage
+                    {
+                        message = $"【TkEndpoint异常】ChangeSuspend\t{node.description}\n{post}\n{ex.Message}\n{ex.StackTrace}",
+                        priority = NifyMessagePriority.high,
+                        tags = ["red_circle"]
+                    });
+                }
+            });
+        }
+
+
+        public static List<(string, string)> TkEndpointGetStatus(int accountid, CancellationToken cancellationToken = default)
+        {
+            var result = ProcessEndPointNodes<(string, string)>(node =>
+            {
+                try
+                {
+                    if (!node.is_public_api) return (node.name, "break");
+                    if (!node.status) return (node.name, "账号下线");
+
+                    string url = $"{node.api_server}api/TkEndpoint/GetStatus";
+                    var result = new WebClientUtility().Request(url, "POST");
+                    string body = result.Body();
+                    var root = body.Convert2JsonElement();
+                    var message = root.Read<string>("message", string.Empty);
+                    return (node.name, message);
+                }
+                catch (Exception ex)
+                {
+                    _ = new LoggerLibrary("GetStatus", "error")
+                    .Info(node.Convert2Json(), $"accountid:\t{accountid}")
+                    .Info(ex.Message, ex.StackTrace)
+                    .SaveAsync();
+
+                    NotifyCore.Notify(new NifyMessage
+                    {
+                        message = $"【TkEndpoint异常】GetStatus\t{node.description}\n{ex.Message}\n{ex.StackTrace}",
+                        priority = NifyMessagePriority.high,
+                        tags = ["red_circle"]
+                    });
+                    return (node.name, ex.Message);
+                }
+            });
+            return result;
+        }
+
+
+
+    }
+}

+ 145 - 145
molilian.core/Core/NotifyCore.cs

@@ -1,146 +1,146 @@
-using Microsoft.AspNetCore.Http;
-using Microsoft.AspNetCore.Mvc.Controllers;
-using Microsoft.AspNetCore.Mvc.Filters;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using dodohold.core;
-
-
-namespace molilian.core
-{
-    public partial class NotifyCore
-    {
-        static string _base_url = string.Empty;
-        static string _anpush_url = string.Empty;
-        static string _qyweixin_push_url = string.Empty;
-
-        static NotifyCore()
-        {
-            _anpush_url = Environment.GetEnvironmentVariable("ANPush");
-            _qyweixin_push_url = Environment.GetEnvironmentVariable("QYWeixinPush");
-
-            string? publish_url = Environment.GetEnvironmentVariable("NtfyServer");
-            if (!string.IsNullOrEmpty(publish_url))
-            {
-                _base_url = publish_url;
-            }
-        }
-
-        // 同步版本保持不变
-        public static void Notify(string message)
-        {
-            if (!string.IsNullOrEmpty(_base_url))
-            {
-                NifyPlus.Send(_base_url, new NifyMessage { message = message });
-            }
-        }
-
-        // 新增异步版本
-        public static async Task NotifyAsync(string message)
-        {
-            if (!string.IsNullOrEmpty(_base_url))
-            {
-                await NifyPlus.SendAsync(_base_url, new NifyMessage { message = message });
-            }
-        }
-
-        // 同步版本保持不变
-        public static void Notify(NifyMessage message)
-        {
-            if (!string.IsNullOrEmpty(_base_url))
-            {
-                NifyPlus.Send(_base_url, message);
-            }
-        }
-
-        // 新增异步版本
-        public static async Task NotifyAsync(NifyMessage message)
-        {
-            if (!string.IsNullOrEmpty(_base_url))
-            {
-                await NifyPlus.SendAsync(_base_url, message);
-            }
-        }
-
-        public static async Task QYWeixinPushNotifyAsync(string title, string content)
-        {
-#if DEBUG
-            await Task.CompletedTask;
-#else
-            if (!string.IsNullOrEmpty(_qyweixin_push_url))
-            {
-                var args = new
-                {
-                    msgtype = "markdown",
-                    markdown = new
-                    {
-                        content = $"# {title}\n{content}",
-                    }
-                };
-                string body = string.Empty;
-                try
-                {
-                    WebClientUtility client = new WebClientUtility();
-                    client.Post(args.Convert2Json());
-                    client.SetContentType("application/json");
-                    var response = await client.RequestAsync(_qyweixin_push_url, "POST");
-                    body = response.Body();
-
-                    new LoggerLibrary("qyweixin").Info(title, content).Info(body).Save();
-                }
-                catch (Exception ex)
-                {
-                    new LoggerLibrary("qyweixin", "error")
-                        .Info(title, content)
-                        .Info(body)
-                        .Info(ex.Message, ex.StackTrace)
-                        .Save();
-                }
-            }
-#endif
-        }
-
-        // 同步版本保持不变
-        public static void AnPushNotify(string title, string content)
-        {
-#if DEBUG
-#else
-        if (!string.IsNullOrEmpty(_anpush_url))
-        {
-            string url = _anpush_url;
-            url = url.Replace("{title}", title?.UrlEncode());
-            url = url.Replace("{content}", content?.UrlEncode());
-
-            try
-            {
-                url.HttpRequest();
-            }
-            catch (Exception ex) { }
-        }
-#endif
-        }
-
-        // 新增异步版本
-        public static async Task AnPushNotifyAsync(string title, string content)
-        {
-#if DEBUG
-            await Task.CompletedTask;
-#else
-        if (!string.IsNullOrEmpty(_anpush_url))
-        {
-            string url = _anpush_url;
-            url = url.Replace("{title}", title?.UrlEncode());
-            url = url.Replace("{content}", content?.UrlEncode());
-
-            try
-            {
-                await Task.Run(() => url.HttpRequest());
-            }
-            catch (Exception ex) { }
-        }
-#endif
-        }
-    }
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc.Controllers;
+using Microsoft.AspNetCore.Mvc.Filters;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using dodohold.core;
+
+
+namespace molilian.core
+{
+    public partial class NotifyCore
+    {
+        static string _base_url = string.Empty;
+        static string _anpush_url = string.Empty;
+        static string _qyweixin_push_url = string.Empty;
+
+        static NotifyCore()
+        {
+            _anpush_url = Environment.GetEnvironmentVariable("ANPush");
+            _qyweixin_push_url = Environment.GetEnvironmentVariable("QYWeixinPush");
+
+            string? publish_url = Environment.GetEnvironmentVariable("NtfyServer");
+            if (!string.IsNullOrEmpty(publish_url))
+            {
+                _base_url = publish_url;
+            }
+        }
+
+        // 同步版本保持不变
+        public static void Notify(string message)
+        {
+            if (!string.IsNullOrEmpty(_base_url))
+            {
+                NifyPlus.Send(_base_url, new NifyMessage { message = message });
+            }
+        }
+
+        // 新增异步版本
+        public static async Task NotifyAsync(string message)
+        {
+            if (!string.IsNullOrEmpty(_base_url))
+            {
+                await NifyPlus.SendAsync(_base_url, new NifyMessage { message = message });
+            }
+        }
+
+        // 同步版本保持不变
+        public static void Notify(NifyMessage message)
+        {
+            if (!string.IsNullOrEmpty(_base_url))
+            {
+                NifyPlus.Send(_base_url, message);
+            }
+        }
+
+        // 新增异步版本
+        public static async Task NotifyAsync(NifyMessage message)
+        {
+            if (!string.IsNullOrEmpty(_base_url))
+            {
+                await NifyPlus.SendAsync(_base_url, message);
+            }
+        }
+
+        public static async Task QYWeixinPushNotifyAsync(string title, string content)
+        {
+#if DEBUG
+            await Task.CompletedTask;
+#else
+            if (!string.IsNullOrEmpty(_qyweixin_push_url))
+            {
+                var args = new
+                {
+                    msgtype = "markdown",
+                    markdown = new
+                    {
+                        content = $"# {title}\n{content}",
+                    }
+                };
+                string body = string.Empty;
+                try
+                {
+                    WebClientUtility client = new WebClientUtility();
+                    client.Post(args.Convert2Json());
+                    client.SetContentType("application/json");
+                    var response = await client.RequestAsync(_qyweixin_push_url, "POST");
+                    body = response.Body();
+
+                    new LoggerLibrary("qyweixin").Info(title, content).Info(body).Save();
+                }
+                catch (Exception ex)
+                {
+                    new LoggerLibrary("qyweixin", "error")
+                        .Info(title, content)
+                        .Info(body)
+                        .Info(ex.Message, ex.StackTrace)
+                        .Save();
+                }
+            }
+#endif
+        }
+
+        // 同步版本保持不变
+        public static void AnPushNotify(string title, string content)
+        {
+#if DEBUG
+#else
+        if (!string.IsNullOrEmpty(_anpush_url))
+        {
+            string url = _anpush_url;
+            url = url.Replace("{title}", title?.UrlEncode());
+            url = url.Replace("{content}", content?.UrlEncode());
+
+            try
+            {
+                url.HttpRequest();
+            }
+            catch (Exception ex) { }
+        }
+#endif
+        }
+
+        // 新增异步版本
+        public static async Task AnPushNotifyAsync(string title, string content)
+        {
+#if DEBUG
+            await Task.CompletedTask;
+#else
+        if (!string.IsNullOrEmpty(_anpush_url))
+        {
+            string url = _anpush_url;
+            url = url.Replace("{title}", title?.UrlEncode());
+            url = url.Replace("{content}", content?.UrlEncode());
+
+            try
+            {
+                await Task.Run(() => url.HttpRequest());
+            }
+            catch (Exception ex) { }
+        }
+#endif
+        }
+    }
 }

+ 268 - 268
molilian.core/Core/OverrideRuleCore.cs

@@ -1,268 +1,268 @@
-using dodohold.core;
-using System.Collections.Generic;
-using System.Data;
-using System.Security.Cryptography;
-using System.Text.RegularExpressions;
-using System.Web;
-using YunhuiKit;
-
-
-namespace molilian.core
-{
-
-    public partial class OverrideRuleCore
-    {
-
-        private static IEnumerable<OverrideRuleDTO> _cached;
-
-        private static readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
-
-        public static async Task<IEnumerable<OverrideRuleDTO>> ListAsync(bool force = false)
-        {
-#if DEBUG
-
-            return new DBContext.Table("tk_override_rules")
-                   .Where("status=@status", new { status = 1 })
-                   .Order("sort DESC, id DESC")
-                   .Select<OverrideRuleDTO>();
-#endif
-
-            if (!force && _cached != null) return _cached;
-            try
-            {
-                await _semaphore.WaitAsync();
-
-                // 如果缓存存在且未强制刷新,直接返回
-                if (!force && _cached != null) return _cached;
-
-                string cache_key = $"cache:tk_override_rules";
-                IEnumerable<OverrideRuleDTO>? list = null;
-
-                // 尝试从Redis获取数据
-                try
-                {
-                    list = await RedisKit.GetAsync<IEnumerable<OverrideRuleDTO>>(cache_key);
-                }
-                catch (Exception ex)
-                {
-                    // 记录Redis错误
-                    _ = new LoggerLibrary("OverrideRule", "Redis").Info(ex.Message, ex.StackTrace).SaveAsync();
-                }
-
-                // 如果Redis获取失败或需要强制刷新
-                if (force || list == null)
-                {
-                    try
-                    {
-                        list = new DBContext.Table("tk_override_rules")
-                            .Where("status=@status", new { status = 1 })
-                            .Order("sort DESC, id DESC")
-                            .Select<OverrideRuleDTO>();
-
-                        if (list != null && list.Any())
-                        {
-                            // 尝试更新Redis缓存
-                            try
-                            {
-                                await RedisKit.SetAsync(cache_key, list, 30 * 86400);
-                            }
-                            catch (Exception ex)
-                            {
-                                // 记录Redis更新错误
-                                _ = new LoggerLibrary("OverrideRule", "Redis").Info(ex.Message, ex.StackTrace).SaveAsync();
-                            }
-                        }
-                    }
-                    catch (Exception ex)
-                    {
-                        // 记录数据库查询错误
-                        _ = new LoggerLibrary("OverrideRule", "Database").Info(ex.Message, ex.StackTrace).SaveAsync();
-
-                        // 如果数据库查询失败但缓存还在,继续使用缓存
-                        if (_cached != null) return _cached;
-                        throw; // 如果没有任何可用数据,则抛出异常
-                    }
-                }
-                _cached = list;
-                return list ?? [];
-            }
-            finally
-            {
-                _semaphore.Release();
-            }
-        }
-
-        public static async Task<OverrideRuleDTO> ProcessAsync(UnionParseRequest request)
-        {
-            if (string.IsNullOrEmpty(request.Channel)) return null;
-            var list = await ListAsync();
-            if (list == default) return null;
-            string content = request.Content;
-            if (request.SpecialText == 1) content = request.QueryText;
-            if (string.IsNullOrEmpty(content)) return null;
-
-            foreach (var item in list)
-            {
-                //
-                if (!request.Channel.Equals(item.platform)) continue;
-                if (item.use_special_text == 1 && request.SpecialText != 1) continue;
-
-                string original_text = item.original_text;
-                if (item.use_special_text == 1) original_text = request.QueryText;
-                if (string.IsNullOrEmpty(original_text)) continue;
-
-                switch (item.rule)
-                {
-                    case "regex":
-                        Match match = Regex.Match(content, original_text);
-                        if (!match.Success) continue;
-                        break;
-                    case "text":
-                        if (!content.Equals(original_text)) continue;
-                        break;
-                    default:
-                        if (!content.Contains(original_text)) continue;
-                        break;
-                }
-                if (item.use_special_text == 1 && request.SpecialText != 1) continue;
-
-                if (item.use_risk_control)
-                {
-                    bool is_ignore = AlimamaPlus.ShouldIgnoreRequest(request.Ip, request.Oaid, request.RiskStrategy, request.LaunchScene, out _);
-                    if (is_ignore) continue;
-                    is_ignore = AlimamaPlus.FlowControlIgnoreRequest(request.Ip, request.Oaid, request.RiskStrategy, request.LaunchScene, out _);
-                    if (is_ignore) continue;
-                }
-
-                // 每小时调用次数统计(无论是否设置限制都要记录)
-                string hourlyKey = $":override_rule_calls:{item.id}:{DateTime.Now:yyyyMMddHH}";
-                try
-                {
-                    long currentHourlyCalls = await RedisKit.IncrByAsync(hourlyKey);
-                    // 如果是第一次调用,设置过期时间为2小时
-                    if (currentHourlyCalls < 10)
-                    {
-                        await RedisKit.ExpireAsync(hourlyKey, 7200); // 2小时
-                    }
-
-                    // 如果设置了每小时限制且超过限制,跳过该规则
-                    if (item.hourly_calls_limit > 0 && currentHourlyCalls > item.hourly_calls_limit) continue;
-                }
-                catch (Exception ex)
-                {
-                    // Redis错误时记录日志但不阻塞业务
-                    _ = new LoggerLibrary("OverrideRule", "Redis").Info($"Hourly limit check failed: {ex.Message}", ex.StackTrace).SaveAsync();
-                }
-
-                // 每日调用次数统计(无论是否设置限制都要记录)
-                string dailyKey = $":override_rule_calls:{item.id}:{DateTime.Now:yyyyMMdd}";
-                try
-                {
-                    long currentDailyCalls = await RedisKit.IncrByAsync(dailyKey);
-                    if (currentDailyCalls < 10)
-                    {
-                        await RedisKit.ExpireAsync(dailyKey, 259200); // 3天
-                    }
-                    // 如果设置了每日限制且超过限制,跳过该规则
-                    if (item.daily_calls_limit > 0 && currentDailyCalls >= item.daily_calls_limit) continue;
-                }
-                catch (Exception ex)
-                {
-                    // Redis错误时记录日志但不阻塞业务
-                    _ = new LoggerLibrary("OverrideRule", "Redis").Info($"Daily limit check failed: {ex.Message}", ex.StackTrace).SaveAsync();
-                }
-
-                // 关键词统计 - 长期缓存(6个月)用于历史数据分析
-                await SaveKeywordStatisticsAsync(item.id, item.platform, original_text);
-
-                string output_text = item.output_text;
-                if (output_text.Contains("{url:query_text}")) output_text = output_text.Replace("{url:query_text}", HttpUtility.UrlEncode(original_text));
-                if (output_text.Contains("{url2:query_text}")) output_text = output_text.Replace("{url2:query_text}", HttpUtility.UrlEncode(HttpUtility.UrlEncode(original_text)));
-                if (output_text.Contains("{url3:query_text}")) output_text = output_text.Replace("{url3:query_text}", HttpUtility.UrlEncode(HttpUtility.UrlEncode(HttpUtility.UrlEncode(original_text))));
-                if (output_text.Contains("{query_text}")) output_text = output_text.Replace("{query_text}", original_text);
-                item.output_text = output_text;
-                return item;
-            }
-            return null;
-        }
-
-        /// <summary>
-        /// 保存关键词统计数据到Redis,用于长期数据分析
-        /// 缓存周期:6个月
-        /// </summary>
-        private static async Task SaveKeywordStatisticsAsync(int ruleId, string platform, string keyword)
-        {
-            try
-            {
-                // 6个月的秒数
-                const int sixMonthsInSeconds = 180 * 86400;
-
-                // 关键词总请求次数统计 - 按月份
-                string monthlyKey = $":keyword_stats:{platform}:{ruleId}:{DateTime.Now:yyyyMM}";
-                await RedisKit.IncrByAsync(monthlyKey);
-                await RedisKit.ExpireAsync(monthlyKey, sixMonthsInSeconds);
-
-                // 关键词总请求次数统计 - 按日
-                string dailyKey = $":keyword_stats:{platform}:{ruleId}:{DateTime.Now:yyyyMMdd}";
-                await RedisKit.IncrByAsync(dailyKey);
-                await RedisKit.ExpireAsync(dailyKey, sixMonthsInSeconds);
-
-                // 关键词总请求次数统计 - 按小时
-                string hourlyKey = $":keyword_stats:{platform}:{ruleId}:{DateTime.Now:yyyyMMddHH}";
-                await RedisKit.IncrByAsync(hourlyKey);
-                await RedisKit.ExpireAsync(hourlyKey, sixMonthsInSeconds);
-
-                // 平台维度统计
-                string platformMonthlyKey = $":keyword_stats:platform:{platform}:{DateTime.Now:yyyyMM}";
-                await RedisKit.IncrByAsync(platformMonthlyKey);
-                await RedisKit.ExpireAsync(platformMonthlyKey, sixMonthsInSeconds);
-
-                // 全局统计
-                string globalMonthlyKey = $":keyword_stats:global:{DateTime.Now:yyyyMM}";
-                await RedisKit.IncrByAsync(globalMonthlyKey);
-                await RedisKit.ExpireAsync(globalMonthlyKey, sixMonthsInSeconds);
-
-                // 记录关键词集合(用于后续查询有哪些关键词)
-                string keywordSetKey = $":keyword_stats:set:{platform}:{DateTime.Now:yyyyMM}";
-                await RedisKit.SAddAsync(keywordSetKey, keyword);
-                await RedisKit.ExpireAsync(keywordSetKey, sixMonthsInSeconds);
-            }
-            catch (Exception ex)
-            {
-                // Redis错误时记录日志但不阻塞业务
-                _ = new LoggerLibrary("OverrideRule", "KeywordStats").Info($"Keyword statistics save failed: {ex.Message}", ex.StackTrace).SaveAsync();
-            }
-        }
-
-        public static void Refresh()
-        {
-            _cached = null;
-            _ = ListAsync(true);
-        }
-
-
-        public static int Update(OverrideRuleDTO data, IDbConnection conn)
-        {
-            var result = (int)conn.Update<OverrideRuleDTO>(data, new { data.id });
-            _ = ListAsync(true);
-#if DEBUG
-#else
-            EndPointCore.NotifyReload();
-#endif
-            return result;
-        }
-
-        public static int Create(OverrideRuleDTO data, IDbConnection conn)
-        {
-            var result = (int)conn.Insert(data);
-            _ = ListAsync(true);
-#if DEBUG
-#else
-            EndPointCore.NotifyReload();
-#endif
-            return result;
-        }
-
-
-    }
-}
+using dodohold.core;
+using System.Collections.Generic;
+using System.Data;
+using System.Security.Cryptography;
+using System.Text.RegularExpressions;
+using System.Web;
+using YunhuiKit;
+
+
+namespace molilian.core
+{
+
+    public partial class OverrideRuleCore
+    {
+
+        private static IEnumerable<OverrideRuleDTO> _cached;
+
+        private static readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
+
+        public static async Task<IEnumerable<OverrideRuleDTO>> ListAsync(bool force = false)
+        {
+#if DEBUG
+
+            return new DBContext.Table("tk_override_rules")
+                   .Where("status=@status", new { status = 1 })
+                   .Order("sort DESC, id DESC")
+                   .Select<OverrideRuleDTO>();
+#endif
+
+            if (!force && _cached != null) return _cached;
+            try
+            {
+                await _semaphore.WaitAsync();
+
+                // 如果缓存存在且未强制刷新,直接返回
+                if (!force && _cached != null) return _cached;
+
+                string cache_key = $"cache:tk_override_rules";
+                IEnumerable<OverrideRuleDTO>? list = null;
+
+                // 尝试从Redis获取数据
+                try
+                {
+                    list = await RedisKit.GetAsync<IEnumerable<OverrideRuleDTO>>(cache_key);
+                }
+                catch (Exception ex)
+                {
+                    // 记录Redis错误
+                    _ = new LoggerLibrary("OverrideRule", "Redis").Info(ex.Message, ex.StackTrace).SaveAsync();
+                }
+
+                // 如果Redis获取失败或需要强制刷新
+                if (force || list == null)
+                {
+                    try
+                    {
+                        list = new DBContext.Table("tk_override_rules")
+                            .Where("status=@status", new { status = 1 })
+                            .Order("sort DESC, id DESC")
+                            .Select<OverrideRuleDTO>();
+
+                        if (list != null && list.Any())
+                        {
+                            // 尝试更新Redis缓存
+                            try
+                            {
+                                await RedisKit.SetAsync(cache_key, list, 30 * 86400);
+                            }
+                            catch (Exception ex)
+                            {
+                                // 记录Redis更新错误
+                                _ = new LoggerLibrary("OverrideRule", "Redis").Info(ex.Message, ex.StackTrace).SaveAsync();
+                            }
+                        }
+                    }
+                    catch (Exception ex)
+                    {
+                        // 记录数据库查询错误
+                        _ = new LoggerLibrary("OverrideRule", "Database").Info(ex.Message, ex.StackTrace).SaveAsync();
+
+                        // 如果数据库查询失败但缓存还在,继续使用缓存
+                        if (_cached != null) return _cached;
+                        throw; // 如果没有任何可用数据,则抛出异常
+                    }
+                }
+                _cached = list;
+                return list ?? [];
+            }
+            finally
+            {
+                _semaphore.Release();
+            }
+        }
+
+        public static async Task<OverrideRuleDTO> ProcessAsync(UnionParseRequest request)
+        {
+            if (string.IsNullOrEmpty(request.Channel)) return null;
+            var list = await ListAsync();
+            if (list == default) return null;
+            string content = request.Content;
+            if (request.SpecialText == 1) content = request.QueryText;
+            if (string.IsNullOrEmpty(content)) return null;
+
+            foreach (var item in list)
+            {
+                //
+                if (!request.Channel.Equals(item.platform)) continue;
+                if (item.use_special_text == 1 && request.SpecialText != 1) continue;
+
+                string original_text = item.original_text;
+                if (item.use_special_text == 1) original_text = request.QueryText;
+                if (string.IsNullOrEmpty(original_text)) continue;
+
+                switch (item.rule)
+                {
+                    case "regex":
+                        Match match = Regex.Match(content, original_text);
+                        if (!match.Success) continue;
+                        break;
+                    case "text":
+                        if (!content.Equals(original_text)) continue;
+                        break;
+                    default:
+                        if (!content.Contains(original_text)) continue;
+                        break;
+                }
+                if (item.use_special_text == 1 && request.SpecialText != 1) continue;
+
+                if (item.use_risk_control)
+                {
+                    bool is_ignore = AlimamaPlus.ShouldIgnoreRequest(request.Ip, request.Oaid, request.RiskStrategy, request.LaunchScene, out _);
+                    if (is_ignore) continue;
+                    is_ignore = AlimamaPlus.FlowControlIgnoreRequest(request.Ip, request.Oaid, request.RiskStrategy, request.LaunchScene, out _);
+                    if (is_ignore) continue;
+                }
+
+                // 每小时调用次数统计(无论是否设置限制都要记录)
+                string hourlyKey = $":override_rule_calls:{item.id}:{DateTime.Now:yyyyMMddHH}";
+                try
+                {
+                    long currentHourlyCalls = await RedisKit.IncrByAsync(hourlyKey);
+                    // 如果是第一次调用,设置过期时间为2小时
+                    if (currentHourlyCalls < 10)
+                    {
+                        await RedisKit.ExpireAsync(hourlyKey, 7200); // 2小时
+                    }
+
+                    // 如果设置了每小时限制且超过限制,跳过该规则
+                    if (item.hourly_calls_limit > 0 && currentHourlyCalls > item.hourly_calls_limit) continue;
+                }
+                catch (Exception ex)
+                {
+                    // Redis错误时记录日志但不阻塞业务
+                    _ = new LoggerLibrary("OverrideRule", "Redis").Info($"Hourly limit check failed: {ex.Message}", ex.StackTrace).SaveAsync();
+                }
+
+                // 每日调用次数统计(无论是否设置限制都要记录)
+                string dailyKey = $":override_rule_calls:{item.id}:{DateTime.Now:yyyyMMdd}";
+                try
+                {
+                    long currentDailyCalls = await RedisKit.IncrByAsync(dailyKey);
+                    if (currentDailyCalls < 10)
+                    {
+                        await RedisKit.ExpireAsync(dailyKey, 259200); // 3天
+                    }
+                    // 如果设置了每日限制且超过限制,跳过该规则
+                    if (item.daily_calls_limit > 0 && currentDailyCalls >= item.daily_calls_limit) continue;
+                }
+                catch (Exception ex)
+                {
+                    // Redis错误时记录日志但不阻塞业务
+                    _ = new LoggerLibrary("OverrideRule", "Redis").Info($"Daily limit check failed: {ex.Message}", ex.StackTrace).SaveAsync();
+                }
+
+                // 关键词统计 - 长期缓存(6个月)用于历史数据分析
+                await SaveKeywordStatisticsAsync(item.id, item.platform, original_text);
+
+                string output_text = item.output_text;
+                if (output_text.Contains("{url:query_text}")) output_text = output_text.Replace("{url:query_text}", HttpUtility.UrlEncode(original_text));
+                if (output_text.Contains("{url2:query_text}")) output_text = output_text.Replace("{url2:query_text}", HttpUtility.UrlEncode(HttpUtility.UrlEncode(original_text)));
+                if (output_text.Contains("{url3:query_text}")) output_text = output_text.Replace("{url3:query_text}", HttpUtility.UrlEncode(HttpUtility.UrlEncode(HttpUtility.UrlEncode(original_text))));
+                if (output_text.Contains("{query_text}")) output_text = output_text.Replace("{query_text}", original_text);
+                item.output_text = output_text;
+                return item;
+            }
+            return null;
+        }
+
+        /// <summary>
+        /// 保存关键词统计数据到Redis,用于长期数据分析
+        /// 缓存周期:6个月
+        /// </summary>
+        private static async Task SaveKeywordStatisticsAsync(int ruleId, string platform, string keyword)
+        {
+            try
+            {
+                // 6个月的秒数
+                const int sixMonthsInSeconds = 180 * 86400;
+
+                // 关键词总请求次数统计 - 按月份
+                string monthlyKey = $":keyword_stats:{platform}:{ruleId}:{DateTime.Now:yyyyMM}";
+                await RedisKit.IncrByAsync(monthlyKey);
+                await RedisKit.ExpireAsync(monthlyKey, sixMonthsInSeconds);
+
+                // 关键词总请求次数统计 - 按日
+                string dailyKey = $":keyword_stats:{platform}:{ruleId}:{DateTime.Now:yyyyMMdd}";
+                await RedisKit.IncrByAsync(dailyKey);
+                await RedisKit.ExpireAsync(dailyKey, sixMonthsInSeconds);
+
+                // 关键词总请求次数统计 - 按小时
+                string hourlyKey = $":keyword_stats:{platform}:{ruleId}:{DateTime.Now:yyyyMMddHH}";
+                await RedisKit.IncrByAsync(hourlyKey);
+                await RedisKit.ExpireAsync(hourlyKey, sixMonthsInSeconds);
+
+                // 平台维度统计
+                string platformMonthlyKey = $":keyword_stats:platform:{platform}:{DateTime.Now:yyyyMM}";
+                await RedisKit.IncrByAsync(platformMonthlyKey);
+                await RedisKit.ExpireAsync(platformMonthlyKey, sixMonthsInSeconds);
+
+                // 全局统计
+                string globalMonthlyKey = $":keyword_stats:global:{DateTime.Now:yyyyMM}";
+                await RedisKit.IncrByAsync(globalMonthlyKey);
+                await RedisKit.ExpireAsync(globalMonthlyKey, sixMonthsInSeconds);
+
+                // 记录关键词集合(用于后续查询有哪些关键词)
+                string keywordSetKey = $":keyword_stats:set:{platform}:{DateTime.Now:yyyyMM}";
+                await RedisKit.SAddAsync(keywordSetKey, keyword);
+                await RedisKit.ExpireAsync(keywordSetKey, sixMonthsInSeconds);
+            }
+            catch (Exception ex)
+            {
+                // Redis错误时记录日志但不阻塞业务
+                _ = new LoggerLibrary("OverrideRule", "KeywordStats").Info($"Keyword statistics save failed: {ex.Message}", ex.StackTrace).SaveAsync();
+            }
+        }
+
+        public static void Refresh()
+        {
+            _cached = null;
+            _ = ListAsync(true);
+        }
+
+
+        public static int Update(OverrideRuleDTO data, IDbConnection conn)
+        {
+            var result = (int)conn.Update<OverrideRuleDTO>(data, new { data.id });
+            _ = ListAsync(true);
+#if DEBUG
+#else
+            EndPointCore.NotifyReload();
+#endif
+            return result;
+        }
+
+        public static int Create(OverrideRuleDTO data, IDbConnection conn)
+        {
+            var result = (int)conn.Insert(data);
+            _ = ListAsync(true);
+#if DEBUG
+#else
+            EndPointCore.NotifyReload();
+#endif
+            return result;
+        }
+
+
+    }
+}

+ 191 - 191
molilian.core/Core/ProxyNodesCore.cs

@@ -1,191 +1,191 @@
-using dodohold.core;
-using System.Net;
-
-
-namespace molilian.core
-{
-    public partial class ProxyNodesCore
-    {
-
-
-        private static string _end_point;
-        static ProxyNodesCore()
-        {
-            _end_point = Environment.GetEnvironmentVariable("EndPoint");
-        }
-
-
-        private static readonly object _lockObj = new();
-        private static IEnumerable<ProxyNodesDTO> _cached;
-        private static IEnumerable<ProxyNodesDTO> _all_cached;
-        public static string GetSelectOne(string node, bool all_node = false)
-        {
-            var list = all_node ? AllList() : List();
-            if (!list.Any()) return null;
-            if (!node.Contains(",")) return node;
-
-            // 处理包含逗号的节点参数
-            string selectedNode = node;
-            var nodes = node.Split(',', StringSplitOptions.RemoveEmptyEntries)
-                           .Select(n => n.Trim())
-                           .Where(n => !string.IsNullOrEmpty(n))
-                           .ToArray();
-
-            if (nodes.Length > 0)
-            {
-                // 随机选择一个节点
-                var random = new Random();
-                selectedNode = nodes[random.Next(nodes.Length)];
-            }
-            return selectedNode;
-        }
-
-
-        public static WebProxy? GetOne(string node, bool all_node = false)
-        {
-            var list = all_node ? AllList() : List();
-            if (!list.Any()) return null;
-
-            // 处理包含逗号的节点参数
-            string selectedNode = node;
-            if (node.Contains(","))
-            {
-                var nodes = node.Split(',', StringSplitOptions.RemoveEmptyEntries)
-                               .Select(n => n.Trim())
-                               .Where(n => !string.IsNullOrEmpty(n))
-                               .ToArray();
-
-                if (nodes.Length > 0)
-                {
-                    // 随机选择一个节点
-                    var random = new Random();
-                    selectedNode = nodes[random.Next(nodes.Length)];
-                }
-            }
-
-            var ProxyNodesDTO = list.Where(e => SelectEndPointUseProxy(e, selectedNode))
-                                   .OrderBy(l => Guid.NewGuid())
-                                   .FirstOrDefault();
-            if (ProxyNodesDTO == null) return null;
-
-
-            return new WebProxy
-            {
-                Address = new Uri(ProxyNodesDTO.server),
-                Credentials = new NetworkCredential(ProxyNodesDTO.username, ProxyNodesDTO.password)
-            };
-        }
-
-
-
-
-
-        public static WebProxy? GetOne(int nodeid)
-        {
-            var list = List();
-            if (!list.Any()) return null;
-
-            var ProxyNodesDTO = list.Where(e => SelectEndPointUseProxy(e, nodeid)).OrderBy(l => Guid.NewGuid()).FirstOrDefault();
-            if (ProxyNodesDTO == null) return null;
-
-            return new WebProxy
-            {
-                Address = new Uri(ProxyNodesDTO.server),
-                Credentials = new NetworkCredential(ProxyNodesDTO.username, ProxyNodesDTO.password)
-            };
-        }
-
-
-        private static bool SelectEndPointUseProxy(ProxyNodesDTO item, string node = "")
-        {
-            if (!string.IsNullOrEmpty(_end_point) && !string.IsNullOrEmpty(item.end_point))
-            {
-                if (item.end_point != _end_point) return false;
-            }
-            return string.IsNullOrEmpty(node) || item.name == node;
-        }
-        private static bool SelectEndPointUseProxy(ProxyNodesDTO item, int nodeid)
-        {
-            if (!string.IsNullOrEmpty(_end_point) && !string.IsNullOrEmpty(item.end_point))
-            {
-                if (item.end_point != _end_point) return false;
-            }
-            return nodeid > 0 || item.id == nodeid;
-        }
-
-        public static WebProxy? RandomOne(string excludeNode = "")
-        {
-#if DEBUG
-            return null;
-#endif
-
-            var list = List();
-            if (!list.Any()) return null;
-            var ProxyNodesDTO = list.Where(e => SelectEndPointExcludeNodeUseProxy(e, string.Empty)).OrderBy(l => Guid.NewGuid()).FirstOrDefault();
-            if (ProxyNodesDTO == null) return null;
-
-            return new WebProxy
-            {
-                Address = new Uri(ProxyNodesDTO.server),
-                Credentials = new NetworkCredential(ProxyNodesDTO.username, ProxyNodesDTO.password)
-            };
-        }
-        private static bool SelectEndPointExcludeNodeUseProxy(ProxyNodesDTO item, string excludeNode = "")
-        {
-            if (!string.IsNullOrEmpty(_end_point) && !string.IsNullOrEmpty(item.end_point))
-            {
-                if (item.end_point != _end_point) return false;
-            }
-            return string.IsNullOrEmpty(excludeNode) || item.name != excludeNode;
-        }
-
-        public static IEnumerable<ProxyNodesDTO> List(bool force = false)
-        {
-            if (!force && _cached != null) return _cached;
-
-            string cache_key = $"cache:proxy_nodes";
-            var list = RedisHelper.Get<IEnumerable<ProxyNodesDTO>>(cache_key);
-            if (force || list == null)
-            {
-                lock (_lockObj)
-                {
-                    list = new DBContext.Table("proxy_nodes")
-                        .Where("status=@status", new { status = 1 })
-                        .Select<ProxyNodesDTO>();
-                    if (list == null) return default;
-                    RedisHelper.Set(cache_key, list, 30 * 86400);
-                }
-            }
-            _cached = list;
-            return list;
-        }
-
-        public static IEnumerable<ProxyNodesDTO> AllList(bool force = false)
-        {
-            if (!force && _all_cached != null) return _all_cached;
-
-            string cache_key = $"cache:all_proxy_nodes";
-            var list = RedisHelper.Get<IEnumerable<ProxyNodesDTO>>(cache_key);
-            if (force || list == null)
-            {
-                lock (_lockObj)
-                {
-                    list = new DBContext.Table("proxy_nodes")
-                        .Where("", new { status = 1 })
-                        .Select<ProxyNodesDTO>();
-                    if (list == null) return default;
-                    RedisHelper.Set(cache_key, list, 30 * 86400);
-                }
-            }
-            _all_cached = list;
-            return list;
-        }
-        public static void Refresh()
-        {
-            _ = List(true);
-            _ = AllList(true);
-        }
-
-    }
-
-}
+using dodohold.core;
+using System.Net;
+
+
+namespace molilian.core
+{
+    public partial class ProxyNodesCore
+    {
+
+
+        private static string _end_point;
+        static ProxyNodesCore()
+        {
+            _end_point = Environment.GetEnvironmentVariable("EndPoint");
+        }
+
+
+        private static readonly object _lockObj = new();
+        private static IEnumerable<ProxyNodesDTO> _cached;
+        private static IEnumerable<ProxyNodesDTO> _all_cached;
+        public static string GetSelectOne(string node, bool all_node = false)
+        {
+            var list = all_node ? AllList() : List();
+            if (!list.Any()) return null;
+            if (!node.Contains(",")) return node;
+
+            // 处理包含逗号的节点参数
+            string selectedNode = node;
+            var nodes = node.Split(',', StringSplitOptions.RemoveEmptyEntries)
+                           .Select(n => n.Trim())
+                           .Where(n => !string.IsNullOrEmpty(n))
+                           .ToArray();
+
+            if (nodes.Length > 0)
+            {
+                // 随机选择一个节点
+                var random = new Random();
+                selectedNode = nodes[random.Next(nodes.Length)];
+            }
+            return selectedNode;
+        }
+
+
+        public static WebProxy? GetOne(string node, bool all_node = false)
+        {
+            var list = all_node ? AllList() : List();
+            if (!list.Any()) return null;
+
+            // 处理包含逗号的节点参数
+            string selectedNode = node;
+            if (node.Contains(","))
+            {
+                var nodes = node.Split(',', StringSplitOptions.RemoveEmptyEntries)
+                               .Select(n => n.Trim())
+                               .Where(n => !string.IsNullOrEmpty(n))
+                               .ToArray();
+
+                if (nodes.Length > 0)
+                {
+                    // 随机选择一个节点
+                    var random = new Random();
+                    selectedNode = nodes[random.Next(nodes.Length)];
+                }
+            }
+
+            var ProxyNodesDTO = list.Where(e => SelectEndPointUseProxy(e, selectedNode))
+                                   .OrderBy(l => Guid.NewGuid())
+                                   .FirstOrDefault();
+            if (ProxyNodesDTO == null) return null;
+
+
+            return new WebProxy
+            {
+                Address = new Uri(ProxyNodesDTO.server),
+                Credentials = new NetworkCredential(ProxyNodesDTO.username, ProxyNodesDTO.password)
+            };
+        }
+
+
+
+
+
+        public static WebProxy? GetOne(int nodeid)
+        {
+            var list = List();
+            if (!list.Any()) return null;
+
+            var ProxyNodesDTO = list.Where(e => SelectEndPointUseProxy(e, nodeid)).OrderBy(l => Guid.NewGuid()).FirstOrDefault();
+            if (ProxyNodesDTO == null) return null;
+
+            return new WebProxy
+            {
+                Address = new Uri(ProxyNodesDTO.server),
+                Credentials = new NetworkCredential(ProxyNodesDTO.username, ProxyNodesDTO.password)
+            };
+        }
+
+
+        private static bool SelectEndPointUseProxy(ProxyNodesDTO item, string node = "")
+        {
+            if (!string.IsNullOrEmpty(_end_point) && !string.IsNullOrEmpty(item.end_point))
+            {
+                if (item.end_point != _end_point) return false;
+            }
+            return string.IsNullOrEmpty(node) || item.name == node;
+        }
+        private static bool SelectEndPointUseProxy(ProxyNodesDTO item, int nodeid)
+        {
+            if (!string.IsNullOrEmpty(_end_point) && !string.IsNullOrEmpty(item.end_point))
+            {
+                if (item.end_point != _end_point) return false;
+            }
+            return nodeid > 0 || item.id == nodeid;
+        }
+
+        public static WebProxy? RandomOne(string excludeNode = "")
+        {
+#if DEBUG
+            return null;
+#endif
+
+            var list = List();
+            if (!list.Any()) return null;
+            var ProxyNodesDTO = list.Where(e => SelectEndPointExcludeNodeUseProxy(e, string.Empty)).OrderBy(l => Guid.NewGuid()).FirstOrDefault();
+            if (ProxyNodesDTO == null) return null;
+
+            return new WebProxy
+            {
+                Address = new Uri(ProxyNodesDTO.server),
+                Credentials = new NetworkCredential(ProxyNodesDTO.username, ProxyNodesDTO.password)
+            };
+        }
+        private static bool SelectEndPointExcludeNodeUseProxy(ProxyNodesDTO item, string excludeNode = "")
+        {
+            if (!string.IsNullOrEmpty(_end_point) && !string.IsNullOrEmpty(item.end_point))
+            {
+                if (item.end_point != _end_point) return false;
+            }
+            return string.IsNullOrEmpty(excludeNode) || item.name != excludeNode;
+        }
+
+        public static IEnumerable<ProxyNodesDTO> List(bool force = false)
+        {
+            if (!force && _cached != null) return _cached;
+
+            string cache_key = $"cache:proxy_nodes";
+            var list = RedisHelper.Get<IEnumerable<ProxyNodesDTO>>(cache_key);
+            if (force || list == null)
+            {
+                lock (_lockObj)
+                {
+                    list = new DBContext.Table("proxy_nodes")
+                        .Where("status=@status", new { status = 1 })
+                        .Select<ProxyNodesDTO>();
+                    if (list == null) return default;
+                    RedisHelper.Set(cache_key, list, 30 * 86400);
+                }
+            }
+            _cached = list;
+            return list;
+        }
+
+        public static IEnumerable<ProxyNodesDTO> AllList(bool force = false)
+        {
+            if (!force && _all_cached != null) return _all_cached;
+
+            string cache_key = $"cache:all_proxy_nodes";
+            var list = RedisHelper.Get<IEnumerable<ProxyNodesDTO>>(cache_key);
+            if (force || list == null)
+            {
+                lock (_lockObj)
+                {
+                    list = new DBContext.Table("proxy_nodes")
+                        .Where("", new { status = 1 })
+                        .Select<ProxyNodesDTO>();
+                    if (list == null) return default;
+                    RedisHelper.Set(cache_key, list, 30 * 86400);
+                }
+            }
+            _all_cached = list;
+            return list;
+        }
+        public static void Refresh()
+        {
+            _ = List(true);
+            _ = AllList(true);
+        }
+
+    }
+
+}

+ 411 - 411
molilian.core/Core/PushDataReport/Enum.cs

@@ -1,411 +1,411 @@
-using Microsoft.AspNetCore.Http;
-using Microsoft.AspNetCore.Mvc.Controllers;
-using Microsoft.AspNetCore.Mvc.Filters;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using dodohold.core;
-using System.Xml.Linq;
-using YunhuiKit;
-using System.Text.Json;
-using COSXML.Network;
-
-
-namespace molilian.core.PushDataReport;
-
-public class ResponseDTO
-{
-    public int code { get; set; } = 0;
-    public string message { get; set; } = string.Empty;
-    public string requestId { get; set; } = string.Empty;
-}
-
-public enum PlatformEnum
-{
-    /// <summary>
-    /// 未知
-    /// </summary>
-    Unknown = 0,
-
-    /// <summary>
-    /// 淘宝
-    /// </summary>
-    Taobao = 1,
-
-    /// <summary>
-    /// 抖音
-    /// </summary>
-    Douyin = 2,
-
-    /// <summary>
-    /// 天猫
-    /// </summary>
-    Tmall = 4,
-
-    /// <summary>
-    /// 百度网盘
-    /// </summary>
-    BaiduNetdisk = 7,
-
-    /// <summary>
-    /// 腾讯会议
-    /// </summary>
-    TencentMeeting = 8,
-
-    /// <summary>
-    /// 拼多多
-    /// </summary>
-    Pinduoduo = 9,
-
-    /// <summary>
-    /// 拼多多H5
-    /// </summary>
-    PinduoduoH5 = 10,
-
-    /// <summary>
-    /// 支付宝
-    /// </summary>
-    Alipay = 11,
-
-    /// <summary>
-    /// 快手
-    /// </summary>
-    Kuaishou = 12,
-
-    /// <summary>
-    /// 京东
-    /// </summary>
-    JD = 13,
-
-    /// <summary>
-    /// 安居客
-    /// </summary>
-    Anjuke = 27,
-
-    /// <summary>
-    /// 抖音商城
-    /// </summary>
-    DouyinMall = 28,
-
-    /// <summary>
-    /// 番茄畅听音乐版
-    /// </summary>
-    FanqieMusic = 29,
-
-    /// <summary>
-    /// 唯品会
-    /// </summary>
-    Vipshop = 30,
-
-    /// <summary>
-    /// 京东金融
-    /// </summary>
-    JDFinance = 32,
-
-    /// <summary>
-    /// 爱奇艺极速版
-    /// </summary>
-    IqiyiLite = 33,
-
-    /// <summary>
-    /// 安逸花
-    /// </summary>
-    Anyihua = 34,
-
-    /// <summary>
-    /// 同程旅行
-    /// </summary>
-    TongchengTravel = 35,
-
-    /// <summary>
-    /// 河马剧场
-    /// </summary>
-    HemaTheater = 36,
-
-    /// <summary>
-    /// BOSS直聘
-    /// </summary>
-    BossZhipin = 37,
-
-    /// <summary>
-    /// 喜马拉雅
-    /// </summary>
-    Ximalaya = 38,
-
-    /// <summary>
-    /// QQ浏览器
-    /// </summary>
-    QQBrowser = 39,
-
-    /// <summary>
-    /// 咪咕视频
-    /// </summary>
-    MiguVideo = 40,
-
-    /// <summary>
-    /// 飞猪旅行
-    /// </summary>
-    Fliggy = 41,
-
-    /// <summary>
-    /// 途虎养车
-    /// </summary>
-    Tuhu = 42,
-
-    /// <summary>
-    /// 桔多多
-    /// </summary>
-    Juduoduo = 43,
-
-    /// <summary>
-    /// Keep
-    /// </summary>
-    Keep = 44,
-
-    /// <summary>
-    /// 百度文库
-    /// </summary>
-    BaiduWenku = 45,
-
-    /// <summary>
-    /// TT语音
-    /// </summary>
-    TTVoice = 46,
-
-    /// <summary>
-    /// 通义
-    /// </summary>
-    Tongyi = 47,
-
-    /// <summary>
-    /// 起点读书
-    /// </summary>
-    Qidian = 48,
-
-    /// <summary>
-    /// 搜狐新闻
-    /// </summary>
-    SohuNews = 49,
-
-    /// <summary>
-    /// YY
-    /// </summary>
-    YY = 50,
-
-    /// <summary>
-    /// 腾讯新闻
-    /// </summary>
-    TencentNews = 52,
-
-    /// <summary>
-    /// 网易云音乐
-    /// </summary>
-    NetEaseMusic = 53,
-
-    /// <summary>
-    /// QQ音乐
-    /// </summary>
-    QQMusic = 54,
-
-    /// <summary>
-    /// 酷狗音乐
-    /// </summary>
-    KugouMusic = 55,
-
-    /// <summary>
-    /// 全民K歌
-    /// </summary>
-    QuanminKge = 56,
-
-    /// <summary>
-    /// 识货
-    /// </summary>
-    Shihuo = 57,
-
-    /// <summary>
-    /// 酷我音乐
-    /// </summary>
-    KuwoMusic = 58,
-
-    /// <summary>
-    /// 微博
-    /// </summary>
-    Weibo = 59,
-
-    /// <summary>
-    /// 转转
-    /// </summary>
-    Zhuanzhuan = 60,
-
-    /// <summary>
-    /// 快手极速版
-    /// </summary>
-    KuaishouLite = 61,
-
-    /// <summary>
-    /// 抖音极速版
-    /// </summary>
-    DouyinLite = 62,
-
-    /// <summary>
-    /// 百度极速版
-    /// </summary>
-    BaiduLite = 63,
-
-    /// <summary>
-    /// 搜狐视频
-    /// </summary>
-    SohuVideo = 64,
-
-    /// <summary>
-    /// 酷安
-    /// </summary>
-    Coolapk = 65,
-
-    /// <summary>
-    /// 好看视频
-    /// </summary>
-    HaokanVideo = 66,
-
-    /// <summary>
-    /// 大众点评
-    /// </summary>
-    Dianping = 67,
-
-    /// <summary>
-    /// 虎牙直播
-    /// </summary>
-    Huya = 68,
-
-    /// <summary>
-    /// 番茄免费小说
-    /// </summary>
-    FanqieNovel = 101,
-
-    /// <summary>
-    /// 红果免费短剧
-    /// </summary>
-    HongguoDrama = 102,
-
-    /// <summary>
-    /// 阿里巴巴
-    /// </summary>
-    Alibaba = 103,
-
-    /// <summary>
-    /// 百度
-    /// </summary>
-    Baidu = 104,
-
-    /// <summary>
-    /// 番茄畅听
-    /// </summary>
-    FanqieAudio = 105,
-
-    /// <summary>
-    /// 小红书
-    /// </summary>
-    Xiaohongshu = 106,
-
-    /// <summary>
-    /// 美团
-    /// </summary>
-    Meituan = 107,
-
-    /// <summary>
-    /// 西瓜视频
-    /// </summary>
-    XiguaVideo = 108,
-
-    /// <summary>
-    /// 抖音火山版
-    /// </summary>
-    DouyinHuoshan = 109,
-
-    /// <summary>
-    /// 饿了么
-    /// </summary>
-    Eleme = 110,
-
-    /// <summary>
-    /// 优酷视频
-    /// </summary>
-    Youku = 111,
-
-    /// <summary>
-    /// 今日头条
-    /// </summary>
-    Toutiao = 112,
-
-    /// <summary>
-    /// 得物
-    /// </summary>
-    Dewu = 113,
-
-    /// <summary>
-    /// 七猫免费小说
-    /// </summary>
-    QimaoNovel = 114,
-
-    /// <summary>
-    /// 夸克
-    /// </summary>
-    Quark = 115,
-
-    /// <summary>
-    /// 悟空浏览器
-    /// </summary>
-    WukongBrowser = 116,
-
-    /// <summary>
-    /// 豆包
-    /// </summary>
-    Doubao = 117,
-
-    /// <summary>
-    /// 懂车帝
-    /// </summary>
-    Dongchedi = 118,
-
-    /// <summary>
-    /// 腾讯视频
-    /// </summary>
-    TencentVideo = 119,
-
-    /// <summary>
-    /// 哔哩哔哩
-    /// </summary>
-    Bilibili = 120,
-
-    /// <summary>
-    /// 头条搜索极速版
-    /// </summary>
-    ToutiaoSearchLite = 121,
-
-    /// <summary>
-    /// 爱奇艺
-    /// </summary>
-    Iqiyi = 122,
-
-    /// <summary>
-    /// 闲鱼
-    /// </summary>
-    Xianyu = 123,
-
-    /// <summary>
-    /// 携程旅行
-    /// </summary>
-    Ctrip = 124,
-
-    /// <summary>
-    /// Soul
-    /// </summary>
-    Soul = 125,
-
-    /// <summary>
-    /// UC浏览器
-    /// </summary>
-    UCBrowser = 126
-}
-
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc.Controllers;
+using Microsoft.AspNetCore.Mvc.Filters;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using dodohold.core;
+using System.Xml.Linq;
+using YunhuiKit;
+using System.Text.Json;
+using COSXML.Network;
+
+
+namespace molilian.core.PushDataReport;
+
+public class ResponseDTO
+{
+    public int code { get; set; } = 0;
+    public string message { get; set; } = string.Empty;
+    public string requestId { get; set; } = string.Empty;
+}
+
+public enum PlatformEnum
+{
+    /// <summary>
+    /// 未知
+    /// </summary>
+    Unknown = 0,
+
+    /// <summary>
+    /// 淘宝
+    /// </summary>
+    Taobao = 1,
+
+    /// <summary>
+    /// 抖音
+    /// </summary>
+    Douyin = 2,
+
+    /// <summary>
+    /// 天猫
+    /// </summary>
+    Tmall = 4,
+
+    /// <summary>
+    /// 百度网盘
+    /// </summary>
+    BaiduNetdisk = 7,
+
+    /// <summary>
+    /// 腾讯会议
+    /// </summary>
+    TencentMeeting = 8,
+
+    /// <summary>
+    /// 拼多多
+    /// </summary>
+    Pinduoduo = 9,
+
+    /// <summary>
+    /// 拼多多H5
+    /// </summary>
+    PinduoduoH5 = 10,
+
+    /// <summary>
+    /// 支付宝
+    /// </summary>
+    Alipay = 11,
+
+    /// <summary>
+    /// 快手
+    /// </summary>
+    Kuaishou = 12,
+
+    /// <summary>
+    /// 京东
+    /// </summary>
+    JD = 13,
+
+    /// <summary>
+    /// 安居客
+    /// </summary>
+    Anjuke = 27,
+
+    /// <summary>
+    /// 抖音商城
+    /// </summary>
+    DouyinMall = 28,
+
+    /// <summary>
+    /// 番茄畅听音乐版
+    /// </summary>
+    FanqieMusic = 29,
+
+    /// <summary>
+    /// 唯品会
+    /// </summary>
+    Vipshop = 30,
+
+    /// <summary>
+    /// 京东金融
+    /// </summary>
+    JDFinance = 32,
+
+    /// <summary>
+    /// 爱奇艺极速版
+    /// </summary>
+    IqiyiLite = 33,
+
+    /// <summary>
+    /// 安逸花
+    /// </summary>
+    Anyihua = 34,
+
+    /// <summary>
+    /// 同程旅行
+    /// </summary>
+    TongchengTravel = 35,
+
+    /// <summary>
+    /// 河马剧场
+    /// </summary>
+    HemaTheater = 36,
+
+    /// <summary>
+    /// BOSS直聘
+    /// </summary>
+    BossZhipin = 37,
+
+    /// <summary>
+    /// 喜马拉雅
+    /// </summary>
+    Ximalaya = 38,
+
+    /// <summary>
+    /// QQ浏览器
+    /// </summary>
+    QQBrowser = 39,
+
+    /// <summary>
+    /// 咪咕视频
+    /// </summary>
+    MiguVideo = 40,
+
+    /// <summary>
+    /// 飞猪旅行
+    /// </summary>
+    Fliggy = 41,
+
+    /// <summary>
+    /// 途虎养车
+    /// </summary>
+    Tuhu = 42,
+
+    /// <summary>
+    /// 桔多多
+    /// </summary>
+    Juduoduo = 43,
+
+    /// <summary>
+    /// Keep
+    /// </summary>
+    Keep = 44,
+
+    /// <summary>
+    /// 百度文库
+    /// </summary>
+    BaiduWenku = 45,
+
+    /// <summary>
+    /// TT语音
+    /// </summary>
+    TTVoice = 46,
+
+    /// <summary>
+    /// 通义
+    /// </summary>
+    Tongyi = 47,
+
+    /// <summary>
+    /// 起点读书
+    /// </summary>
+    Qidian = 48,
+
+    /// <summary>
+    /// 搜狐新闻
+    /// </summary>
+    SohuNews = 49,
+
+    /// <summary>
+    /// YY
+    /// </summary>
+    YY = 50,
+
+    /// <summary>
+    /// 腾讯新闻
+    /// </summary>
+    TencentNews = 52,
+
+    /// <summary>
+    /// 网易云音乐
+    /// </summary>
+    NetEaseMusic = 53,
+
+    /// <summary>
+    /// QQ音乐
+    /// </summary>
+    QQMusic = 54,
+
+    /// <summary>
+    /// 酷狗音乐
+    /// </summary>
+    KugouMusic = 55,
+
+    /// <summary>
+    /// 全民K歌
+    /// </summary>
+    QuanminKge = 56,
+
+    /// <summary>
+    /// 识货
+    /// </summary>
+    Shihuo = 57,
+
+    /// <summary>
+    /// 酷我音乐
+    /// </summary>
+    KuwoMusic = 58,
+
+    /// <summary>
+    /// 微博
+    /// </summary>
+    Weibo = 59,
+
+    /// <summary>
+    /// 转转
+    /// </summary>
+    Zhuanzhuan = 60,
+
+    /// <summary>
+    /// 快手极速版
+    /// </summary>
+    KuaishouLite = 61,
+
+    /// <summary>
+    /// 抖音极速版
+    /// </summary>
+    DouyinLite = 62,
+
+    /// <summary>
+    /// 百度极速版
+    /// </summary>
+    BaiduLite = 63,
+
+    /// <summary>
+    /// 搜狐视频
+    /// </summary>
+    SohuVideo = 64,
+
+    /// <summary>
+    /// 酷安
+    /// </summary>
+    Coolapk = 65,
+
+    /// <summary>
+    /// 好看视频
+    /// </summary>
+    HaokanVideo = 66,
+
+    /// <summary>
+    /// 大众点评
+    /// </summary>
+    Dianping = 67,
+
+    /// <summary>
+    /// 虎牙直播
+    /// </summary>
+    Huya = 68,
+
+    /// <summary>
+    /// 番茄免费小说
+    /// </summary>
+    FanqieNovel = 101,
+
+    /// <summary>
+    /// 红果免费短剧
+    /// </summary>
+    HongguoDrama = 102,
+
+    /// <summary>
+    /// 阿里巴巴
+    /// </summary>
+    Alibaba = 103,
+
+    /// <summary>
+    /// 百度
+    /// </summary>
+    Baidu = 104,
+
+    /// <summary>
+    /// 番茄畅听
+    /// </summary>
+    FanqieAudio = 105,
+
+    /// <summary>
+    /// 小红书
+    /// </summary>
+    Xiaohongshu = 106,
+
+    /// <summary>
+    /// 美团
+    /// </summary>
+    Meituan = 107,
+
+    /// <summary>
+    /// 西瓜视频
+    /// </summary>
+    XiguaVideo = 108,
+
+    /// <summary>
+    /// 抖音火山版
+    /// </summary>
+    DouyinHuoshan = 109,
+
+    /// <summary>
+    /// 饿了么
+    /// </summary>
+    Eleme = 110,
+
+    /// <summary>
+    /// 优酷视频
+    /// </summary>
+    Youku = 111,
+
+    /// <summary>
+    /// 今日头条
+    /// </summary>
+    Toutiao = 112,
+
+    /// <summary>
+    /// 得物
+    /// </summary>
+    Dewu = 113,
+
+    /// <summary>
+    /// 七猫免费小说
+    /// </summary>
+    QimaoNovel = 114,
+
+    /// <summary>
+    /// 夸克
+    /// </summary>
+    Quark = 115,
+
+    /// <summary>
+    /// 悟空浏览器
+    /// </summary>
+    WukongBrowser = 116,
+
+    /// <summary>
+    /// 豆包
+    /// </summary>
+    Doubao = 117,
+
+    /// <summary>
+    /// 懂车帝
+    /// </summary>
+    Dongchedi = 118,
+
+    /// <summary>
+    /// 腾讯视频
+    /// </summary>
+    TencentVideo = 119,
+
+    /// <summary>
+    /// 哔哩哔哩
+    /// </summary>
+    Bilibili = 120,
+
+    /// <summary>
+    /// 头条搜索极速版
+    /// </summary>
+    ToutiaoSearchLite = 121,
+
+    /// <summary>
+    /// 爱奇艺
+    /// </summary>
+    Iqiyi = 122,
+
+    /// <summary>
+    /// 闲鱼
+    /// </summary>
+    Xianyu = 123,
+
+    /// <summary>
+    /// 携程旅行
+    /// </summary>
+    Ctrip = 124,
+
+    /// <summary>
+    /// Soul
+    /// </summary>
+    Soul = 125,
+
+    /// <summary>
+    /// UC浏览器
+    /// </summary>
+    UCBrowser = 126
+}
+

+ 342 - 342
molilian.core/Core/PushDataReport/PushDataReportCore.cs

@@ -1,343 +1,343 @@
-using Microsoft.AspNetCore.Http;
-using Microsoft.AspNetCore.Mvc.Controllers;
-using Microsoft.AspNetCore.Mvc.Filters;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using dodohold.core;
-using Dataoke;
-using Google.Protobuf.WellKnownTypes;
-using System.Xml.Linq;
-using Mysqlx.Crud;
-using ZstdSharp.Unsafe;
-using YunhuiKit;
-using COSXML.Network;
-using System.Threading.Channels;
-using TencentCloud.Apm.V20210622.Models;
-using static QRCoder.PayloadGenerator;
-using TencentCloud.Tcm.V20210413.Models;
-using TencentCloud.Tione.V20211111.Models;
-using TencentCloud.Cdn.V20180606.Models;
-using static molilian.core.ApiReportCore;
-using molilian.core.PushDataReport;
-using Org.BouncyCastle.Asn1.Ocsp;
-using TencentCloud.Cms.V20190321.Models;
-
-
-namespace molilian.core
-{
-
-    public partial class PushDataReportCore
-    {
-        private PushDataReportPush plus;
-        public PushDataReportCore()
-        {
-            plus = new PushDataReportPush();
-        }
-        public bool FillReportData(DateTime date)
-        {
-            return true;
-        }
-
-        public async Task<int> FillData(DateTime reportDatetime)
-        {
-            int count = 0;
-            count += await FillToolData(reportDatetime);
-            count += await FillParseData(reportDatetime);
-            count += await FillDeeplinkData(reportDatetime);
-            count += await FillCpsCouponData(reportDatetime);
-            return count;
-        }
-        public async Task<int> FillParseData(DateTime reportDatetime)
-        {
-            var conn = DBContext.GetOpenConnection();
-            try
-            {
-                var platform_list = new List<DeeplinkParseRuleDTO>()
-                {
-                    new() { channel_id = 0,  push_channel_id = 1, channel_name = "tb" },
-                    new() { channel_id = 0,  push_channel_id = 1, channel_name = "tbdp" },
-                    new() { channel_id = 1, push_channel_id = 13, channel_name = "jd" },
-                    new() { channel_id = 3,  push_channel_id = 9, channel_name = "pdd" }
-                };
-
-                int count = 0;
-                foreach (var p in platform_list)
-                {
-                    int platform = p.channel_id;
-                    string channel_name = p.channel_name;
-
-
-                    int data_type = 1; //转链数据
-                    int sub_type = 100000;
-                    if ("tbdp".Equals(channel_name))
-                    {
-                        data_type = 3;
-                        sub_type = 300002;
-                    }
-                    string filter = "`is_show`=@is_show AND data_type=@data_type AND platform=@platform AND report_date=@report_date";
-
-                    string orderBy = "report_date DESC";
-
-                    var list = new DBContext.Table("api_call_report_dailys")
-                                .Where(filter, new { is_show = true, platform, data_type, report_date = reportDatetime.Date })
-                                .Order(orderBy)
-                                .Select<RecordDTO>().ToList();
-
-                    foreach (var data in list)
-                    {
-                        //描述:0:全分销类型;-1:各分销类型,1:goods 2: live 3:video 4: profile 5: unknown
-
-                        //int[] distributiontype_list = [1, 2, 3, 4, 5, 6, 7];
-
-                        //foreach (var distribution in distributiontype_list)
-                        {
-                            int distribution = 0;
-
-                            filter = " report_date=@report_date AND platform=@platform AND distribution=@distribution AND type=@data_type AND sub_type=@sub_type";
-                            var exist = new DBContext.Table("push_data_report")
-                                        .Get<PushDataReportDTO>(filter, new { report_date = reportDatetime.Date, platform = p.push_channel_id, distribution, data_type, sub_type });
-                            if (exist != null && exist.status == 1) continue;
-
-                            var item = new PushDataReportDTO();
-                            item.report_date = reportDatetime.Date;
-                            item.platform = p.push_channel_id;
-                            item.platform_name = p.channel_name;
-                            item.type = data_type;
-                            item.sub_type = sub_type;
-                            item.distribution = distribution;
-                            item.req_count = data.total_count;
-                            item.dp_transition_succ_count = data.success_count;
-                            item.click_count = data.uclk_pv;
-                            item.click_user_count = data.uclk_uv;
-                            item.order_count = data.eff_ord_num;
-                            item.order_user_count = data.pay_ord_uv;
-                            item.Gmv = data.eff_ord_amt;
-                            item.income = data.eff_tk_disp_tfee;
-                            conn.Replace<PushDataReportDTO>(item);
-                            count++;
-                        }
-                    }
-                }
-                return count;
-            }
-            catch (Exception ex)
-            {
-                //暂时不记录日志
-                return 0;
-            }
-        }
-
-        public async Task<int> FillToolData(DateTime reportDatetime)
-        {
-            var conn = DBContext.GetOpenConnection();
-            try
-            {
-                var list = new List<DeeplinkParseRuleDTO>()
-                {
-                    new() { push_channel_id = 7, channel_name = "bdpan" },
-                    new() { push_channel_id = 8, channel_name = "wemeet" }
-                };
-
-                int count = 0;
-                foreach (var deeplink in list)
-                {
-                    string accountName = $"{deeplink.channel_name}";
-                    int all_total = await TkLogCore.GetTotalAsync($":parse_total:{deeplink.channel_name}:{reportDatetime:yyyyMMdd}");
-                    int success_total = await TkLogCore.GetTotalAsync($":parse_total:{deeplink.channel_name}:success:{reportDatetime:yyyyMMdd}");
-
-                    var item = new PushDataReportDTO();
-                    item.report_date = reportDatetime.Date;
-                    item.platform = deeplink.push_channel_id;
-                    item.platform_name = deeplink.channel_name;
-                    item.req_count = all_total;
-                    item.dp_transition_succ_count = success_total;
-                    conn.Replace<PushDataReportDTO>(item);
-                    count++;
-                }
-                return count;
-            }
-            catch (Exception ex)
-            {
-                //暂时不记录日志
-                return 0;
-            }
-        }
-        public async Task<int> FillCpsCouponData(DateTime report_date)
-        {
-
-            var list = DeeplinkParseRuleCore.List();
-            var conn = DBContext.GetOpenConnection();
-            try
-            {
-
-                //  107 美团
-                //  110 饿了么
-                //tb/jd/eleme/meituan
-
-                Dictionary<int, string> channels = [];
-                channels.Add(107, "meituan");
-                channels.Add(110, "eleme");
-
-                int count = 0;
-                foreach (var kv in channels)
-                {
-
-                    int platform = kv.Key;
-                    string platform_name = kv.Value;
-                    int type = 2;
-                    int sub_type = 100001;
-
-                    string cacheKey = $":cps_total:{kv.Value}:{report_date:yyyyMMdd}";
-                    int req_count = await TkLogCore.GetTotalAsync(cacheKey);
-                    if (req_count == 0) continue;
-
-                    var update = new DBContext.Table("push_data_report");
-                    update.Add("req_count", req_count);
-                    update.Add("update_time", DateTime.Now);
-                    update.Add("status", 3);
-
-                    string filter = "report_date=@report_date AND platform=@platform AND type=@type AND sub_type=@sub_type";
-
-                    var item = new DBContext.Table(conn, "push_data_report")
-                        .Get<PushDataReportDTO>(filter, new { report_date, platform, type, sub_type });
-                    if (item == null)
-                    {
-                        update.Add("create_time", DateTime.Now);
-                        update.Add("report_date", report_date);
-                        update.Add("platform", platform);
-                        update.Add("platform_name", platform_name);
-                        update.Add("type", type);
-                        update.Add("sub_type", sub_type);
-                        update.Create();
-                        count++;
-                    }
-                    else
-                    {
-                        if (item.req_count < req_count)
-                        {
-                            var result = update.Where("id=@id", new { item.id }).Update();
-                            count++;
-                        }
-                    }
-                }
-                return count;
-            }
-            catch (Exception ex)
-            {
-                //暂时不记录日志
-                return 0;
-            }
-        }
-
-
-        public async Task<int> FillDeeplinkData(DateTime reportDatetime)
-        {
-
-            var list = DeeplinkParseRuleCore.List();
-            var conn = DBContext.GetOpenConnection();
-            try
-            {
-                int count = 0;
-                foreach (var deeplink in list)
-                {
-                    if (deeplink.push_channel_id == 0) continue;
-
-                    //先跳过百度网盘的数据处理
-                    if (deeplink.channel_id == 102) continue;
-
-                    string[] flags = ["none", "home", "success", "fail"];
-                    int all_total = 0;
-                    int success_total = 0;
-                    foreach (var dp_flag in flags)
-                    {
-                        string accountName = $"dp_{dp_flag}:{deeplink.channel_name}";
-                        all_total += await TkLogCore.GetTotalAsync($":parse_total:{accountName}:{reportDatetime:yyyyMMdd}");
-                        if ("success".Equals(dp_flag))
-                        {
-                            success_total = await TkLogCore.GetTotalAsync($":parse_total:{accountName}:success:{reportDatetime:yyyyMMdd}");
-                        }
-                    }
-
-                    var item = new PushDataReportDTO();
-                    item.report_date = reportDatetime.Date;
-                    item.platform = deeplink.push_channel_id;
-                    item.platform_name = deeplink.channel_name;
-                    item.req_count = all_total;
-                    item.dp_transition_succ_count = success_total;
-                    conn.Replace<PushDataReportDTO>(item);
-                    count++;
-                }
-                return count;
-            }
-            catch (Exception ex)
-            {
-                //暂时不记录日志
-                return 0;
-            }
-        }
-
-        public async Task<int> PushReportData(DateTime report_date, bool repush = false, CancellationToken cancellationToken = default)
-        {
-            string filter = "report_date=@report_date";
-#if DEBUG
-            filter += " AND sub_type=100000 AND platform IN (1,9,13)";
-#endif
-            if (!repush)
-            {
-                filter += " AND status=@status";
-            }
-
-            var list = new DBContext.Table("push_data_report")
-                .Where(filter, new { report_date = report_date.Date, status = 0 })
-                .Select<PushDataReportDTO>();
-
-            int count = 0;
-            foreach (var item in list)
-            {
-                count++;
-                item.request_id = Guid.NewGuid().ToString();
-                var response = await plus.PushReportData(item, cancellationToken);
-
-                bool status = response.code == 200;
-                if (!status)
-                {
-                    item.request_id = $"{item.request_id}:{response.message}";
-                }
-
-                new DBContext.Table("push_data_report")
-                    .Add("status", status)
-                    .Add("request_id", item.request_id)
-                    .Add("last_push_time", DateTime.Now)
-                    .Where("id=@id", new { item.id })
-                    .Update();
-            }
-            return count;
-        }
-
-        public int NotifyNotPushed(CancellationToken cancellationToken = default)
-        {
-            DateTime report_date = DateTime.Now.Date.AddDays(-1);
-            string filter = "report_date=@report_date and status=@status";
-
-            var list = new DBContext.Table("push_data_report")
-                .Where(filter, new { report_date = report_date.Date, status = 0 })
-                .Select<PushDataReportDTO>();
-
-            int count = list.Count();
-
-            if (count > 0)
-            {
-                NotifyCore.Notify(new NifyMessage
-                {
-                    message = $"【数据回传】未推送警告\n当日还有:{count} 条数据未推送",
-                    priority = NifyMessagePriority.high,
-                    tags = ["red_circle"]
-                });
-                _ = NotifyCore.QYWeixinPushNotifyAsync("【数据回传】未推送警告", $"当日还有:{count}条数据未推送");
-            }
-            return count;
-        }
-    }
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc.Controllers;
+using Microsoft.AspNetCore.Mvc.Filters;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using dodohold.core;
+using Dataoke;
+using Google.Protobuf.WellKnownTypes;
+using System.Xml.Linq;
+using Mysqlx.Crud;
+using ZstdSharp.Unsafe;
+using YunhuiKit;
+using COSXML.Network;
+using System.Threading.Channels;
+using TencentCloud.Apm.V20210622.Models;
+using static QRCoder.PayloadGenerator;
+using TencentCloud.Tcm.V20210413.Models;
+using TencentCloud.Tione.V20211111.Models;
+using TencentCloud.Cdn.V20180606.Models;
+using static molilian.core.ApiReportCore;
+using molilian.core.PushDataReport;
+using Org.BouncyCastle.Asn1.Ocsp;
+using TencentCloud.Cms.V20190321.Models;
+
+
+namespace molilian.core
+{
+
+    public partial class PushDataReportCore
+    {
+        private PushDataReportPush plus;
+        public PushDataReportCore()
+        {
+            plus = new PushDataReportPush();
+        }
+        public bool FillReportData(DateTime date)
+        {
+            return true;
+        }
+
+        public async Task<int> FillData(DateTime reportDatetime)
+        {
+            int count = 0;
+            count += await FillToolData(reportDatetime);
+            count += await FillParseData(reportDatetime);
+            count += await FillDeeplinkData(reportDatetime);
+            count += await FillCpsCouponData(reportDatetime);
+            return count;
+        }
+        public async Task<int> FillParseData(DateTime reportDatetime)
+        {
+            var conn = DBContext.GetOpenConnection();
+            try
+            {
+                var platform_list = new List<DeeplinkParseRuleDTO>()
+                {
+                    new() { channel_id = 0,  push_channel_id = 1, channel_name = "tb" },
+                    new() { channel_id = 0,  push_channel_id = 1, channel_name = "tbdp" },
+                    new() { channel_id = 1, push_channel_id = 13, channel_name = "jd" },
+                    new() { channel_id = 3,  push_channel_id = 9, channel_name = "pdd" }
+                };
+
+                int count = 0;
+                foreach (var p in platform_list)
+                {
+                    int platform = p.channel_id;
+                    string channel_name = p.channel_name;
+
+
+                    int data_type = 1; //转链数据
+                    int sub_type = 100000;
+                    if ("tbdp".Equals(channel_name))
+                    {
+                        data_type = 3;
+                        sub_type = 300002;
+                    }
+                    string filter = "`is_show`=@is_show AND data_type=@data_type AND platform=@platform AND report_date=@report_date";
+
+                    string orderBy = "report_date DESC";
+
+                    var list = new DBContext.Table("api_call_report_dailys")
+                                .Where(filter, new { is_show = true, platform, data_type, report_date = reportDatetime.Date })
+                                .Order(orderBy)
+                                .Select<RecordDTO>().ToList();
+
+                    foreach (var data in list)
+                    {
+                        //描述:0:全分销类型;-1:各分销类型,1:goods 2: live 3:video 4: profile 5: unknown
+
+                        //int[] distributiontype_list = [1, 2, 3, 4, 5, 6, 7];
+
+                        //foreach (var distribution in distributiontype_list)
+                        {
+                            int distribution = 0;
+
+                            filter = " report_date=@report_date AND platform=@platform AND distribution=@distribution AND type=@data_type AND sub_type=@sub_type";
+                            var exist = new DBContext.Table("push_data_report")
+                                        .Get<PushDataReportDTO>(filter, new { report_date = reportDatetime.Date, platform = p.push_channel_id, distribution, data_type, sub_type });
+                            if (exist != null && exist.status == 1) continue;
+
+                            var item = new PushDataReportDTO();
+                            item.report_date = reportDatetime.Date;
+                            item.platform = p.push_channel_id;
+                            item.platform_name = p.channel_name;
+                            item.type = data_type;
+                            item.sub_type = sub_type;
+                            item.distribution = distribution;
+                            item.req_count = data.total_count;
+                            item.dp_transition_succ_count = data.success_count;
+                            item.click_count = data.uclk_pv;
+                            item.click_user_count = data.uclk_uv;
+                            item.order_count = data.eff_ord_num;
+                            item.order_user_count = data.pay_ord_uv;
+                            item.Gmv = data.eff_ord_amt;
+                            item.income = data.eff_tk_disp_tfee;
+                            conn.Replace<PushDataReportDTO>(item);
+                            count++;
+                        }
+                    }
+                }
+                return count;
+            }
+            catch (Exception ex)
+            {
+                //暂时不记录日志
+                return 0;
+            }
+        }
+
+        public async Task<int> FillToolData(DateTime reportDatetime)
+        {
+            var conn = DBContext.GetOpenConnection();
+            try
+            {
+                var list = new List<DeeplinkParseRuleDTO>()
+                {
+                    new() { push_channel_id = 7, channel_name = "bdpan" },
+                    new() { push_channel_id = 8, channel_name = "wemeet" }
+                };
+
+                int count = 0;
+                foreach (var deeplink in list)
+                {
+                    string accountName = $"{deeplink.channel_name}";
+                    int all_total = await TkLogCore.GetTotalAsync($":parse_total:{deeplink.channel_name}:{reportDatetime:yyyyMMdd}");
+                    int success_total = await TkLogCore.GetTotalAsync($":parse_total:{deeplink.channel_name}:success:{reportDatetime:yyyyMMdd}");
+
+                    var item = new PushDataReportDTO();
+                    item.report_date = reportDatetime.Date;
+                    item.platform = deeplink.push_channel_id;
+                    item.platform_name = deeplink.channel_name;
+                    item.req_count = all_total;
+                    item.dp_transition_succ_count = success_total;
+                    conn.Replace<PushDataReportDTO>(item);
+                    count++;
+                }
+                return count;
+            }
+            catch (Exception ex)
+            {
+                //暂时不记录日志
+                return 0;
+            }
+        }
+        public async Task<int> FillCpsCouponData(DateTime report_date)
+        {
+
+            var list = DeeplinkParseRuleCore.List();
+            var conn = DBContext.GetOpenConnection();
+            try
+            {
+
+                //  107 美团
+                //  110 饿了么
+                //tb/jd/eleme/meituan
+
+                Dictionary<int, string> channels = [];
+                channels.Add(107, "meituan");
+                channels.Add(110, "eleme");
+
+                int count = 0;
+                foreach (var kv in channels)
+                {
+
+                    int platform = kv.Key;
+                    string platform_name = kv.Value;
+                    int type = 2;
+                    int sub_type = 100001;
+
+                    string cacheKey = $":cps_total:{kv.Value}:{report_date:yyyyMMdd}";
+                    int req_count = await TkLogCore.GetTotalAsync(cacheKey);
+                    if (req_count == 0) continue;
+
+                    var update = new DBContext.Table("push_data_report");
+                    update.Add("req_count", req_count);
+                    update.Add("update_time", DateTime.Now);
+                    update.Add("status", 3);
+
+                    string filter = "report_date=@report_date AND platform=@platform AND type=@type AND sub_type=@sub_type";
+
+                    var item = new DBContext.Table(conn, "push_data_report")
+                        .Get<PushDataReportDTO>(filter, new { report_date, platform, type, sub_type });
+                    if (item == null)
+                    {
+                        update.Add("create_time", DateTime.Now);
+                        update.Add("report_date", report_date);
+                        update.Add("platform", platform);
+                        update.Add("platform_name", platform_name);
+                        update.Add("type", type);
+                        update.Add("sub_type", sub_type);
+                        update.Create();
+                        count++;
+                    }
+                    else
+                    {
+                        if (item.req_count < req_count)
+                        {
+                            var result = update.Where("id=@id", new { item.id }).Update();
+                            count++;
+                        }
+                    }
+                }
+                return count;
+            }
+            catch (Exception ex)
+            {
+                //暂时不记录日志
+                return 0;
+            }
+        }
+
+
+        public async Task<int> FillDeeplinkData(DateTime reportDatetime)
+        {
+
+            var list = DeeplinkParseRuleCore.List();
+            var conn = DBContext.GetOpenConnection();
+            try
+            {
+                int count = 0;
+                foreach (var deeplink in list)
+                {
+                    if (deeplink.push_channel_id == 0) continue;
+
+                    //先跳过百度网盘的数据处理
+                    if (deeplink.channel_id == 102) continue;
+
+                    string[] flags = ["none", "home", "success", "fail"];
+                    int all_total = 0;
+                    int success_total = 0;
+                    foreach (var dp_flag in flags)
+                    {
+                        string accountName = $"dp_{dp_flag}:{deeplink.channel_name}";
+                        all_total += await TkLogCore.GetTotalAsync($":parse_total:{accountName}:{reportDatetime:yyyyMMdd}");
+                        if ("success".Equals(dp_flag))
+                        {
+                            success_total = await TkLogCore.GetTotalAsync($":parse_total:{accountName}:success:{reportDatetime:yyyyMMdd}");
+                        }
+                    }
+
+                    var item = new PushDataReportDTO();
+                    item.report_date = reportDatetime.Date;
+                    item.platform = deeplink.push_channel_id;
+                    item.platform_name = deeplink.channel_name;
+                    item.req_count = all_total;
+                    item.dp_transition_succ_count = success_total;
+                    conn.Replace<PushDataReportDTO>(item);
+                    count++;
+                }
+                return count;
+            }
+            catch (Exception ex)
+            {
+                //暂时不记录日志
+                return 0;
+            }
+        }
+
+        public async Task<int> PushReportData(DateTime report_date, bool repush = false, CancellationToken cancellationToken = default)
+        {
+            string filter = "report_date=@report_date";
+#if DEBUG
+            filter += " AND sub_type=100000 AND platform IN (1,9,13)";
+#endif
+            if (!repush)
+            {
+                filter += " AND status=@status";
+            }
+
+            var list = new DBContext.Table("push_data_report")
+                .Where(filter, new { report_date = report_date.Date, status = 0 })
+                .Select<PushDataReportDTO>();
+
+            int count = 0;
+            foreach (var item in list)
+            {
+                count++;
+                item.request_id = Guid.NewGuid().ToString();
+                var response = await plus.PushReportData(item, cancellationToken);
+
+                bool status = response.code == 200;
+                if (!status)
+                {
+                    item.request_id = $"{item.request_id}:{response.message}";
+                }
+
+                new DBContext.Table("push_data_report")
+                    .Add("status", status)
+                    .Add("request_id", item.request_id)
+                    .Add("last_push_time", DateTime.Now)
+                    .Where("id=@id", new { item.id })
+                    .Update();
+            }
+            return count;
+        }
+
+        public int NotifyNotPushed(CancellationToken cancellationToken = default)
+        {
+            DateTime report_date = DateTime.Now.Date.AddDays(-1);
+            string filter = "report_date=@report_date and status=@status";
+
+            var list = new DBContext.Table("push_data_report")
+                .Where(filter, new { report_date = report_date.Date, status = 0 })
+                .Select<PushDataReportDTO>();
+
+            int count = list.Count();
+
+            if (count > 0)
+            {
+                NotifyCore.Notify(new NifyMessage
+                {
+                    message = $"【数据回传】未推送警告\n当日还有:{count} 条数据未推送",
+                    priority = NifyMessagePriority.high,
+                    tags = ["red_circle"]
+                });
+                _ = NotifyCore.QYWeixinPushNotifyAsync("【数据回传】未推送警告", $"当日还有:{count}条数据未推送");
+            }
+            return count;
+        }
+    }
 }

+ 187 - 187
molilian.core/Core/PushDataReport/PushDataReportDTO.cs

@@ -1,187 +1,187 @@
-using dodohold.core;
-using System;
-using System.ComponentModel;
-using System.Text.Json.Serialization;
-
-namespace molilian.core.PushDataReport;
-public class Response
-{
-    public int code { get; set; } = 0;
-    public string message { get; set; } = string.Empty;
-    public string requestId { get; set; } = string.Empty;
-}
-
-[Table("push_data_report")]
-public class PushDataReportDTO
-{
-    /// <summary>
-    /// 主键ID
-    /// </summary>
-    [JsonIgnore]
-    [Key]
-    public long id { get; set; }
-
-    /// <summary>
-    /// 报告日期
-    /// </summary>
-
-    [JsonPropertyName("datetime")]
-    [JsonConverter(typeof(YunhuiKit.DateOnlyConverter))] // 自定义日期转换器
-    public DateTime report_date { get; set; } = DateTime.Now.Date;
-
-    /// <summary>
-    /// 是否结算数据
-    /// </summary>
-    [JsonPropertyName("settlement")]
-    public bool is_settlement { get; set; } = false;
-
-    /// <summary>
-    /// 投放场景
-    /// </summary>
-    public int type { get; set; } = 1;
-
-
-    /// <summary>
-    /// 子场景
-    /// </summary>
-    /// </summary>
-    [JsonPropertyName("subType")]
-    public int sub_type { get; set; } = 100000;
-
-    /// <summary>
-    /// 平台
-    /// </summary>
-    public int platform { get; set; } = 0;
-
-    [JsonIgnore]
-    [JsonPropertyName("platformName")]
-    public string platform_name { get; set; } = string.Empty;
-
-    /// <summary>
-    /// 分销类型(0:未知,1:商品,2:直播,3:视频等)
-    /// </summary>
-    public int distribution { get; set; } = 0;
-
-    /// <summary>
-    /// 请求量
-    /// </summary>
-    [JsonConverter(typeof(YunhuiKit.LongToStringConverter))]
-    [JsonPropertyName("reqCount")]
-    public long req_count { get; set; } = 0;
-
-    /// <summary>
-    /// DP转链成功数
-    /// </summary>
-    [JsonConverter(typeof(YunhuiKit.LongToStringConverter))]
-    [JsonPropertyName("dpTransitionSuccCount")]
-    public long dp_transition_succ_count { get; set; } = 0;
-
-    /// <summary>
-    /// 商业转链成功数
-    /// </summary>
-    [JsonConverter(typeof(YunhuiKit.LongToStringConverter))]
-    [JsonPropertyName("transitionSuccCount")]
-    public long transition_succ_count { get; set; } = 0;
-
-    /// <summary>
-    /// 曝光PV
-    /// </summary>
-    [JsonConverter(typeof(YunhuiKit.LongToStringConverter))]
-    [JsonPropertyName("exposeCount")]
-    public long expose_count { get; set; } = 0;
-
-    /// <summary>
-    /// 曝光UV
-    /// </summary>
-    [JsonConverter(typeof(YunhuiKit.LongToStringConverter))]
-    [JsonPropertyName("exposeUserCount")]
-    public long expose_user_count { get; set; } = 0;
-
-    /// <summary>
-    /// 点击PV
-    /// </summary>  
-    [JsonConverter(typeof(YunhuiKit.LongToStringConverter))]
-    [JsonPropertyName("clickCount")]
-    public long click_count { get; set; } = 0;
-
-    /// <summary>
-    /// 点击UV
-    /// </summary>
-    [JsonConverter(typeof(YunhuiKit.LongToStringConverter))]
-    [JsonPropertyName("clickUserCount")]
-    public long click_user_count { get; set; } = 0;
-
-    /// <summary>
-    /// 唤端PV
-    /// </summary>
-    [JsonConverter(typeof(YunhuiKit.LongToStringConverter))]
-    [JsonPropertyName("appOpenCount")]
-    public long app_open_count { get; set; } = 0;
-
-    /// <summary>
-    /// 唤端UV
-    /// </summary>
-    [JsonConverter(typeof(YunhuiKit.LongToStringConverter))]
-    [JsonPropertyName("appOpenUserCount")]
-    public long app_open_user_count { get; set; } = 0;
-
-    /// <summary>
-    /// 订单PV
-    /// </summary>
-    [JsonConverter(typeof(YunhuiKit.LongToStringConverter))]
-    [JsonPropertyName("orderCount")]
-    public long order_count { get; set; } = 0;
-
-    /// <summary>
-    /// 订单UV
-    /// </summary>
-    [JsonConverter(typeof(YunhuiKit.LongToStringConverter))]
-    [JsonPropertyName("orderUserCount")]
-    public long order_user_count { get; set; } = 0;
-
-    /// <summary>
-    /// GMV(元)
-    /// </summary>
-    [JsonConverter(typeof(YunhuiKit.DecimalToStringConverter))]
-    public decimal Gmv { get; set; } = 0;
-
-    /// <summary>
-    /// 预估收入(元)
-    /// </summary>
-    [JsonConverter(typeof(YunhuiKit.DecimalToStringConverter))]
-    public decimal income { get; set; } = 0;
-
-    /// <summary>
-    /// 请求唯一标识
-    /// </summary> 
-    [JsonPropertyName("requestId")]
-    public string request_id { get; set; } = string.Empty;
-
-    /// <summary>
-    /// 创建时间
-    /// </summary>
-    [JsonIgnore]
-    public DateTime create_time { get; set; } = DateTime.Now;
-
-    /// <summary>
-    /// 更新时间
-    /// </summary>
-    [JsonIgnore]
-    public DateTime update_time { get; set; } = DateTime.Now;
-
-    /// <summary>
-    /// 最后推送时间
-    /// </summary>
-    [JsonIgnore]
-    public DateTime last_push_time { get; set; } = DateTime.Now;
-    [JsonIgnore]
-    public int status { get; set; } = 0;
-
-    public string adzoneId { get; set; } = string.Empty;
-
-    public string adId { get; set; } = string.Empty;
-    
-
-
-
-}
+using dodohold.core;
+using System;
+using System.ComponentModel;
+using System.Text.Json.Serialization;
+
+namespace molilian.core.PushDataReport;
+public class Response
+{
+    public int code { get; set; } = 0;
+    public string message { get; set; } = string.Empty;
+    public string requestId { get; set; } = string.Empty;
+}
+
+[Table("push_data_report")]
+public class PushDataReportDTO
+{
+    /// <summary>
+    /// 主键ID
+    /// </summary>
+    [JsonIgnore]
+    [Key]
+    public long id { get; set; }
+
+    /// <summary>
+    /// 报告日期
+    /// </summary>
+
+    [JsonPropertyName("datetime")]
+    [JsonConverter(typeof(YunhuiKit.DateOnlyConverter))] // 自定义日期转换器
+    public DateTime report_date { get; set; } = DateTime.Now.Date;
+
+    /// <summary>
+    /// 是否结算数据
+    /// </summary>
+    [JsonPropertyName("settlement")]
+    public bool is_settlement { get; set; } = false;
+
+    /// <summary>
+    /// 投放场景
+    /// </summary>
+    public int type { get; set; } = 1;
+
+
+    /// <summary>
+    /// 子场景
+    /// </summary>
+    /// </summary>
+    [JsonPropertyName("subType")]
+    public int sub_type { get; set; } = 100000;
+
+    /// <summary>
+    /// 平台
+    /// </summary>
+    public int platform { get; set; } = 0;
+
+    [JsonIgnore]
+    [JsonPropertyName("platformName")]
+    public string platform_name { get; set; } = string.Empty;
+
+    /// <summary>
+    /// 分销类型(0:未知,1:商品,2:直播,3:视频等)
+    /// </summary>
+    public int distribution { get; set; } = 0;
+
+    /// <summary>
+    /// 请求量
+    /// </summary>
+    [JsonConverter(typeof(YunhuiKit.LongToStringConverter))]
+    [JsonPropertyName("reqCount")]
+    public long req_count { get; set; } = 0;
+
+    /// <summary>
+    /// DP转链成功数
+    /// </summary>
+    [JsonConverter(typeof(YunhuiKit.LongToStringConverter))]
+    [JsonPropertyName("dpTransitionSuccCount")]
+    public long dp_transition_succ_count { get; set; } = 0;
+
+    /// <summary>
+    /// 商业转链成功数
+    /// </summary>
+    [JsonConverter(typeof(YunhuiKit.LongToStringConverter))]
+    [JsonPropertyName("transitionSuccCount")]
+    public long transition_succ_count { get; set; } = 0;
+
+    /// <summary>
+    /// 曝光PV
+    /// </summary>
+    [JsonConverter(typeof(YunhuiKit.LongToStringConverter))]
+    [JsonPropertyName("exposeCount")]
+    public long expose_count { get; set; } = 0;
+
+    /// <summary>
+    /// 曝光UV
+    /// </summary>
+    [JsonConverter(typeof(YunhuiKit.LongToStringConverter))]
+    [JsonPropertyName("exposeUserCount")]
+    public long expose_user_count { get; set; } = 0;
+
+    /// <summary>
+    /// 点击PV
+    /// </summary>  
+    [JsonConverter(typeof(YunhuiKit.LongToStringConverter))]
+    [JsonPropertyName("clickCount")]
+    public long click_count { get; set; } = 0;
+
+    /// <summary>
+    /// 点击UV
+    /// </summary>
+    [JsonConverter(typeof(YunhuiKit.LongToStringConverter))]
+    [JsonPropertyName("clickUserCount")]
+    public long click_user_count { get; set; } = 0;
+
+    /// <summary>
+    /// 唤端PV
+    /// </summary>
+    [JsonConverter(typeof(YunhuiKit.LongToStringConverter))]
+    [JsonPropertyName("appOpenCount")]
+    public long app_open_count { get; set; } = 0;
+
+    /// <summary>
+    /// 唤端UV
+    /// </summary>
+    [JsonConverter(typeof(YunhuiKit.LongToStringConverter))]
+    [JsonPropertyName("appOpenUserCount")]
+    public long app_open_user_count { get; set; } = 0;
+
+    /// <summary>
+    /// 订单PV
+    /// </summary>
+    [JsonConverter(typeof(YunhuiKit.LongToStringConverter))]
+    [JsonPropertyName("orderCount")]
+    public long order_count { get; set; } = 0;
+
+    /// <summary>
+    /// 订单UV
+    /// </summary>
+    [JsonConverter(typeof(YunhuiKit.LongToStringConverter))]
+    [JsonPropertyName("orderUserCount")]
+    public long order_user_count { get; set; } = 0;
+
+    /// <summary>
+    /// GMV(元)
+    /// </summary>
+    [JsonConverter(typeof(YunhuiKit.DecimalToStringConverter))]
+    public decimal Gmv { get; set; } = 0;
+
+    /// <summary>
+    /// 预估收入(元)
+    /// </summary>
+    [JsonConverter(typeof(YunhuiKit.DecimalToStringConverter))]
+    public decimal income { get; set; } = 0;
+
+    /// <summary>
+    /// 请求唯一标识
+    /// </summary> 
+    [JsonPropertyName("requestId")]
+    public string request_id { get; set; } = string.Empty;
+
+    /// <summary>
+    /// 创建时间
+    /// </summary>
+    [JsonIgnore]
+    public DateTime create_time { get; set; } = DateTime.Now;
+
+    /// <summary>
+    /// 更新时间
+    /// </summary>
+    [JsonIgnore]
+    public DateTime update_time { get; set; } = DateTime.Now;
+
+    /// <summary>
+    /// 最后推送时间
+    /// </summary>
+    [JsonIgnore]
+    public DateTime last_push_time { get; set; } = DateTime.Now;
+    [JsonIgnore]
+    public int status { get; set; } = 0;
+
+    public string adzoneId { get; set; } = string.Empty;
+
+    public string adId { get; set; } = string.Empty;
+    
+
+
+
+}

+ 102 - 102
molilian.core/Core/PushDataReport/PushDataReportPush.cs

@@ -1,103 +1,103 @@
-using Microsoft.AspNetCore.Http;
-using Microsoft.AspNetCore.Mvc.Controllers;
-using Microsoft.AspNetCore.Mvc.Filters;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using dodohold.core;
-using System.Xml.Linq;
-using YunhuiKit;
-using System.Text.Json;
-using COSXML.Network;
-
-using molilian.core.PushDataReport;
-
-namespace molilian.core
-{
-
-    public class PushDataReportPush
-    {
-        private string _url = "https://adx.ads.heytapmobi.com";
-        private string _app_key = "mll";
-        private string _app_secret = "fHTPd7RK7OnpIv7zGvPgL2Wv8P3pDYfW";
-        public PushDataReportPush()
-        {
-        }
-
-        public PushDataReportPush(string url, string app_key, string app_secret)
-        {
-            _url = url;
-            _app_key = app_key;
-            _app_secret = app_secret;
-        }
-
-        public async Task<ResponseDTO> PushReportData(PushDataReportDTO item, CancellationToken cancellationToken = default)
-        {
-            try
-            {
-                var options = new JsonSerializerOptions
-                {
-                    PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
-                    WriteIndented = true
-                };
-                string json = JsonSerializer.Serialize(item, options);
-
-
-                var root = await Request("/cps/data/report", json, cancellationToken);
-                var response = root.GetRawText().Convert2Object<ResponseDTO>();
-                return response;
-            }
-            catch (Exception ex)
-            {
-                return new ResponseDTO
-                {
-                    code = 500,
-                    message = ex.Message
-                };
-            }
-        }
-
-        public async Task<JsonElement> Request(string action, string post, CancellationToken cancellationToken = default)
-        {
-            string url = $"{_url}{action}";
-
-            long ts = DateTime.Now.Convert2UnixTimestamp(true);
-            string sign = $"{_app_key}{_app_secret}{ts}".ToSHA1();
-            string token = $"{_app_key}:{ts}:{sign}".ToBase64();
-
-            try
-            {
-                WebClientUtility client = new WebClientUtility();
-                client.SetContentType("application/json");
-                client.SetHeaders(new Dictionary<string, string> { { "Authorization", $"Bearer {token}" } });
-                client.Post(post);
-                var response = await client.RequestAsync(url, "POST", cancellationToken);
-
-                if (!response.Successed)
-                {
-                    throw response.ResponseException;
-                }
-                var body = response.Body();
-
-                await new LoggerLibrary("push_report")
-                    .Info(action, post)
-                    .Info(body)
-                    .SaveAsync();
-
-                var root = body.Convert2JsonElement();
-                return root;
-            }
-            catch (Exception ex)
-            {
-                await new LoggerLibrary("push_report", "error")
-                    .Info(action, post)
-                    .Info(ex.Message, ex.StackTrace)
-                    .SaveAsync();
-                throw;
-            }
-        }
-
-
-    }
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc.Controllers;
+using Microsoft.AspNetCore.Mvc.Filters;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using dodohold.core;
+using System.Xml.Linq;
+using YunhuiKit;
+using System.Text.Json;
+using COSXML.Network;
+
+using molilian.core.PushDataReport;
+
+namespace molilian.core
+{
+
+    public class PushDataReportPush
+    {
+        private string _url = "https://adx.ads.heytapmobi.com";
+        private string _app_key = "mll";
+        private string _app_secret = "fHTPd7RK7OnpIv7zGvPgL2Wv8P3pDYfW";
+        public PushDataReportPush()
+        {
+        }
+
+        public PushDataReportPush(string url, string app_key, string app_secret)
+        {
+            _url = url;
+            _app_key = app_key;
+            _app_secret = app_secret;
+        }
+
+        public async Task<ResponseDTO> PushReportData(PushDataReportDTO item, CancellationToken cancellationToken = default)
+        {
+            try
+            {
+                var options = new JsonSerializerOptions
+                {
+                    PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
+                    WriteIndented = true
+                };
+                string json = JsonSerializer.Serialize(item, options);
+
+
+                var root = await Request("/cps/data/report", json, cancellationToken);
+                var response = root.GetRawText().Convert2Object<ResponseDTO>();
+                return response;
+            }
+            catch (Exception ex)
+            {
+                return new ResponseDTO
+                {
+                    code = 500,
+                    message = ex.Message
+                };
+            }
+        }
+
+        public async Task<JsonElement> Request(string action, string post, CancellationToken cancellationToken = default)
+        {
+            string url = $"{_url}{action}";
+
+            long ts = DateTime.Now.Convert2UnixTimestamp(true);
+            string sign = $"{_app_key}{_app_secret}{ts}".ToSHA1();
+            string token = $"{_app_key}:{ts}:{sign}".ToBase64();
+
+            try
+            {
+                WebClientUtility client = new WebClientUtility();
+                client.SetContentType("application/json");
+                client.SetHeaders(new Dictionary<string, string> { { "Authorization", $"Bearer {token}" } });
+                client.Post(post);
+                var response = await client.RequestAsync(url, "POST", cancellationToken);
+
+                if (!response.Successed)
+                {
+                    throw response.ResponseException;
+                }
+                var body = response.Body();
+
+                await new LoggerLibrary("push_report")
+                    .Info(action, post)
+                    .Info(body)
+                    .SaveAsync();
+
+                var root = body.Convert2JsonElement();
+                return root;
+            }
+            catch (Exception ex)
+            {
+                await new LoggerLibrary("push_report", "error")
+                    .Info(action, post)
+                    .Info(ex.Message, ex.StackTrace)
+                    .SaveAsync();
+                throw;
+            }
+        }
+
+
+    }
 }

+ 45 - 45
molilian.core/Core/TracksCore.cs

@@ -1,4 +1,4 @@
-using System;
+using System;
 using System.Collections.Generic;
 using System.Data;
 using System.Net;
@@ -11,20 +11,20 @@ using YunhuiKit;
 namespace molilian.core
 {
     public partial class TracksCore
-    {
+    {
         private const string RedisPrefix = ":tracks_v123";
         private const int DailyExpireSeconds = 40 * 86400;
         private const int HourlyExpireSeconds = 7 * 86400; // keep a week of hourly buckets
         private const string DefaultDimensionValue = "";
         private const int LinkCacheExpireSeconds = 30 * 86400;
         private const string TrackRequestLogKey = ":tracks:request:logs";
-        private static readonly HashSet<string> SupportEventTypes = new(StringComparer.OrdinalIgnoreCase) { "expose", "click" };
-        internal static int ExposeTrackId = 2;
-        internal static int ClickTrackId = 1;
-
-        /// <summary>
-        /// 生成一个监测链接(仅返回 path,不包含域名),同时落地到 track_links
-        /// </summary>
+        private static readonly HashSet<string> SupportEventTypes = new(StringComparer.OrdinalIgnoreCase) { "expose", "click" };
+        internal static int ExposeTrackId = 2;
+        internal static int ClickTrackId = 1;
+
+        /// <summary>
+        /// 生成一个监测链接(仅返回 path,不包含域名),同时落地到 track_links
+        /// </summary>
         public static Task<TrackLinkDTO> CreateLinkAsync(string eventType, string typename, string scene, string uniqueId, string description = "", bool forceNew = false)
         {
             eventType = NormalizeEventType(eventType);
@@ -119,13 +119,13 @@ namespace molilian.core
                 _ = new LoggerLibrary("TracksCore", "CreateLink")
                     .Info(ex.Message, ex.StackTrace)
                     .SaveAsync();
-                return Task.FromResult<TrackLinkDTO>(null);
-            }
-        }
-
-        /// <summary>
-        /// 曝光/点击触发:计入 Redis,失败不影响返回
-        /// </summary>
+                return Task.FromResult<TrackLinkDTO>(null);
+            }
+        }
+
+        /// <summary>
+        /// 曝光/点击触发:计入 Redis,失败不影响返回
+        /// </summary>
         public static Task<bool> TrackAsync(TrackLinkDTO link)
         {
 
@@ -153,17 +153,17 @@ namespace molilian.core
             catch (Exception ex)
             {
                 _ = new LoggerLibrary("TracksCore", "TrackAsync")
-                    .Info(ex.Message, ex.StackTrace)
-                    .SaveAsync();
-                return Task.FromResult(false);
-            }
-        }
-
-        /// <summary>
-        /// 日报:拉取昨日 Redis 计数并落地到 track_daily_report(默认统计昨天,可传 reportDate)
-        /// </summary>
-        public static async Task<int> FlushDailyAsync(DateTime targetDate)
-        {
+                    .Info(ex.Message, ex.StackTrace)
+                    .SaveAsync();
+                return Task.FromResult(false);
+            }
+        }
+
+        /// <summary>
+        /// 日报:拉取昨日 Redis 计数并落地到 track_daily_report(默认统计昨天,可传 reportDate)
+        /// </summary>
+        public static async Task<int> FlushDailyAsync(DateTime targetDate)
+        {
             string dateStr = targetDate.ToString("yyyyMMdd");
             string indexKey = $"{RedisPrefix}:daily:index:{dateStr}:track";
 
@@ -212,26 +212,26 @@ namespace molilian.core
                 rows++;
             }
             return rows;
-        }
+        }
         public static string BuildPath(int trackId, TkDataDTO result)
-        {
-            if (result == null || result.parse_type != "dp") return string.Empty;
-            string unique_id = $"1|{result.itemId}_{result.mktId}";
+        {
+            if (result == null || result.parse_type != "dp") return string.Empty;
+            string unique_id = $"1|{result.itemId}_{result.mktId}";
             return BuildPath(trackId, unique_id);
-        }
+        }
         public static string BuildPath(int trackId, JdDataDTO result)
-        {
-            if (result == null || result.parse_type != "dp") return string.Empty;
-            string unique_id = $"13|{result.shortLinkurl.UrlEncode()}";
+        {
+            if (result == null || result.parse_type != "dp") return string.Empty;
+            string unique_id = $"13|{result.shortLinkurl.UrlEncode()}";
             return BuildPath(trackId, unique_id);
-        }
+        }
         public static string BuildPath(int trackId, PddDataDTO result)
-        {
-            if (result == null || result.parse_type != "dp") return string.Empty;
-            string unique_id = $"9|{result.shortLinkurl.UrlEncode()}";
+        {
+            if (result == null || result.parse_type != "dp") return string.Empty;
+            string unique_id = $"9|{result.shortLinkurl.UrlEncode()}";
             return BuildPath(trackId, unique_id);
         }
-
+
         public static string BuildPath(int trackId, string unique_id = "")
         {
             return $"https://api.molilian.com/tracks/track?track_id={trackId}&unique_id={unique_id}";
@@ -328,7 +328,7 @@ namespace molilian.core
             }
             return count;
         }
-
+
         private static string Normalize(string value)
         {
             return (value ?? string.Empty).Trim();
@@ -344,7 +344,7 @@ namespace molilian.core
         {
             return Normalize(value).ToLowerInvariant();
         }
-
+
         private static int GetTrackLinkId(IDbConnection conn, string eventType, string platform, string scene, string uniqueId)
         {
             try
@@ -353,14 +353,14 @@ namespace molilian.core
                     .Fields("id")
                     .Get<TrackLinkDTO>("event_type=@event_type AND platform=@platform AND scene=@scene AND unique_id=@unique_id",
                         new { event_type = eventType, platform, scene, unique_id = uniqueId });
-                if (record == null) return 0;
+                if (record == null) return 0;
                 return record.id;
             }
             catch
             {
                 return 0;
             }
-        }
-    }
+        }
+    }
 
 }

+ 407 - 407
molilian.core/Core/admin/ChartDataCore.cs

@@ -1,407 +1,407 @@
-using Microsoft.AspNetCore.Http;
-using Microsoft.AspNetCore.Mvc.Controllers;
-using Microsoft.AspNetCore.Mvc.Filters;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using dodohold.core;
-using System.Threading.Channels;
-
-
-namespace molilian.core
-{
-    public partial class ChartDataCore
-    {
-        public class ChartDataItem
-        {
-            public string name { get; set; } = string.Empty;
-            public int value { get; set; } = 0;
-            public decimal increases { get; set; } = 0;
-        }
-        public class ChartDataRes
-        {
-            public List<ChartDataItem> data { get; set; } = [];
-            public List<ChartDataItem> data2 { get; set; } = [];
-            public List<ChartDataItem> data3 { get; set; } = [];
-            public int total { get; set; } = 0;
-        }
-
-        public static async Task<ChartDataRes> GetJdDataAsync(string total_key, string accountName, string report_date)
-        {
-            ChartDataRes result = new();
-            total_key = "parse_total";
-            if (string.IsNullOrEmpty(accountName) || "all".Equals(accountName)) accountName = "jd";
-            string cacheKey = $":{total_key}:{accountName}:{report_date}";
-            result.total = await TkLogCore.GetTotalAsync(cacheKey);
-            if (result.total > 0)
-            {
-                string[] keys = [];
-
-                cacheKey = $":{total_key}:{accountName}:message:{report_date}";
-                string[] message_keys = await TkLogCore.GetTotalKeysAsync(cacheKey);
-
-                var combinedKeys = keys.Union(message_keys).ToList();
-                combinedKeys.RemoveAll(item => item == "fail");
-                combinedKeys.RemoveAll(item => item == "OK");
-
-                foreach (var keyname in combinedKeys)
-                {
-                    cacheKey = $":{total_key}:{accountName}:{keyname}:{report_date}";
-                    int value = await TkLogCore.GetTotalAsync(cacheKey);
-                    if (value == 0) continue;
-                    result.data.Add(new ChartDataItem
-                    {
-                        name = keyname,
-                        value = value,
-                        increases = Math.Round((decimal)value / result.total * 100, 2)
-                    });
-                }
-                result.data = result.data.OrderByDescending(item => item.value).ToList();
-
-                cacheKey = $":{total_key}:{accountName}:reason:{report_date}";
-                string[] reason_keys = await TkLogCore.GetTotalKeysAsync(cacheKey);
-
-                foreach (var keyname in reason_keys)
-                {
-                    if ("没有优惠券".Equals(keyname)) continue;
-                    cacheKey = $":{total_key}:{accountName}:{keyname}:{report_date}";
-                    int value = await TkLogCore.GetTotalAsync(cacheKey);
-                    //if (value < 50) continue;
-                    result.data2.Add(new ChartDataItem
-                    {
-                        name = keyname,
-                        value = value,
-                        increases = Math.Round((decimal)value / result.total * 100, 2)
-                    });
-                }
-                result.data2 = result.data2.OrderByDescending(item => item.value).ToList();
-
-                string[] deeplink_keys = ["none", "home", "success", "fail"];
-                foreach (var keyname in deeplink_keys)
-                {
-                    cacheKey = $":{total_key}:dp_{keyname}:{accountName}:{report_date}";
-                    int value = await TkLogCore.GetTotalAsync(cacheKey);
-                    if (value == 0) continue;
-                    result.data3.Add(new ChartDataItem
-                    {
-                        name = keyname,
-                        value = value,
-                        increases = Math.Round((decimal)value / result.total * 100, 2)
-                    });
-                }
-                result.data3 = result.data3.OrderByDescending(item => item.value).ToList();
-            }
-
-            return result;
-        }
-        public static async Task<ChartDataRes> GetTaobaoDataAsync(string total_key, string accountName, string report_date)
-        {
-            ChartDataRes result = new();
-            if (string.IsNullOrEmpty(accountName) || "all".Equals(accountName)) accountName = "tb";
-            string cacheKey = $":{total_key}:{accountName}:{report_date}";
-            result.total = await TkLogCore.GetTotalAsync(cacheKey);
-            if (result.total > 0)
-            {
-                string[] keys = ["success"];
-
-                cacheKey = $":{total_key}:{accountName}:message:{report_date}";
-                string[] message_keys = await TkLogCore.GetTotalKeysAsync(cacheKey);
-
-                var combinedKeys = keys.Union(message_keys).ToList();
-                combinedKeys.RemoveAll(item => item == "fail");
-                combinedKeys.RemoveAll(item => item == "OK");
-
-                foreach (var keyname in combinedKeys)
-                {
-                    cacheKey = $":{total_key}:{accountName}:{keyname}:{report_date}";
-                    int value = await TkLogCore.GetTotalAsync(cacheKey);
-                    if (value == 0) continue;
-                    result.data.Add(new ChartDataItem
-                    {
-                        name = keyname,
-                        value = value,
-                        increases = Math.Round((decimal)value / result.total * 100, 2)
-                    });
-                }
-                result.data = result.data.OrderByDescending(item => item.value).ToList();
-
-                cacheKey = $":{total_key}:{accountName}:reason:{report_date}";
-                string[] reason_keys = await TkLogCore.GetTotalKeysAsync(cacheKey);
-
-                foreach (var keyname in reason_keys)
-                {
-                    if ("没有优惠券".Equals(keyname)) continue;
-
-                    cacheKey = $":{total_key}:{accountName}:{keyname}:{report_date}";
-                    int value = await TkLogCore.GetTotalAsync(cacheKey);
-                    //if (value < 50) continue;
-                    result.data2.Add(new ChartDataItem
-                    {
-                        name = keyname,
-                        value = value,
-                        increases = Math.Round((decimal)value / result.total * 100, 2)
-                    });
-                }
-                result.data2 = result.data2.OrderByDescending(item => item.value).ToList();
-
-                string[] deeplink_keys = ["none", "home", "success", "fail"];
-                foreach (var keyname in deeplink_keys)
-                {
-                    cacheKey = $":{total_key}:dp_{keyname}:{accountName}:{report_date}";
-                    int value = await TkLogCore.GetTotalAsync(cacheKey);
-                    if (value == 0) continue;
-                    result.data3.Add(new ChartDataItem
-                    {
-                        name = keyname,
-                        value = value,
-                        increases = Math.Round((decimal)value / result.total * 100, 2)
-                    });
-                }
-                result.data3 = result.data3.OrderByDescending(item => item.value).ToList();
-            }
-
-            return result;
-        }
-
-        public static async Task<ChartDataRes> GetTaobaoCouponDataAsync(string total_key, string accountName, string report_date)
-        {
-            ChartDataRes result = new();
-            if (string.IsNullOrEmpty(accountName) || "all".Equals(accountName)) accountName = "tb";
-            string cacheKey = $":{total_key}:{accountName}:{report_date}";
-            result.total = await TkLogCore.GetTotalAsync(cacheKey, false);
-            if (result.total > 0)
-            {
-                string[] keys = ["success"];
-
-                cacheKey = $":{total_key}:{accountName}:message:{report_date}";
-                string[] message_keys = await TkLogCore.GetTotalKeysAsync(cacheKey, false);
-
-                var combinedKeys = keys.Union(message_keys).ToList();
-                combinedKeys.RemoveAll(item => item == "fail");
-                combinedKeys.RemoveAll(item => item == "OK");
-
-                foreach (var keyname in combinedKeys)
-                {
-                    cacheKey = $":{total_key}:{accountName}:{keyname}:{report_date}";
-                    int value = await TkLogCore.GetTotalAsync(cacheKey, false);
-                    if (value == 0) continue;
-                    result.data.Add(new ChartDataItem
-                    {
-                        name = keyname,
-                        value = value,
-                        increases = Math.Round((decimal)value / result.total * 100, 2)
-                    });
-                }
-                result.data = result.data.OrderByDescending(item => item.value).ToList();
-
-                cacheKey = $":{total_key}:{accountName}:reason:{report_date}";
-                string[] reason_keys = await TkLogCore.GetTotalKeysAsync(cacheKey, false);
-
-                foreach (var keyname in reason_keys)
-                {
-                    if ("没有优惠券".Equals(keyname)) continue;
-
-                    cacheKey = $":{total_key}:{accountName}:{keyname}:{report_date}";
-                    int value = await TkLogCore.GetTotalAsync(cacheKey, false);
-                    //if (value < 50) continue;
-                    result.data2.Add(new ChartDataItem
-                    {
-                        name = keyname,
-                        value = value,
-                        increases = Math.Round((decimal)value / result.total * 100, 2)
-                    });
-                }
-                result.data2 = result.data2.OrderByDescending(item => item.value).ToList();
-            }
-            return result;
-        }
-
-        public static async Task<ChartDataRes> GetToolDataAsync(string total_key, string accountName, string report_date)
-        {
-            ChartDataRes result = new();
-
-            accountName = "tool";
-            total_key = "parse_total";
-
-            string cacheKey = $":{total_key}:{accountName}:{report_date}";
-            result.total = await TkLogCore.GetTotalAsync(cacheKey);
-            if (result.total > 0)
-            {
-                string[] keys = ["bdpan", "wemeet"];
-
-                foreach (var keyname in keys)
-                {
-
-                    cacheKey = $":{total_key}:{keyname}:{report_date}";
-                    var total = await TkLogCore.GetTotalAsync(cacheKey);
-
-                    cacheKey = $":{total_key}:{keyname}:success:{report_date}";
-                    int value = await TkLogCore.GetTotalAsync(cacheKey);
-                    if (value == 0) continue;
-                    result.data.Add(new ChartDataItem
-                    {
-                        name = $"{keyname}-成功",
-                        value = value,
-                        increases = Math.Round((decimal)value / total * 100, 2)
-                    });
-
-                    cacheKey = $":{total_key}:{keyname}:fail:{report_date}";
-                    value = await TkLogCore.GetTotalAsync(cacheKey);
-                    if (value == 0) continue;
-                    result.data.Add(new ChartDataItem
-                    {
-                        name = $"{keyname}-失败",
-                        value = value,
-                        increases = Math.Round((decimal)value / total * 100, 2)
-                    });
-                }
-
-                cacheKey = $":{total_key}:{accountName}:reason:{report_date}";
-                string[] reason_keys = await TkLogCore.GetTotalKeysAsync(cacheKey);
-
-                foreach (var keyname in reason_keys)
-                {
-                    cacheKey = $":{total_key}:{accountName}:{keyname}:{report_date}";
-                    int value = await TkLogCore.GetTotalAsync(cacheKey);
-                    if (value == 0) continue;
-                    result.data2.Add(new ChartDataItem
-                    {
-                        name = keyname,
-                        value = value,
-                        increases = Math.Round((decimal)value / result.total * 100, 2)
-                    });
-                }
-                result.data2 = result.data2.OrderByDescending(item => item.value).ToList();
-
-
-                string[] deeplink_keys = ["none", "home", "success", "fail"];
-                foreach (var keyname in deeplink_keys)
-                {
-                    cacheKey = $":{total_key}:dp_{keyname}:{accountName}:{report_date}";
-                    int value = await TkLogCore.GetTotalAsync(cacheKey);
-                    if (value == 0) continue;
-                    result.data3.Add(new ChartDataItem
-                    {
-                        name = keyname,
-                        value = value,
-                        increases = Math.Round((decimal)value / result.total * 100, 2)
-                    });
-                }
-                result.data3 = result.data3.OrderByDescending(item => item.value).ToList();
-            }
-
-
-            //string dp_flag = deeplink switch
-            //{
-            //    "" => "none",
-            //    "tbopen://m.taobao.com/tbopen/index.html" or
-            //    "pinduoduo://com.xunmeng.pinduoduo/" or
-            //    "snssdk1128://feed?refer=web" or
-            //    "bdnetdisk://n/action.EXTERNAL_ACTIVITY" or
-            //    "openapp.jdmobile://virtual?params=" => "home",
-            //    _ => success ? "success" : "fail",
-            //};
-
-            ////关于dp的缓存
-            //saveParseAccountCache($"dp_{dp_flag}:all", success, message, reason);
-            //saveParseAccountCache($"dp_{dp_flag}:{channel}", success, message, reason);
-            //if (accountId != 0)
-            //{
-            //    saveParseAccountCache($"dp_{dp_flag}:{channel}_{accountId}", success, message, reason);
-            //}
-
-
-            return result;
-        }
-
-
-        //public static void GetTaobaoData(string accountName, string report_date)
-        //{
-        //    if (string.IsNullOrEmpty(accountName)) accountName = "tb";
-        //    if (total_key == "tool_total")
-        //    {
-        //        accountName = "tool";
-        //        total_key = "parse_total";
-
-        //        string cacheKey = $":{total_key}:{accountName}:{_report_date}";
-        //        double total = TkLogCore.GetTotal(cacheKey);
-        //        if (total > 0)
-        //        {
-        //            string[] keys = ["bdpan", "wemeet"];
-
-        //            foreach (var keyname in keys)
-        //            {
-
-        //                cacheKey = $":{total_key}:{keyname}:{_report_date}";
-        //                total = TkLogCore.GetTotal(cacheKey);
-
-        //                cacheKey = $":{total_key}:{keyname}:success:{_report_date}";
-        //                int value = TkLogCore.GetTotal(cacheKey);
-        //                if (value == 0) continue;
-        //                result.Add(new { name = $"{keyname}-成功", value, increases = Math.Round(value / total * 100, 2) });
-
-        //                cacheKey = $":{total_key}:{keyname}:fail:{_report_date}";
-        //                value = TkLogCore.GetTotal(cacheKey);
-        //                if (value == 0) continue;
-        //                result.Add(new { name = $"{keyname}-失败", value, increases = Math.Round(value / total * 100, 2) });
-        //            }
-
-        //            cacheKey = $":{total_key}:{accountName}:reason:{_report_date}";
-        //            string[] reason_keys = TkLogCore.GetTotalKeys(cacheKey);
-
-        //            foreach (var keyname in reason_keys)
-        //            {
-        //                cacheKey = $":{total_key}:{accountName}:{keyname}:{_report_date}";
-        //                int value = TkLogCore.GetTotal(cacheKey);
-        //                if (value == 0) continue;
-        //                reason_result.Add(new { name = keyname, value, increases = Math.Round(value / total * 100, 2) });
-        //            }
-        //            reason_result = reason_result.OrderByDescending(item => item.value).ToList();
-        //        }
-        //        return new APIResult(new { total, data = result, data2 = reason_result });
-        //    }
-        //    else
-        //    {
-        //        string cacheKey = $":{total_key}:{accountName}:{_report_date}";
-        //        double total = TkLogCore.GetTotal(cacheKey);
-        //        if (total > 0)
-        //        {
-        //            string[] keys = ["success"];
-
-        //            cacheKey = $":{total_key}:{accountName}:message:{_report_date}";
-        //            string[] message_keys = TkLogCore.GetTotalKeys(cacheKey);
-
-        //            var combinedKeys = keys.Union(message_keys).ToList();
-        //            combinedKeys.RemoveAll(item => item == "fail");
-
-        //            foreach (var keyname in combinedKeys)
-        //            {
-        //                cacheKey = $":{total_key}:{accountName}:{keyname}:{_report_date}";
-        //                int value = TkLogCore.GetTotal(cacheKey);
-        //                if (value == 0) continue;
-        //                result.Add(new { name = keyname, value, increases = Math.Round(value / total * 100, 2) });
-        //            }
-        //            result = result.OrderByDescending(item => item.value).ToList();
-
-        //            cacheKey = $":{total_key}:{accountName}:reason:{_report_date}";
-        //            string[] reason_keys = TkLogCore.GetTotalKeys(cacheKey);
-
-        //            foreach (var keyname in reason_keys)
-        //            {
-        //                cacheKey = $":{total_key}:{accountName}:{keyname}:{_report_date}";
-        //                int value = TkLogCore.GetTotal(cacheKey);
-        //                if (value < 50) continue;
-        //                reason_result.Add(new { name = keyname, value, increases = Math.Round(value / total * 100, 2) });
-        //            }
-        //            reason_result = reason_result.OrderByDescending(item => item.value).ToList();
-        //        }
-        //        return new APIResult(new { total, data = result, data2 = reason_result });
-        //    }
-
-        //}
-
-
-    }
-
-}
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc.Controllers;
+using Microsoft.AspNetCore.Mvc.Filters;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using dodohold.core;
+using System.Threading.Channels;
+
+
+namespace molilian.core
+{
+    public partial class ChartDataCore
+    {
+        public class ChartDataItem
+        {
+            public string name { get; set; } = string.Empty;
+            public int value { get; set; } = 0;
+            public decimal increases { get; set; } = 0;
+        }
+        public class ChartDataRes
+        {
+            public List<ChartDataItem> data { get; set; } = [];
+            public List<ChartDataItem> data2 { get; set; } = [];
+            public List<ChartDataItem> data3 { get; set; } = [];
+            public int total { get; set; } = 0;
+        }
+
+        public static async Task<ChartDataRes> GetJdDataAsync(string total_key, string accountName, string report_date)
+        {
+            ChartDataRes result = new();
+            total_key = "parse_total";
+            if (string.IsNullOrEmpty(accountName) || "all".Equals(accountName)) accountName = "jd";
+            string cacheKey = $":{total_key}:{accountName}:{report_date}";
+            result.total = await TkLogCore.GetTotalAsync(cacheKey);
+            if (result.total > 0)
+            {
+                string[] keys = [];
+
+                cacheKey = $":{total_key}:{accountName}:message:{report_date}";
+                string[] message_keys = await TkLogCore.GetTotalKeysAsync(cacheKey);
+
+                var combinedKeys = keys.Union(message_keys).ToList();
+                combinedKeys.RemoveAll(item => item == "fail");
+                combinedKeys.RemoveAll(item => item == "OK");
+
+                foreach (var keyname in combinedKeys)
+                {
+                    cacheKey = $":{total_key}:{accountName}:{keyname}:{report_date}";
+                    int value = await TkLogCore.GetTotalAsync(cacheKey);
+                    if (value == 0) continue;
+                    result.data.Add(new ChartDataItem
+                    {
+                        name = keyname,
+                        value = value,
+                        increases = Math.Round((decimal)value / result.total * 100, 2)
+                    });
+                }
+                result.data = result.data.OrderByDescending(item => item.value).ToList();
+
+                cacheKey = $":{total_key}:{accountName}:reason:{report_date}";
+                string[] reason_keys = await TkLogCore.GetTotalKeysAsync(cacheKey);
+
+                foreach (var keyname in reason_keys)
+                {
+                    if ("没有优惠券".Equals(keyname)) continue;
+                    cacheKey = $":{total_key}:{accountName}:{keyname}:{report_date}";
+                    int value = await TkLogCore.GetTotalAsync(cacheKey);
+                    //if (value < 50) continue;
+                    result.data2.Add(new ChartDataItem
+                    {
+                        name = keyname,
+                        value = value,
+                        increases = Math.Round((decimal)value / result.total * 100, 2)
+                    });
+                }
+                result.data2 = result.data2.OrderByDescending(item => item.value).ToList();
+
+                string[] deeplink_keys = ["none", "home", "success", "fail"];
+                foreach (var keyname in deeplink_keys)
+                {
+                    cacheKey = $":{total_key}:dp_{keyname}:{accountName}:{report_date}";
+                    int value = await TkLogCore.GetTotalAsync(cacheKey);
+                    if (value == 0) continue;
+                    result.data3.Add(new ChartDataItem
+                    {
+                        name = keyname,
+                        value = value,
+                        increases = Math.Round((decimal)value / result.total * 100, 2)
+                    });
+                }
+                result.data3 = result.data3.OrderByDescending(item => item.value).ToList();
+            }
+
+            return result;
+        }
+        public static async Task<ChartDataRes> GetTaobaoDataAsync(string total_key, string accountName, string report_date)
+        {
+            ChartDataRes result = new();
+            if (string.IsNullOrEmpty(accountName) || "all".Equals(accountName)) accountName = "tb";
+            string cacheKey = $":{total_key}:{accountName}:{report_date}";
+            result.total = await TkLogCore.GetTotalAsync(cacheKey);
+            if (result.total > 0)
+            {
+                string[] keys = ["success"];
+
+                cacheKey = $":{total_key}:{accountName}:message:{report_date}";
+                string[] message_keys = await TkLogCore.GetTotalKeysAsync(cacheKey);
+
+                var combinedKeys = keys.Union(message_keys).ToList();
+                combinedKeys.RemoveAll(item => item == "fail");
+                combinedKeys.RemoveAll(item => item == "OK");
+
+                foreach (var keyname in combinedKeys)
+                {
+                    cacheKey = $":{total_key}:{accountName}:{keyname}:{report_date}";
+                    int value = await TkLogCore.GetTotalAsync(cacheKey);
+                    if (value == 0) continue;
+                    result.data.Add(new ChartDataItem
+                    {
+                        name = keyname,
+                        value = value,
+                        increases = Math.Round((decimal)value / result.total * 100, 2)
+                    });
+                }
+                result.data = result.data.OrderByDescending(item => item.value).ToList();
+
+                cacheKey = $":{total_key}:{accountName}:reason:{report_date}";
+                string[] reason_keys = await TkLogCore.GetTotalKeysAsync(cacheKey);
+
+                foreach (var keyname in reason_keys)
+                {
+                    if ("没有优惠券".Equals(keyname)) continue;
+
+                    cacheKey = $":{total_key}:{accountName}:{keyname}:{report_date}";
+                    int value = await TkLogCore.GetTotalAsync(cacheKey);
+                    //if (value < 50) continue;
+                    result.data2.Add(new ChartDataItem
+                    {
+                        name = keyname,
+                        value = value,
+                        increases = Math.Round((decimal)value / result.total * 100, 2)
+                    });
+                }
+                result.data2 = result.data2.OrderByDescending(item => item.value).ToList();
+
+                string[] deeplink_keys = ["none", "home", "success", "fail"];
+                foreach (var keyname in deeplink_keys)
+                {
+                    cacheKey = $":{total_key}:dp_{keyname}:{accountName}:{report_date}";
+                    int value = await TkLogCore.GetTotalAsync(cacheKey);
+                    if (value == 0) continue;
+                    result.data3.Add(new ChartDataItem
+                    {
+                        name = keyname,
+                        value = value,
+                        increases = Math.Round((decimal)value / result.total * 100, 2)
+                    });
+                }
+                result.data3 = result.data3.OrderByDescending(item => item.value).ToList();
+            }
+
+            return result;
+        }
+
+        public static async Task<ChartDataRes> GetTaobaoCouponDataAsync(string total_key, string accountName, string report_date)
+        {
+            ChartDataRes result = new();
+            if (string.IsNullOrEmpty(accountName) || "all".Equals(accountName)) accountName = "tb";
+            string cacheKey = $":{total_key}:{accountName}:{report_date}";
+            result.total = await TkLogCore.GetTotalAsync(cacheKey, false);
+            if (result.total > 0)
+            {
+                string[] keys = ["success"];
+
+                cacheKey = $":{total_key}:{accountName}:message:{report_date}";
+                string[] message_keys = await TkLogCore.GetTotalKeysAsync(cacheKey, false);
+
+                var combinedKeys = keys.Union(message_keys).ToList();
+                combinedKeys.RemoveAll(item => item == "fail");
+                combinedKeys.RemoveAll(item => item == "OK");
+
+                foreach (var keyname in combinedKeys)
+                {
+                    cacheKey = $":{total_key}:{accountName}:{keyname}:{report_date}";
+                    int value = await TkLogCore.GetTotalAsync(cacheKey, false);
+                    if (value == 0) continue;
+                    result.data.Add(new ChartDataItem
+                    {
+                        name = keyname,
+                        value = value,
+                        increases = Math.Round((decimal)value / result.total * 100, 2)
+                    });
+                }
+                result.data = result.data.OrderByDescending(item => item.value).ToList();
+
+                cacheKey = $":{total_key}:{accountName}:reason:{report_date}";
+                string[] reason_keys = await TkLogCore.GetTotalKeysAsync(cacheKey, false);
+
+                foreach (var keyname in reason_keys)
+                {
+                    if ("没有优惠券".Equals(keyname)) continue;
+
+                    cacheKey = $":{total_key}:{accountName}:{keyname}:{report_date}";
+                    int value = await TkLogCore.GetTotalAsync(cacheKey, false);
+                    //if (value < 50) continue;
+                    result.data2.Add(new ChartDataItem
+                    {
+                        name = keyname,
+                        value = value,
+                        increases = Math.Round((decimal)value / result.total * 100, 2)
+                    });
+                }
+                result.data2 = result.data2.OrderByDescending(item => item.value).ToList();
+            }
+            return result;
+        }
+
+        public static async Task<ChartDataRes> GetToolDataAsync(string total_key, string accountName, string report_date)
+        {
+            ChartDataRes result = new();
+
+            accountName = "tool";
+            total_key = "parse_total";
+
+            string cacheKey = $":{total_key}:{accountName}:{report_date}";
+            result.total = await TkLogCore.GetTotalAsync(cacheKey);
+            if (result.total > 0)
+            {
+                string[] keys = ["bdpan", "wemeet"];
+
+                foreach (var keyname in keys)
+                {
+
+                    cacheKey = $":{total_key}:{keyname}:{report_date}";
+                    var total = await TkLogCore.GetTotalAsync(cacheKey);
+
+                    cacheKey = $":{total_key}:{keyname}:success:{report_date}";
+                    int value = await TkLogCore.GetTotalAsync(cacheKey);
+                    if (value == 0) continue;
+                    result.data.Add(new ChartDataItem
+                    {
+                        name = $"{keyname}-成功",
+                        value = value,
+                        increases = Math.Round((decimal)value / total * 100, 2)
+                    });
+
+                    cacheKey = $":{total_key}:{keyname}:fail:{report_date}";
+                    value = await TkLogCore.GetTotalAsync(cacheKey);
+                    if (value == 0) continue;
+                    result.data.Add(new ChartDataItem
+                    {
+                        name = $"{keyname}-失败",
+                        value = value,
+                        increases = Math.Round((decimal)value / total * 100, 2)
+                    });
+                }
+
+                cacheKey = $":{total_key}:{accountName}:reason:{report_date}";
+                string[] reason_keys = await TkLogCore.GetTotalKeysAsync(cacheKey);
+
+                foreach (var keyname in reason_keys)
+                {
+                    cacheKey = $":{total_key}:{accountName}:{keyname}:{report_date}";
+                    int value = await TkLogCore.GetTotalAsync(cacheKey);
+                    if (value == 0) continue;
+                    result.data2.Add(new ChartDataItem
+                    {
+                        name = keyname,
+                        value = value,
+                        increases = Math.Round((decimal)value / result.total * 100, 2)
+                    });
+                }
+                result.data2 = result.data2.OrderByDescending(item => item.value).ToList();
+
+
+                string[] deeplink_keys = ["none", "home", "success", "fail"];
+                foreach (var keyname in deeplink_keys)
+                {
+                    cacheKey = $":{total_key}:dp_{keyname}:{accountName}:{report_date}";
+                    int value = await TkLogCore.GetTotalAsync(cacheKey);
+                    if (value == 0) continue;
+                    result.data3.Add(new ChartDataItem
+                    {
+                        name = keyname,
+                        value = value,
+                        increases = Math.Round((decimal)value / result.total * 100, 2)
+                    });
+                }
+                result.data3 = result.data3.OrderByDescending(item => item.value).ToList();
+            }
+
+
+            //string dp_flag = deeplink switch
+            //{
+            //    "" => "none",
+            //    "tbopen://m.taobao.com/tbopen/index.html" or
+            //    "pinduoduo://com.xunmeng.pinduoduo/" or
+            //    "snssdk1128://feed?refer=web" or
+            //    "bdnetdisk://n/action.EXTERNAL_ACTIVITY" or
+            //    "openapp.jdmobile://virtual?params=" => "home",
+            //    _ => success ? "success" : "fail",
+            //};
+
+            ////关于dp的缓存
+            //saveParseAccountCache($"dp_{dp_flag}:all", success, message, reason);
+            //saveParseAccountCache($"dp_{dp_flag}:{channel}", success, message, reason);
+            //if (accountId != 0)
+            //{
+            //    saveParseAccountCache($"dp_{dp_flag}:{channel}_{accountId}", success, message, reason);
+            //}
+
+
+            return result;
+        }
+
+
+        //public static void GetTaobaoData(string accountName, string report_date)
+        //{
+        //    if (string.IsNullOrEmpty(accountName)) accountName = "tb";
+        //    if (total_key == "tool_total")
+        //    {
+        //        accountName = "tool";
+        //        total_key = "parse_total";
+
+        //        string cacheKey = $":{total_key}:{accountName}:{_report_date}";
+        //        double total = TkLogCore.GetTotal(cacheKey);
+        //        if (total > 0)
+        //        {
+        //            string[] keys = ["bdpan", "wemeet"];
+
+        //            foreach (var keyname in keys)
+        //            {
+
+        //                cacheKey = $":{total_key}:{keyname}:{_report_date}";
+        //                total = TkLogCore.GetTotal(cacheKey);
+
+        //                cacheKey = $":{total_key}:{keyname}:success:{_report_date}";
+        //                int value = TkLogCore.GetTotal(cacheKey);
+        //                if (value == 0) continue;
+        //                result.Add(new { name = $"{keyname}-成功", value, increases = Math.Round(value / total * 100, 2) });
+
+        //                cacheKey = $":{total_key}:{keyname}:fail:{_report_date}";
+        //                value = TkLogCore.GetTotal(cacheKey);
+        //                if (value == 0) continue;
+        //                result.Add(new { name = $"{keyname}-失败", value, increases = Math.Round(value / total * 100, 2) });
+        //            }
+
+        //            cacheKey = $":{total_key}:{accountName}:reason:{_report_date}";
+        //            string[] reason_keys = TkLogCore.GetTotalKeys(cacheKey);
+
+        //            foreach (var keyname in reason_keys)
+        //            {
+        //                cacheKey = $":{total_key}:{accountName}:{keyname}:{_report_date}";
+        //                int value = TkLogCore.GetTotal(cacheKey);
+        //                if (value == 0) continue;
+        //                reason_result.Add(new { name = keyname, value, increases = Math.Round(value / total * 100, 2) });
+        //            }
+        //            reason_result = reason_result.OrderByDescending(item => item.value).ToList();
+        //        }
+        //        return new APIResult(new { total, data = result, data2 = reason_result });
+        //    }
+        //    else
+        //    {
+        //        string cacheKey = $":{total_key}:{accountName}:{_report_date}";
+        //        double total = TkLogCore.GetTotal(cacheKey);
+        //        if (total > 0)
+        //        {
+        //            string[] keys = ["success"];
+
+        //            cacheKey = $":{total_key}:{accountName}:message:{_report_date}";
+        //            string[] message_keys = TkLogCore.GetTotalKeys(cacheKey);
+
+        //            var combinedKeys = keys.Union(message_keys).ToList();
+        //            combinedKeys.RemoveAll(item => item == "fail");
+
+        //            foreach (var keyname in combinedKeys)
+        //            {
+        //                cacheKey = $":{total_key}:{accountName}:{keyname}:{_report_date}";
+        //                int value = TkLogCore.GetTotal(cacheKey);
+        //                if (value == 0) continue;
+        //                result.Add(new { name = keyname, value, increases = Math.Round(value / total * 100, 2) });
+        //            }
+        //            result = result.OrderByDescending(item => item.value).ToList();
+
+        //            cacheKey = $":{total_key}:{accountName}:reason:{_report_date}";
+        //            string[] reason_keys = TkLogCore.GetTotalKeys(cacheKey);
+
+        //            foreach (var keyname in reason_keys)
+        //            {
+        //                cacheKey = $":{total_key}:{accountName}:{keyname}:{_report_date}";
+        //                int value = TkLogCore.GetTotal(cacheKey);
+        //                if (value < 50) continue;
+        //                reason_result.Add(new { name = keyname, value, increases = Math.Round(value / total * 100, 2) });
+        //            }
+        //            reason_result = reason_result.OrderByDescending(item => item.value).ToList();
+        //        }
+        //        return new APIResult(new { total, data = result, data2 = reason_result });
+        //    }
+
+        //}
+
+
+    }
+
+}

+ 49 - 49
molilian.core/Core/admin/OperationLogCore.cs

@@ -1,49 +1,49 @@
-using Microsoft.AspNetCore.Http;
-using Microsoft.AspNetCore.Mvc.Controllers;
-using Microsoft.AspNetCore.Mvc.Filters;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using dodohold.core;
-using Dataoke;
-
-
-namespace molilian.core
-{
-    [Table("admin_logs")]
-    public class OperationLog
-    {
-        [Key]
-        public int Id { get; set; }
-        public string Operator { get; set; } = string.Empty;
-        public string OperatorIp { get; set; } = string.Empty;
-        public string TargetObject { get; set; } = string.Empty;
-        public string BeforeValue { get; set; } = string.Empty;
-        public string ChangeValue { get; set; } = string.Empty;
-        public DateTime OperationTime { get; set; } = DateTime.Now;
-    }
-    public partial class OperationLogCore
-    {
-
-        public static void LogOperation(string operatorName, string operatorIp, string targetObject,
-            string beforeValue, string changeValue)
-        {
-            var log = new OperationLog
-            {
-                Operator = operatorName,
-                OperatorIp = operatorIp,
-                TargetObject = targetObject,
-                BeforeValue = beforeValue,
-                ChangeValue = changeValue,
-                OperationTime = DateTime.Now
-            };
-
-            using var conn = DBContext.GetOpenConnection();
-            conn.Insert(log);
-        }
-
-    }
-
-
-}
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc.Controllers;
+using Microsoft.AspNetCore.Mvc.Filters;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using dodohold.core;
+using Dataoke;
+
+
+namespace molilian.core
+{
+    [Table("admin_logs")]
+    public class OperationLog
+    {
+        [Key]
+        public int Id { get; set; }
+        public string Operator { get; set; } = string.Empty;
+        public string OperatorIp { get; set; } = string.Empty;
+        public string TargetObject { get; set; } = string.Empty;
+        public string BeforeValue { get; set; } = string.Empty;
+        public string ChangeValue { get; set; } = string.Empty;
+        public DateTime OperationTime { get; set; } = DateTime.Now;
+    }
+    public partial class OperationLogCore
+    {
+
+        public static void LogOperation(string operatorName, string operatorIp, string targetObject,
+            string beforeValue, string changeValue)
+        {
+            var log = new OperationLog
+            {
+                Operator = operatorName,
+                OperatorIp = operatorIp,
+                TargetObject = targetObject,
+                BeforeValue = beforeValue,
+                ChangeValue = changeValue,
+                OperationTime = DateTime.Now
+            };
+
+            using var conn = DBContext.GetOpenConnection();
+            conn.Insert(log);
+        }
+
+    }
+
+
+}

+ 505 - 505
molilian.core/Core/aliyun/AliyunCore.cs

@@ -1,505 +1,505 @@
-using dodohold.core;
-using Dataoke;
-using Tea;
-using AlibabaCloud.SDK.BssOpenApi20171214.Models;
-using AlibabaCloud.SDK.Ecs20140526.Models;
-using AlibabaCloud.SDK.Vpc20160428.Models;
-using TencentCloud.Dbbrain.V20210527.Models;
-using Org.BouncyCastle.Bcpg.OpenPgp;
-using TencentCloud.Tke.V20180525.Models;
-using static AlibabaCloud.SDK.Ecs20140526.Models.DescribeInstancesResponseBody;
-using static AlibabaCloud.SDK.Ecs20140526.Models.DescribeNetworkInterfacesResponseBody;
-using static AlibabaCloud.SDK.Vpc20160428.Models.DescribeEipAddressesResponseBody.DescribeEipAddressesResponseBodyEipAddresses;
-using AlibabaCloud.SDK.ImageSearch20210501.Models;
-using static AlibabaCloud.SDK.Ecs20140526.Models.DescribeNetworkInterfacesResponseBody.DescribeNetworkInterfacesResponseBodyNetworkInterfaceSets;
-using TencentCloud.Cam.V20190116.Models;
-using static AlibabaCloud.SDK.Vpc20160428.Models.DescribeNetworkAclAttributesResponseBody;
-using static AlibabaCloud.SDK.Ecs20140526.Models.DescribeNetworkInterfaceAttributeResponseBody;
-
-namespace molilian.core
-{
-
-    public partial class AliyunCore
-    {
-        private AliyunPlus plus;
-        public AliyunPoolDTO _account;
-
-        public AliyunCore(int id)
-        {
-            _account = new DBContext.Table("aliyun_pool").Get<AliyunPoolDTO>(id);
-            if (_account == null) throw new Exception("无效账号");
-            plus = new AliyunPlus(_account.accessKeyId, _account.accessKeySecret);
-        }
-        public AliyunCore(AliyunPoolDTO item)
-        {
-            _account = item;
-            plus = new AliyunPlus(_account.accessKeyId, _account.accessKeySecret);
-        }
-
-
-        public AliyunCore(string accessKeyId, string accessKeySecret)
-        {
-            plus = new AliyunPlus(accessKeyId, accessKeySecret);
-        }
-
-        public async Task<SearchByPicResponse> SearchByPicAsync(string base64String, string pid)
-        {
-            return await plus.SearchByPicAsync(base64String, pid);
-        }
-
-        public async Task<PromotionQueryDTO> picSimilaritemAsync(string pic, string pid, PromotionQueryDTO result)
-        {
-            var response = await plus.SearchByPicAsync(pic, pid);
-            if (response == null)
-            {
-                result.message = "调用失败";
-                result.reason = "内部错误";
-                return result;
-            }
-
-            result.success = (bool)response.Body.Success;
-            if (!result.success)
-            {
-                result.message = "调用失败";
-                result.reason = $"{response.Body.Code}:{response.Body.Message}";
-                return result;
-            }
-
-
-            List<PromotionQueryItemDTO> arr = [];
-            foreach (var e in response.Body.Data.Auctions)
-            {
-                var item = e.Result;
-
-                PromotionQueryItemDTO newItem = new()
-                {
-                    itemId = item.ItemId,
-                    h5_url = string.IsNullOrEmpty(item.CouponShareUrl) ? item.Url : item.CouponShareUrl,
-                    deeplink_url = string.IsNullOrEmpty(item.DeeplinkCouponShareUrl) ? item.DeeplinkUrl : item.DeeplinkCouponShareUrl,
-                    userType = $"{item.UserType}",
-                    shopTitle = item.ShopTitle,
-                    itemName = item.Title,
-                    pic = item.PicUrl,
-                    price = item.ReservePrice,
-                    promotionPrice = item.PriceAfterCoupon,
-                    couponAmount = $"{item.CouponAmount}",
-                    couponEffectiveStartTime = item.CouponStartTime,
-                    couponEffectiveEndTime = item.CouponEndTime,
-                    sellCountStr = $"{item.Volume}",
-                    provcity = item.Provcity,
-                    exposeTracks = $"https://api.molilian.com/tracks/track?track_id=7&unique_id={item.ItemId}",
-                    clickTracks = $"https://api.molilian.com/tracks/track?track_id=8&unique_id={item.ItemId}"
-                };
-
-                if (newItem.h5_url.StartsWith("//")) newItem.h5_url = "https:" + newItem.h5_url;
-                if (newItem.pic.StartsWith("//")) newItem.pic = "https:" + newItem.pic;
-                arr.Add(newItem);
-            }
-
-            result.message = "OK";
-            result.reason = string.Empty;
-            result.PromotionImg = arr;
-            return result;
-        }
-
-
-
-        public QueryAccountBalanceResponse QueryAccountBalance()
-        {
-            return plus.QueryAccountBalance();
-        }
-
-        /// <summary>
-        /// 获取这个地区下的所有ecs
-        /// </summary>
-        /// <param name="region"></param>
-        /// <returns></returns>
-        /// <exception cref="Exception"></exception>
-        public DescribeInstancesResponseBodyInstances GetInstances(string region)
-        {
-            try
-            {
-                //查询实例信息
-                var response = plus.DescribeInstances(region);
-                var Instances = response.Body.Instances;
-                return Instances;
-            }
-            catch (Exception ex)
-            {
-                throw new Exception(ex.Message);
-            }
-        }
-        public DescribeNetworkInterfacesResponseBodyNetworkInterfaceSets GetNetworkInterfaces(string region)
-        {
-            try
-            {
-                //查询实例信息
-                var response = plus.DescribeNetworkInterfaces(region);
-                var networkInterfaces = response.Body.NetworkInterfaceSets;
-                return networkInterfaces;
-            }
-            catch (Exception ex)
-            {
-                throw new Exception(ex.Message);
-            }
-        }
-        //public bool xxx(string region, string instanceId, string privateIp)
-        //{
-        //    try
-        //    {
-        //        //查询实例信息
-        //        var instance = plus.DescribeInstanceAttribute(region, instanceId);
-        //        var vswitchId = instance.Body.VpcAttributes.VSwitchId;
-        //        var securityGroupId = instance.Body.SecurityGroupIds.SecurityGroupId;
-
-        //        plus.DescribeNetworkInterfaces(region, instanceId)
-        //        //释放弹性网卡
-        //        ReleaseEipAddress("cn-beijing", allocationId);
-        //        //申请并绑定公网IP
-        //        EcsAssociateEipAddress(region, networkInterfaceId, privateIpAddress);
-        //        return true;
-        //    }
-        //    catch (Exception ex)
-        //    {
-        //        throw new Exception(ex.Message);
-        //    }
-        //}
-
-
-
-        public async Task<(bool, string, string)> ChangePublicIpAsync(string privateIp)
-        {
-            LoggerLibrary log = new LoggerLibrary("ChangePublicIp");
-            log.Info($"=====更改公网IP:{privateIp}=====");
-            try
-            {
-                var regions = _account.regions?.Split(',');
-                foreach (var s in regions)
-                {
-                    var region = s.Trim();
-                    if (string.IsNullOrEmpty(region)) continue;
-
-                    //获取这个地区下的所有网卡
-                    var list = GetNetworkInterfaces(region);
-                    if (list == null) continue;
-
-                    foreach (var networkInterface in list.NetworkInterfaceSet)
-                    {
-                        //找出网卡信息
-                        string networkInterfaceId = networkInterface.NetworkInterfaceId;
-                        var privateIps = networkInterface?.PrivateIpSets;
-
-                        var taraget = privateIps?.PrivateIpSet.Where(e => e.PrivateIpAddress.Equals(privateIp)).FirstOrDefault();
-                        if (taraget == null) continue;
-
-
-                        //找到当前公网IP
-                        string currentPublicIp = taraget?.AssociatedPublicIp?.PublicIpAddress;
-                        DescribeEipAddressesResponseBodyEipAddressesEipAddress currentEip;
-                        if (string.IsNullOrEmpty(currentPublicIp))
-                        {
-                            await Task.Delay(500);
-                            var currentEips = DescribeEipsAddresse(region, string.Empty);
-                            currentEip = currentEips?.Where(e => privateIp.Equals(e.PrivateIpAddress))?.FirstOrDefault();
-                            currentPublicIp = currentEip?.IpAddress;
-
-                            if (string.IsNullOrEmpty(currentPublicIp))
-                            {
-                                var emptyPrivateEips = currentEips?.Where(e => string.IsNullOrEmpty(e.PrivateIpAddress));
-                                foreach (var emptyEip in emptyPrivateEips)
-                                {
-                                    await Task.Delay(500);
-                                    var body = DescribeNetworkInterfaceAttribute(region, emptyEip.InstanceId);
-                                    if (body != null)
-                                    {
-                                        if (body.PrivateIpAddress == privateIp)
-                                        {
-                                            currentEip = emptyEip;
-                                            currentPublicIp = emptyEip?.IpAddress;
-                                            break;
-                                        }
-                                    }
-                                }
-                            }
-                        }
-                        else
-                        {
-                            await Task.Delay(500);
-                            currentEip = DescribeEipAddresse(region, currentPublicIp) ?? throw new Exception("没有找到 EIP");
-                        }
-                        log.Info($"找到当前公网IP:{currentPublicIp}");
-                        if (currentEip == null) throw new Exception("没有找到 EIP");
-
-                        //第1步、获取当前的公网EIP
-                        var currentAllocationId = currentEip.AllocationId;
-                        log.Info($"获取当前的公网EIP:{currentAllocationId}");
-
-                        //第2步、申请新的公网ip
-                        await Task.Delay(500);
-                        var eip = plus.AllocateEipAddress(region);
-                        var allocationId = eip.Body.AllocationId;
-                        string eipAddress = eip.Body.EipAddress;
-
-                        if (string.IsNullOrEmpty(eipAddress))
-                        {
-                            log.Info("申请新的公网ip 可能出现异常", eip.Convert2Json());
-                        }
-                        else
-                        {
-                            log.Info($"申请新的公网ip:{eipAddress}");
-                        }
-
-
-                        //第3步、解绑当前的公网ip
-                        await Task.Delay(500);
-                        plus.UnassociateEipAddress(region, currentAllocationId);
-
-                        log.Info("解绑当前的公网ip");
-
-                        //第4步、检查公网ip是否成功解绑
-                        string unPublicIpAddress = currentPublicIp;
-
-                        log.Info("检查公网ip是否成功解绑");
-
-                        while (!string.IsNullOrEmpty(unPublicIpAddress))
-                        {
-                            await Task.Delay(500);
-                            var networkInterfaceState = plus.DescribeNetworkInterfaceAttribute(region, networkInterfaceId);
-                            var currentPrivateIpSet = networkInterfaceState?.Body?.PrivateIpSets?.PrivateIpSet;
-
-                            var currentPrivateIpAddress = currentPrivateIpSet.Where(e => e.PrivateIpAddress.Equals(privateIp)).FirstOrDefault();
-                            unPublicIpAddress = currentPrivateIpAddress?.AssociatedPublicIp?.PublicIpAddress;
-
-                            log.Info($"{unPublicIpAddress} 还没解绑");
-
-                            if (string.IsNullOrEmpty(unPublicIpAddress)) break;
-                            await Task.Delay(500);
-                        }
-                        log.Info("公网ip成功解绑");
-
-
-                        await Task.Delay(1500);
-                        //第5步、绑定新的公网ip
-                        var AssociateEipAddress =
-                            plus.AssociateEipAddress(region, allocationId, networkInterfaceId, EIPInstanceType.NetworkInterface, privateIp);
-
-                        log.Info($"绑定新的公网ip", AssociateEipAddress.Convert2Json());
-                        await Task.Delay(500);
-                        //第5步、释放当前的公网IP
-                        plus.ReleaseEipAddress(region, currentAllocationId);
-                        log.Info($"释放当前的公网IP");
-                        return (true, currentPublicIp, eipAddress);
-                    }
-                }
-                throw new Exception("没有符合的数据");
-
-            }
-            catch (Exception ex)
-            {
-                log.Info($"发生异常:{ex.Message}");
-                throw;
-            }
-            finally
-            {
-                log.Info($"=====结束更改公网IP:{privateIp}=====");
-                log.Save();
-            }
-        }
-
-        public bool EcsCreateNetworkInterface(string region, string instanceId)
-        {
-            try
-            {
-                //查询实例信息
-                var instance = plus.DescribeInstanceAttribute(region, instanceId);
-                var vswitchId = instance.Body.VpcAttributes.VSwitchId;
-                var securityGroupId = instance.Body.SecurityGroupIds.SecurityGroupId;
-
-                //申请弹性网卡
-                var response = plus.CreateNetworkInterface(region, vswitchId, securityGroupId);
-                string networkInterfaceId = response.Body.NetworkInterfaceId;
-                string privateIpAddress = response.Body.PrivateIpAddress;
-                if (string.IsNullOrEmpty(networkInterfaceId)) throw new Exception(response.Body.ToString());
-
-                //绑定网卡
-                plus.AttachNetworkInterface(region, instanceId, networkInterfaceId);
-
-                //申请并绑定公网IP
-                EcsAssociateEipAddress(region, networkInterfaceId, privateIpAddress);
-                return true;
-            }
-            catch (Exception ex)
-            {
-                throw new Exception(ex.Message);
-            }
-        }
-
-        public List<DescribeNetworkInterfacesResponseBodyNetworkInterfaceSetsNetworkInterfaceSet> DescribeNetworkInterfaces(string region, string instanceId)
-        {
-            try
-            {
-                var response = plus.DescribeNetworkInterfaces(region, instanceId);
-                var list = response.Body.NetworkInterfaceSets.NetworkInterfaceSet;
-                return list;
-            }
-            catch (Exception ex)
-            {
-                throw new Exception(ex.Message);
-            }
-        }
-
-
-        public bool EcsAssignPrivateIpAddresses(string region, string instanceId, int count, bool isPrimaryNetworkInterface = false)
-        {
-            try
-            {
-                //查询实例信息
-                var instance = plus.DescribeInstanceAttribute(region, instanceId);
-                var vswitchId = instance.Body.VpcAttributes.VSwitchId;
-                var securityGroupId = instance.Body.SecurityGroupIds.SecurityGroupId;
-
-                var response = plus.DescribeNetworkInterfaces(region, instanceId);
-                foreach (var e in response.Body.NetworkInterfaceSets.NetworkInterfaceSet)
-                {
-                    string type = isPrimaryNetworkInterface ? "Primary" : "Secondary";
-                    if (type.Equals(e.Type))
-                    {
-                        //var response2 = plus.DescribeNetworkInterfaceAttribute(region, e.NetworkInterfaceId);
-                        //var PrivateIpAddressList = response2.Body.PrivateIpSets.PrivateIpSet;
-
-                        //foreach (var privateIp in PrivateIpAddressList)
-                        //{
-                        //    if (privateIp.Primary == true) continue;
-                        //    var privateIpAddress = privateIp.PrivateIpAddress;
-
-                        //    //申请公网ip
-                        //    var eip = plus.AllocateEipAddress(region);
-                        //    var allocationId = eip.Body.AllocationId;
-                        //    string eipAddress = eip.Body.EipAddress;
-
-                        //    //绑定公网ip
-                        //    var AssociateEipAddress =
-                        //        plus.AssociateEipAddress(
-                        //            region, allocationId, e.NetworkInterfaceId, EIPInstanceType.NetworkInterface, privateIpAddress);
-                        //}
-
-                        //添加辅助私网
-                        var response2 = plus.AssignPrivateIpAddresses(region, e.NetworkInterfaceId, count);
-                        var PrivateIpAddressList = response2.Body.AssignedPrivateIpAddressesSet.PrivateIpSet.PrivateIpAddress;
-
-                        foreach (var privateIpAddress in PrivateIpAddressList)
-                        {
-                            //申请公网ip
-                            var eip = plus.AllocateEipAddress(region);
-                            var allocationId = eip.Body.AllocationId;
-                            string eipAddress = eip.Body.EipAddress;
-
-                            //绑定公网ip
-                            var AssociateEipAddress =
-                                plus.AssociateEipAddress(region, allocationId, e.NetworkInterfaceId, EIPInstanceType.NetworkInterface, privateIpAddress);
-                        }
-
-
-                        return true;
-                    }
-                }
-
-                return false;
-            }
-            catch (Exception ex)
-            {
-                throw new Exception(ex.Message);
-            }
-        }
-
-        /// <summary>
-        /// 申请并绑定公网IP
-        /// </summary>
-        /// <param name="region"></param>
-        /// <param name="instanceId"></param>
-        /// <param name="privateIpAddress"></param>
-        /// <returns></returns>
-        /// <exception cref="Exception"></exception>
-        public bool EcsAssociateEipAddress(string region, string networkInterfaceId, string privateIpAddress)
-        {
-            try
-            {
-                //申请公网ip
-                var eip = plus.AllocateEipAddress(region);
-                var allocationId = eip.Body.AllocationId;
-                string eipAddress = eip.Body.EipAddress;
-
-                //绑定公网ip
-                var AssociateEipAddress =
-                    plus.AssociateEipAddress(region, allocationId, networkInterfaceId, EIPInstanceType.NetworkInterface);
-                return true;
-            }
-            catch (Exception ex)
-            {
-                throw new Exception(ex.Message);
-            }
-        }
-
-
-        public DescribeEipAddressesResponseBodyEipAddressesEipAddress DescribeEipAddresse(string region, string eipAddress)
-        {
-            try
-            {
-                var response = plus.DescribeEipAddresses(region, eipAddress);
-                var eip = response?.Body?.EipAddresses?.EipAddress?.FirstOrDefault();
-                return eip;
-            }
-            catch (Exception ex)
-            {
-                throw new Exception(ex.Message);
-            }
-        }
-
-
-        public List<DescribeEipAddressesResponseBodyEipAddressesEipAddress> DescribeEipsAddresse(string region, string eipAddress)
-        {
-            try
-            {
-                var response = plus.DescribeEipAddresses(region, eipAddress);
-                var eips = response?.Body?.EipAddresses?.EipAddress;
-                return eips;
-            }
-            catch (Exception ex)
-            {
-                throw new Exception(ex.Message);
-            }
-        }
-
-        public DescribeNetworkInterfaceAttributeResponseBody DescribeNetworkInterfaceAttribute(string region, string networkInterfaceId)
-        {
-            try
-            {
-                var response = plus.DescribeNetworkInterfaceAttribute(region, networkInterfaceId);
-                var body = response?.Body;
-                return body;
-            }
-            catch (Exception ex)
-            {
-                throw new Exception(ex.Message);
-            }
-        }
-
-
-        public bool ReleaseEipAddress(string region, string allocationId)
-        {
-            try
-            {
-                plus.ReleaseEipAddress(region, allocationId);
-                return true;
-            }
-            catch (Exception ex)
-            {
-                throw new Exception(ex.Message);
-            }
-        }
-
-    }
-
-
-}
-
+using dodohold.core;
+using Dataoke;
+using Tea;
+using AlibabaCloud.SDK.BssOpenApi20171214.Models;
+using AlibabaCloud.SDK.Ecs20140526.Models;
+using AlibabaCloud.SDK.Vpc20160428.Models;
+using TencentCloud.Dbbrain.V20210527.Models;
+using Org.BouncyCastle.Bcpg.OpenPgp;
+using TencentCloud.Tke.V20180525.Models;
+using static AlibabaCloud.SDK.Ecs20140526.Models.DescribeInstancesResponseBody;
+using static AlibabaCloud.SDK.Ecs20140526.Models.DescribeNetworkInterfacesResponseBody;
+using static AlibabaCloud.SDK.Vpc20160428.Models.DescribeEipAddressesResponseBody.DescribeEipAddressesResponseBodyEipAddresses;
+using AlibabaCloud.SDK.ImageSearch20210501.Models;
+using static AlibabaCloud.SDK.Ecs20140526.Models.DescribeNetworkInterfacesResponseBody.DescribeNetworkInterfacesResponseBodyNetworkInterfaceSets;
+using TencentCloud.Cam.V20190116.Models;
+using static AlibabaCloud.SDK.Vpc20160428.Models.DescribeNetworkAclAttributesResponseBody;
+using static AlibabaCloud.SDK.Ecs20140526.Models.DescribeNetworkInterfaceAttributeResponseBody;
+
+namespace molilian.core
+{
+
+    public partial class AliyunCore
+    {
+        private AliyunPlus plus;
+        public AliyunPoolDTO _account;
+
+        public AliyunCore(int id)
+        {
+            _account = new DBContext.Table("aliyun_pool").Get<AliyunPoolDTO>(id);
+            if (_account == null) throw new Exception("无效账号");
+            plus = new AliyunPlus(_account.accessKeyId, _account.accessKeySecret);
+        }
+        public AliyunCore(AliyunPoolDTO item)
+        {
+            _account = item;
+            plus = new AliyunPlus(_account.accessKeyId, _account.accessKeySecret);
+        }
+
+
+        public AliyunCore(string accessKeyId, string accessKeySecret)
+        {
+            plus = new AliyunPlus(accessKeyId, accessKeySecret);
+        }
+
+        public async Task<SearchByPicResponse> SearchByPicAsync(string base64String, string pid)
+        {
+            return await plus.SearchByPicAsync(base64String, pid);
+        }
+
+        public async Task<PromotionQueryDTO> picSimilaritemAsync(string pic, string pid, PromotionQueryDTO result)
+        {
+            var response = await plus.SearchByPicAsync(pic, pid);
+            if (response == null)
+            {
+                result.message = "调用失败";
+                result.reason = "内部错误";
+                return result;
+            }
+
+            result.success = (bool)response.Body.Success;
+            if (!result.success)
+            {
+                result.message = "调用失败";
+                result.reason = $"{response.Body.Code}:{response.Body.Message}";
+                return result;
+            }
+
+
+            List<PromotionQueryItemDTO> arr = [];
+            foreach (var e in response.Body.Data.Auctions)
+            {
+                var item = e.Result;
+
+                PromotionQueryItemDTO newItem = new()
+                {
+                    itemId = item.ItemId,
+                    h5_url = string.IsNullOrEmpty(item.CouponShareUrl) ? item.Url : item.CouponShareUrl,
+                    deeplink_url = string.IsNullOrEmpty(item.DeeplinkCouponShareUrl) ? item.DeeplinkUrl : item.DeeplinkCouponShareUrl,
+                    userType = $"{item.UserType}",
+                    shopTitle = item.ShopTitle,
+                    itemName = item.Title,
+                    pic = item.PicUrl,
+                    price = item.ReservePrice,
+                    promotionPrice = item.PriceAfterCoupon,
+                    couponAmount = $"{item.CouponAmount}",
+                    couponEffectiveStartTime = item.CouponStartTime,
+                    couponEffectiveEndTime = item.CouponEndTime,
+                    sellCountStr = $"{item.Volume}",
+                    provcity = item.Provcity,
+                    exposeTracks = $"https://api.molilian.com/tracks/track?track_id=7&unique_id={item.ItemId}",
+                    clickTracks = $"https://api.molilian.com/tracks/track?track_id=8&unique_id={item.ItemId}"
+                };
+
+                if (newItem.h5_url.StartsWith("//")) newItem.h5_url = "https:" + newItem.h5_url;
+                if (newItem.pic.StartsWith("//")) newItem.pic = "https:" + newItem.pic;
+                arr.Add(newItem);
+            }
+
+            result.message = "OK";
+            result.reason = string.Empty;
+            result.PromotionImg = arr;
+            return result;
+        }
+
+
+
+        public QueryAccountBalanceResponse QueryAccountBalance()
+        {
+            return plus.QueryAccountBalance();
+        }
+
+        /// <summary>
+        /// 获取这个地区下的所有ecs
+        /// </summary>
+        /// <param name="region"></param>
+        /// <returns></returns>
+        /// <exception cref="Exception"></exception>
+        public DescribeInstancesResponseBodyInstances GetInstances(string region)
+        {
+            try
+            {
+                //查询实例信息
+                var response = plus.DescribeInstances(region);
+                var Instances = response.Body.Instances;
+                return Instances;
+            }
+            catch (Exception ex)
+            {
+                throw new Exception(ex.Message);
+            }
+        }
+        public DescribeNetworkInterfacesResponseBodyNetworkInterfaceSets GetNetworkInterfaces(string region)
+        {
+            try
+            {
+                //查询实例信息
+                var response = plus.DescribeNetworkInterfaces(region);
+                var networkInterfaces = response.Body.NetworkInterfaceSets;
+                return networkInterfaces;
+            }
+            catch (Exception ex)
+            {
+                throw new Exception(ex.Message);
+            }
+        }
+        //public bool xxx(string region, string instanceId, string privateIp)
+        //{
+        //    try
+        //    {
+        //        //查询实例信息
+        //        var instance = plus.DescribeInstanceAttribute(region, instanceId);
+        //        var vswitchId = instance.Body.VpcAttributes.VSwitchId;
+        //        var securityGroupId = instance.Body.SecurityGroupIds.SecurityGroupId;
+
+        //        plus.DescribeNetworkInterfaces(region, instanceId)
+        //        //释放弹性网卡
+        //        ReleaseEipAddress("cn-beijing", allocationId);
+        //        //申请并绑定公网IP
+        //        EcsAssociateEipAddress(region, networkInterfaceId, privateIpAddress);
+        //        return true;
+        //    }
+        //    catch (Exception ex)
+        //    {
+        //        throw new Exception(ex.Message);
+        //    }
+        //}
+
+
+
+        public async Task<(bool, string, string)> ChangePublicIpAsync(string privateIp)
+        {
+            LoggerLibrary log = new LoggerLibrary("ChangePublicIp");
+            log.Info($"=====更改公网IP:{privateIp}=====");
+            try
+            {
+                var regions = _account.regions?.Split(',');
+                foreach (var s in regions)
+                {
+                    var region = s.Trim();
+                    if (string.IsNullOrEmpty(region)) continue;
+
+                    //获取这个地区下的所有网卡
+                    var list = GetNetworkInterfaces(region);
+                    if (list == null) continue;
+
+                    foreach (var networkInterface in list.NetworkInterfaceSet)
+                    {
+                        //找出网卡信息
+                        string networkInterfaceId = networkInterface.NetworkInterfaceId;
+                        var privateIps = networkInterface?.PrivateIpSets;
+
+                        var taraget = privateIps?.PrivateIpSet.Where(e => e.PrivateIpAddress.Equals(privateIp)).FirstOrDefault();
+                        if (taraget == null) continue;
+
+
+                        //找到当前公网IP
+                        string currentPublicIp = taraget?.AssociatedPublicIp?.PublicIpAddress;
+                        DescribeEipAddressesResponseBodyEipAddressesEipAddress currentEip;
+                        if (string.IsNullOrEmpty(currentPublicIp))
+                        {
+                            await Task.Delay(500);
+                            var currentEips = DescribeEipsAddresse(region, string.Empty);
+                            currentEip = currentEips?.Where(e => privateIp.Equals(e.PrivateIpAddress))?.FirstOrDefault();
+                            currentPublicIp = currentEip?.IpAddress;
+
+                            if (string.IsNullOrEmpty(currentPublicIp))
+                            {
+                                var emptyPrivateEips = currentEips?.Where(e => string.IsNullOrEmpty(e.PrivateIpAddress));
+                                foreach (var emptyEip in emptyPrivateEips)
+                                {
+                                    await Task.Delay(500);
+                                    var body = DescribeNetworkInterfaceAttribute(region, emptyEip.InstanceId);
+                                    if (body != null)
+                                    {
+                                        if (body.PrivateIpAddress == privateIp)
+                                        {
+                                            currentEip = emptyEip;
+                                            currentPublicIp = emptyEip?.IpAddress;
+                                            break;
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                        else
+                        {
+                            await Task.Delay(500);
+                            currentEip = DescribeEipAddresse(region, currentPublicIp) ?? throw new Exception("没有找到 EIP");
+                        }
+                        log.Info($"找到当前公网IP:{currentPublicIp}");
+                        if (currentEip == null) throw new Exception("没有找到 EIP");
+
+                        //第1步、获取当前的公网EIP
+                        var currentAllocationId = currentEip.AllocationId;
+                        log.Info($"获取当前的公网EIP:{currentAllocationId}");
+
+                        //第2步、申请新的公网ip
+                        await Task.Delay(500);
+                        var eip = plus.AllocateEipAddress(region);
+                        var allocationId = eip.Body.AllocationId;
+                        string eipAddress = eip.Body.EipAddress;
+
+                        if (string.IsNullOrEmpty(eipAddress))
+                        {
+                            log.Info("申请新的公网ip 可能出现异常", eip.Convert2Json());
+                        }
+                        else
+                        {
+                            log.Info($"申请新的公网ip:{eipAddress}");
+                        }
+
+
+                        //第3步、解绑当前的公网ip
+                        await Task.Delay(500);
+                        plus.UnassociateEipAddress(region, currentAllocationId);
+
+                        log.Info("解绑当前的公网ip");
+
+                        //第4步、检查公网ip是否成功解绑
+                        string unPublicIpAddress = currentPublicIp;
+
+                        log.Info("检查公网ip是否成功解绑");
+
+                        while (!string.IsNullOrEmpty(unPublicIpAddress))
+                        {
+                            await Task.Delay(500);
+                            var networkInterfaceState = plus.DescribeNetworkInterfaceAttribute(region, networkInterfaceId);
+                            var currentPrivateIpSet = networkInterfaceState?.Body?.PrivateIpSets?.PrivateIpSet;
+
+                            var currentPrivateIpAddress = currentPrivateIpSet.Where(e => e.PrivateIpAddress.Equals(privateIp)).FirstOrDefault();
+                            unPublicIpAddress = currentPrivateIpAddress?.AssociatedPublicIp?.PublicIpAddress;
+
+                            log.Info($"{unPublicIpAddress} 还没解绑");
+
+                            if (string.IsNullOrEmpty(unPublicIpAddress)) break;
+                            await Task.Delay(500);
+                        }
+                        log.Info("公网ip成功解绑");
+
+
+                        await Task.Delay(1500);
+                        //第5步、绑定新的公网ip
+                        var AssociateEipAddress =
+                            plus.AssociateEipAddress(region, allocationId, networkInterfaceId, EIPInstanceType.NetworkInterface, privateIp);
+
+                        log.Info($"绑定新的公网ip", AssociateEipAddress.Convert2Json());
+                        await Task.Delay(500);
+                        //第5步、释放当前的公网IP
+                        plus.ReleaseEipAddress(region, currentAllocationId);
+                        log.Info($"释放当前的公网IP");
+                        return (true, currentPublicIp, eipAddress);
+                    }
+                }
+                throw new Exception("没有符合的数据");
+
+            }
+            catch (Exception ex)
+            {
+                log.Info($"发生异常:{ex.Message}");
+                throw;
+            }
+            finally
+            {
+                log.Info($"=====结束更改公网IP:{privateIp}=====");
+                log.Save();
+            }
+        }
+
+        public bool EcsCreateNetworkInterface(string region, string instanceId)
+        {
+            try
+            {
+                //查询实例信息
+                var instance = plus.DescribeInstanceAttribute(region, instanceId);
+                var vswitchId = instance.Body.VpcAttributes.VSwitchId;
+                var securityGroupId = instance.Body.SecurityGroupIds.SecurityGroupId;
+
+                //申请弹性网卡
+                var response = plus.CreateNetworkInterface(region, vswitchId, securityGroupId);
+                string networkInterfaceId = response.Body.NetworkInterfaceId;
+                string privateIpAddress = response.Body.PrivateIpAddress;
+                if (string.IsNullOrEmpty(networkInterfaceId)) throw new Exception(response.Body.ToString());
+
+                //绑定网卡
+                plus.AttachNetworkInterface(region, instanceId, networkInterfaceId);
+
+                //申请并绑定公网IP
+                EcsAssociateEipAddress(region, networkInterfaceId, privateIpAddress);
+                return true;
+            }
+            catch (Exception ex)
+            {
+                throw new Exception(ex.Message);
+            }
+        }
+
+        public List<DescribeNetworkInterfacesResponseBodyNetworkInterfaceSetsNetworkInterfaceSet> DescribeNetworkInterfaces(string region, string instanceId)
+        {
+            try
+            {
+                var response = plus.DescribeNetworkInterfaces(region, instanceId);
+                var list = response.Body.NetworkInterfaceSets.NetworkInterfaceSet;
+                return list;
+            }
+            catch (Exception ex)
+            {
+                throw new Exception(ex.Message);
+            }
+        }
+
+
+        public bool EcsAssignPrivateIpAddresses(string region, string instanceId, int count, bool isPrimaryNetworkInterface = false)
+        {
+            try
+            {
+                //查询实例信息
+                var instance = plus.DescribeInstanceAttribute(region, instanceId);
+                var vswitchId = instance.Body.VpcAttributes.VSwitchId;
+                var securityGroupId = instance.Body.SecurityGroupIds.SecurityGroupId;
+
+                var response = plus.DescribeNetworkInterfaces(region, instanceId);
+                foreach (var e in response.Body.NetworkInterfaceSets.NetworkInterfaceSet)
+                {
+                    string type = isPrimaryNetworkInterface ? "Primary" : "Secondary";
+                    if (type.Equals(e.Type))
+                    {
+                        //var response2 = plus.DescribeNetworkInterfaceAttribute(region, e.NetworkInterfaceId);
+                        //var PrivateIpAddressList = response2.Body.PrivateIpSets.PrivateIpSet;
+
+                        //foreach (var privateIp in PrivateIpAddressList)
+                        //{
+                        //    if (privateIp.Primary == true) continue;
+                        //    var privateIpAddress = privateIp.PrivateIpAddress;
+
+                        //    //申请公网ip
+                        //    var eip = plus.AllocateEipAddress(region);
+                        //    var allocationId = eip.Body.AllocationId;
+                        //    string eipAddress = eip.Body.EipAddress;
+
+                        //    //绑定公网ip
+                        //    var AssociateEipAddress =
+                        //        plus.AssociateEipAddress(
+                        //            region, allocationId, e.NetworkInterfaceId, EIPInstanceType.NetworkInterface, privateIpAddress);
+                        //}
+
+                        //添加辅助私网
+                        var response2 = plus.AssignPrivateIpAddresses(region, e.NetworkInterfaceId, count);
+                        var PrivateIpAddressList = response2.Body.AssignedPrivateIpAddressesSet.PrivateIpSet.PrivateIpAddress;
+
+                        foreach (var privateIpAddress in PrivateIpAddressList)
+                        {
+                            //申请公网ip
+                            var eip = plus.AllocateEipAddress(region);
+                            var allocationId = eip.Body.AllocationId;
+                            string eipAddress = eip.Body.EipAddress;
+
+                            //绑定公网ip
+                            var AssociateEipAddress =
+                                plus.AssociateEipAddress(region, allocationId, e.NetworkInterfaceId, EIPInstanceType.NetworkInterface, privateIpAddress);
+                        }
+
+
+                        return true;
+                    }
+                }
+
+                return false;
+            }
+            catch (Exception ex)
+            {
+                throw new Exception(ex.Message);
+            }
+        }
+
+        /// <summary>
+        /// 申请并绑定公网IP
+        /// </summary>
+        /// <param name="region"></param>
+        /// <param name="instanceId"></param>
+        /// <param name="privateIpAddress"></param>
+        /// <returns></returns>
+        /// <exception cref="Exception"></exception>
+        public bool EcsAssociateEipAddress(string region, string networkInterfaceId, string privateIpAddress)
+        {
+            try
+            {
+                //申请公网ip
+                var eip = plus.AllocateEipAddress(region);
+                var allocationId = eip.Body.AllocationId;
+                string eipAddress = eip.Body.EipAddress;
+
+                //绑定公网ip
+                var AssociateEipAddress =
+                    plus.AssociateEipAddress(region, allocationId, networkInterfaceId, EIPInstanceType.NetworkInterface);
+                return true;
+            }
+            catch (Exception ex)
+            {
+                throw new Exception(ex.Message);
+            }
+        }
+
+
+        public DescribeEipAddressesResponseBodyEipAddressesEipAddress DescribeEipAddresse(string region, string eipAddress)
+        {
+            try
+            {
+                var response = plus.DescribeEipAddresses(region, eipAddress);
+                var eip = response?.Body?.EipAddresses?.EipAddress?.FirstOrDefault();
+                return eip;
+            }
+            catch (Exception ex)
+            {
+                throw new Exception(ex.Message);
+            }
+        }
+
+
+        public List<DescribeEipAddressesResponseBodyEipAddressesEipAddress> DescribeEipsAddresse(string region, string eipAddress)
+        {
+            try
+            {
+                var response = plus.DescribeEipAddresses(region, eipAddress);
+                var eips = response?.Body?.EipAddresses?.EipAddress;
+                return eips;
+            }
+            catch (Exception ex)
+            {
+                throw new Exception(ex.Message);
+            }
+        }
+
+        public DescribeNetworkInterfaceAttributeResponseBody DescribeNetworkInterfaceAttribute(string region, string networkInterfaceId)
+        {
+            try
+            {
+                var response = plus.DescribeNetworkInterfaceAttribute(region, networkInterfaceId);
+                var body = response?.Body;
+                return body;
+            }
+            catch (Exception ex)
+            {
+                throw new Exception(ex.Message);
+            }
+        }
+
+
+        public bool ReleaseEipAddress(string region, string allocationId)
+        {
+            try
+            {
+                plus.ReleaseEipAddress(region, allocationId);
+                return true;
+            }
+            catch (Exception ex)
+            {
+                throw new Exception(ex.Message);
+            }
+        }
+
+    }
+
+
+}
+

+ 282 - 282
molilian.core/Core/aliyun/AliyunPoolCore.cs

@@ -1,282 +1,282 @@
-using dodohold.core;
-using Dataoke;
-using Tea;
-using AlibabaCloud.SDK.BssOpenApi20171214.Models;
-using AlibabaCloud.SDK.Ecs20140526.Models;
-using AlibabaCloud.SDK.Vpc20160428.Models;
-using TencentCloud.Dbbrain.V20210527.Models;
-using Org.BouncyCastle.Bcpg.OpenPgp;
-using TencentCloud.Tke.V20180525.Models;
-using static molilian.core.TkPoolCore;
-using ZstdSharp.Unsafe;
-using System.Collections.Generic;
-
-namespace molilian.core
-{
-
-    public partial class AliyunPoolCore
-    {
-
-        private static readonly object _lockObj = new();
-        private static IEnumerable<AliyunPoolDTO> _cached;
-
-        public static AliyunPoolDTO? GetOne(int id)
-        {
-            var list = List();
-            if (!list.Any()) return null;
-            return list.Where(e => e.id == id).FirstOrDefault();
-        }
-
-        public static IEnumerable<AliyunPoolDTO> List(bool force = false)
-        {
-
-            if (!force && _cached != null) return _cached;
-
-            string cache_key = $"cache:aliyun_pool";
-            var list = RedisHelper.Get<IEnumerable<AliyunPoolDTO>>(cache_key);
-            if (force || list == null)
-            {
-                lock (_lockObj)
-                {
-                    list = new DBContext.Table("aliyun_pool")
-                        .Where("status=@status", new { status = 1 })
-                        .Select<AliyunPoolDTO>();
-
-                    list = new DBContext.Table("aliyun_pool")
-                        .Where("status=@status", new { status = 1 })
-                        .Select<AliyunPoolDTO>();
-                    if (list == null) return default;
-                    RedisHelper.Set(cache_key, list, 30 * 86400);
-                }
-            }
-            _cached = list;
-            return list;
-        }
-        public static void Refresh()
-        {
-            _ = List(true);
-        }
-        public static void UpdateDrawBalance(string name, decimal amout)
-        {
-            new DBContext.Table("aliyun_pool")
-                .Add("balance", amout)
-                .Add("last_time", DateTime.Now)
-                .Where("name=@name", new { name })
-                .Update();
-            _ = List(true);
-        }
-
-
-        public void GetBlances()
-        {
-            var list = new DBContext.Table("aliyun_pool").Where(string.Empty, new { }).Select<AliyunPoolDTO>();
-            foreach (var item in list)
-            {
-                if (string.IsNullOrEmpty(item.accessKeyId)) continue;
-
-                decimal amout = 0;
-                try
-                {
-                    AliyunCore core = new AliyunCore(item);
-                    var response = core.QueryAccountBalance();
-                    var balance = response.Body.Data.AvailableAmount;
-                    if (decimal.TryParse(balance, out amout))
-                    {
-                        UpdateDrawBalance(item.name, amout);
-                        if (item.balance != amout && amout <= item.balance_alert_threshold)
-                        {
-                            string message = $"【{item.company}】阿里云当前余额:{amout} 元";
-                            _ = NotifyCore.QYWeixinPushNotifyAsync("余额预警", message);
-                            NotifyCore.Notify(new NifyMessage
-                            {
-                                message = message,
-                                priority = NifyMessagePriority.high,
-                                tags = ["red_circle"]
-                            });
-                        }
-                    }
-                }
-                catch (Exception ex)
-                {
-                    new LoggerLibrary("aliyun", "balance_error")
-                    .Info($"Account:{item.id}\tbalance:{amout}")
-                    .Info(ex.Message, ex.StackTrace)
-                    .SaveAsync();
-                }
-            }
-        }
-
-
-
-        public static List<AliyunEcsDTO> EcsList(AliyunPoolDTO item)
-        {
-            List<AliyunEcsDTO> result = [];
-            decimal amout = 0;
-            try
-            {
-                AliyunCore core = new AliyunCore(item);
-                var regions = item.regions.Split(',');
-                foreach (var region in regions)
-                {
-                    var instances = core.GetInstances(region);
-                    foreach (var instance in instances.Instance)
-                    {
-                        var data = new AliyunEcsDTO();
-
-                        data.cpu = (int)instance.Cpu;
-                        data.instance_id = instance.InstanceId;
-                        data.instance_name = instance.InstanceName;
-                        data.host_name = instance.HostName;
-
-                        if (DateTime.TryParse(instance.ExpiredTime, out DateTime expired_time))
-                        {
-                            data.expired_time = expired_time;
-                        }
-                        if (DateTime.TryParse(instance.CreationTime, out DateTime creation_time))
-                        {
-                            data.creation_time = creation_time;
-                        }
-                        data.memory = (int)instance.Memory / 1024;
-                        data.region_id = instance.RegionId;
-                        data.instance_type = instance.InstanceType;
-
-                        var networks = core.DescribeNetworkInterfaces(data.region_id, data.instance_id);
-
-                        data.AddressIps = new List<AddressIpDTO>();
-                        foreach (var networkInterface in networks)
-                        {
-                            //找出网卡信息
-                            string networkInterfaceId = networkInterface.NetworkInterfaceId;
-                            var privateIps = networkInterface?.PrivateIpSets;
-                            foreach (var taraget in privateIps.PrivateIpSet)
-                            {
-                                string privateIp = taraget.PrivateIpAddress;
-                                //找到当前公网IP
-                                string currentPublicIp = taraget?.AssociatedPublicIp?.PublicIpAddress;
-                                if (string.IsNullOrEmpty(currentPublicIp))
-                                {
-                                    var currentEips = core.DescribeEipsAddresse(region, string.Empty);
-                                    var currentEip = currentEips?.Where(e => privateIp.Equals(e.PrivateIpAddress))?.FirstOrDefault();
-                                    currentPublicIp = currentEip?.IpAddress;
-
-                                    if (string.IsNullOrEmpty(currentPublicIp))
-                                    {
-                                        var emptyPrivateEips = currentEips?.Where(e => string.IsNullOrEmpty(e.PrivateIpAddress));
-                                        foreach (var emptyEip in emptyPrivateEips)
-                                        {
-                                            var body = core.DescribeNetworkInterfaceAttribute(region, emptyEip.InstanceId);
-                                            if (body != null)
-                                            {
-                                                if (body.PrivateIpAddress == privateIp)
-                                                {
-                                                    currentPublicIp = emptyEip?.IpAddress;
-                                                    break;
-                                                }
-                                            }
-                                        }
-                                    }
-                                }
-                                data.AddressIps.Add(new AddressIpDTO
-                                {
-                                    networkInterfaceId = networkInterfaceId,
-                                    privateIp = privateIp,
-                                    publicIp = currentPublicIp,
-                                });
-                            }
-                        }
-                        result.Add(data);
-                    }
-
-                }
-
-            }
-            catch (Exception ex)
-            {
-                new LoggerLibrary("aliyun", "balance_error")
-                .Info($"Account:{item.id}\tbalance:{amout}")
-                .Info(ex.Message, ex.StackTrace)
-                .SaveAsync();
-            }
-            return result;
-        }
-
-        public static List<AliyunEcsDTO> EcsList()
-        {
-            List<AliyunEcsDTO> result = new List<AliyunEcsDTO>();
-            var list = new DBContext.Table("aliyun_pool").Where(string.Empty, new { }).Select<AliyunPoolDTO>();
-            foreach (var item in list)
-            {
-                if (string.IsNullOrEmpty(item.accessKeyId)) continue;
-
-                decimal amout = 0;
-                try
-                {
-                    AliyunCore core = new AliyunCore(item);
-                    var regions = item.regions.Split(',');
-                    foreach (var region in regions)
-                    {
-                        var instances = core.GetInstances(region);
-                        foreach (var instance in instances.Instance)
-                        {
-                            var data = new AliyunEcsDTO();
-
-                            data.cpu = (int)instance.Cpu;
-                            data.instance_id = instance.InstanceId;
-                            data.instance_name = instance.InstanceName;
-                            data.host_name = instance.HostName;
-
-                            if (DateTime.TryParse(instance.ExpiredTime, out DateTime expired_time))
-                            {
-                                data.expired_time = expired_time;
-                            }
-                            if (DateTime.TryParse(instance.CreationTime, out DateTime creation_time))
-                            {
-                                data.creation_time = creation_time;
-                            }
-                            data.memory = (int)instance.Memory / 1024;
-                            data.region_id = instance.RegionId;
-                            data.instance_type = instance.InstanceType;
-
-                            var networks = core.DescribeNetworkInterfaces(data.region_id, data.instance_id);
-
-                            data.AddressIps = new List<AddressIpDTO>();
-                            foreach (var networkInterface in networks)
-                            {
-                                //找出网卡信息
-                                string networkInterfaceId = networkInterface.NetworkInterfaceId;
-                                var privateIps = networkInterface?.PrivateIpSets;
-                                foreach (var taraget in privateIps.PrivateIpSet)
-                                {
-                                    //找到当前公网IP
-                                    string currentPublicIp = taraget?.AssociatedPublicIp?.PublicIpAddress;
-
-                                    data.AddressIps.Add(new AddressIpDTO
-                                    {
-                                        networkInterfaceId = networkInterfaceId,
-                                        privateIp = taraget.PrivateIpAddress,
-                                        publicIp = currentPublicIp,
-                                    });
-                                }
-                            }
-                            result.Add(data);
-                        }
-
-                    }
-
-                }
-                catch (Exception ex)
-                {
-                    new LoggerLibrary("aliyun", "balance_error")
-                    .Info($"Account:{item.id}\tbalance:{amout}")
-                    .Info(ex.Message, ex.StackTrace)
-                    .SaveAsync();
-                }
-            }
-            return result;
-        }
-
-    }
-
-
-}
-
+using dodohold.core;
+using Dataoke;
+using Tea;
+using AlibabaCloud.SDK.BssOpenApi20171214.Models;
+using AlibabaCloud.SDK.Ecs20140526.Models;
+using AlibabaCloud.SDK.Vpc20160428.Models;
+using TencentCloud.Dbbrain.V20210527.Models;
+using Org.BouncyCastle.Bcpg.OpenPgp;
+using TencentCloud.Tke.V20180525.Models;
+using static molilian.core.TkPoolCore;
+using ZstdSharp.Unsafe;
+using System.Collections.Generic;
+
+namespace molilian.core
+{
+
+    public partial class AliyunPoolCore
+    {
+
+        private static readonly object _lockObj = new();
+        private static IEnumerable<AliyunPoolDTO> _cached;
+
+        public static AliyunPoolDTO? GetOne(int id)
+        {
+            var list = List();
+            if (!list.Any()) return null;
+            return list.Where(e => e.id == id).FirstOrDefault();
+        }
+
+        public static IEnumerable<AliyunPoolDTO> List(bool force = false)
+        {
+
+            if (!force && _cached != null) return _cached;
+
+            string cache_key = $"cache:aliyun_pool";
+            var list = RedisHelper.Get<IEnumerable<AliyunPoolDTO>>(cache_key);
+            if (force || list == null)
+            {
+                lock (_lockObj)
+                {
+                    list = new DBContext.Table("aliyun_pool")
+                        .Where("status=@status", new { status = 1 })
+                        .Select<AliyunPoolDTO>();
+
+                    list = new DBContext.Table("aliyun_pool")
+                        .Where("status=@status", new { status = 1 })
+                        .Select<AliyunPoolDTO>();
+                    if (list == null) return default;
+                    RedisHelper.Set(cache_key, list, 30 * 86400);
+                }
+            }
+            _cached = list;
+            return list;
+        }
+        public static void Refresh()
+        {
+            _ = List(true);
+        }
+        public static void UpdateDrawBalance(string name, decimal amout)
+        {
+            new DBContext.Table("aliyun_pool")
+                .Add("balance", amout)
+                .Add("last_time", DateTime.Now)
+                .Where("name=@name", new { name })
+                .Update();
+            _ = List(true);
+        }
+
+
+        public void GetBlances()
+        {
+            var list = new DBContext.Table("aliyun_pool").Where(string.Empty, new { }).Select<AliyunPoolDTO>();
+            foreach (var item in list)
+            {
+                if (string.IsNullOrEmpty(item.accessKeyId)) continue;
+
+                decimal amout = 0;
+                try
+                {
+                    AliyunCore core = new AliyunCore(item);
+                    var response = core.QueryAccountBalance();
+                    var balance = response.Body.Data.AvailableAmount;
+                    if (decimal.TryParse(balance, out amout))
+                    {
+                        UpdateDrawBalance(item.name, amout);
+                        if (item.balance != amout && amout <= item.balance_alert_threshold)
+                        {
+                            string message = $"【{item.company}】阿里云当前余额:{amout} 元";
+                            _ = NotifyCore.QYWeixinPushNotifyAsync("余额预警", message);
+                            NotifyCore.Notify(new NifyMessage
+                            {
+                                message = message,
+                                priority = NifyMessagePriority.high,
+                                tags = ["red_circle"]
+                            });
+                        }
+                    }
+                }
+                catch (Exception ex)
+                {
+                    new LoggerLibrary("aliyun", "balance_error")
+                    .Info($"Account:{item.id}\tbalance:{amout}")
+                    .Info(ex.Message, ex.StackTrace)
+                    .SaveAsync();
+                }
+            }
+        }
+
+
+
+        public static List<AliyunEcsDTO> EcsList(AliyunPoolDTO item)
+        {
+            List<AliyunEcsDTO> result = [];
+            decimal amout = 0;
+            try
+            {
+                AliyunCore core = new AliyunCore(item);
+                var regions = item.regions.Split(',');
+                foreach (var region in regions)
+                {
+                    var instances = core.GetInstances(region);
+                    foreach (var instance in instances.Instance)
+                    {
+                        var data = new AliyunEcsDTO();
+
+                        data.cpu = (int)instance.Cpu;
+                        data.instance_id = instance.InstanceId;
+                        data.instance_name = instance.InstanceName;
+                        data.host_name = instance.HostName;
+
+                        if (DateTime.TryParse(instance.ExpiredTime, out DateTime expired_time))
+                        {
+                            data.expired_time = expired_time;
+                        }
+                        if (DateTime.TryParse(instance.CreationTime, out DateTime creation_time))
+                        {
+                            data.creation_time = creation_time;
+                        }
+                        data.memory = (int)instance.Memory / 1024;
+                        data.region_id = instance.RegionId;
+                        data.instance_type = instance.InstanceType;
+
+                        var networks = core.DescribeNetworkInterfaces(data.region_id, data.instance_id);
+
+                        data.AddressIps = new List<AddressIpDTO>();
+                        foreach (var networkInterface in networks)
+                        {
+                            //找出网卡信息
+                            string networkInterfaceId = networkInterface.NetworkInterfaceId;
+                            var privateIps = networkInterface?.PrivateIpSets;
+                            foreach (var taraget in privateIps.PrivateIpSet)
+                            {
+                                string privateIp = taraget.PrivateIpAddress;
+                                //找到当前公网IP
+                                string currentPublicIp = taraget?.AssociatedPublicIp?.PublicIpAddress;
+                                if (string.IsNullOrEmpty(currentPublicIp))
+                                {
+                                    var currentEips = core.DescribeEipsAddresse(region, string.Empty);
+                                    var currentEip = currentEips?.Where(e => privateIp.Equals(e.PrivateIpAddress))?.FirstOrDefault();
+                                    currentPublicIp = currentEip?.IpAddress;
+
+                                    if (string.IsNullOrEmpty(currentPublicIp))
+                                    {
+                                        var emptyPrivateEips = currentEips?.Where(e => string.IsNullOrEmpty(e.PrivateIpAddress));
+                                        foreach (var emptyEip in emptyPrivateEips)
+                                        {
+                                            var body = core.DescribeNetworkInterfaceAttribute(region, emptyEip.InstanceId);
+                                            if (body != null)
+                                            {
+                                                if (body.PrivateIpAddress == privateIp)
+                                                {
+                                                    currentPublicIp = emptyEip?.IpAddress;
+                                                    break;
+                                                }
+                                            }
+                                        }
+                                    }
+                                }
+                                data.AddressIps.Add(new AddressIpDTO
+                                {
+                                    networkInterfaceId = networkInterfaceId,
+                                    privateIp = privateIp,
+                                    publicIp = currentPublicIp,
+                                });
+                            }
+                        }
+                        result.Add(data);
+                    }
+
+                }
+
+            }
+            catch (Exception ex)
+            {
+                new LoggerLibrary("aliyun", "balance_error")
+                .Info($"Account:{item.id}\tbalance:{amout}")
+                .Info(ex.Message, ex.StackTrace)
+                .SaveAsync();
+            }
+            return result;
+        }
+
+        public static List<AliyunEcsDTO> EcsList()
+        {
+            List<AliyunEcsDTO> result = new List<AliyunEcsDTO>();
+            var list = new DBContext.Table("aliyun_pool").Where(string.Empty, new { }).Select<AliyunPoolDTO>();
+            foreach (var item in list)
+            {
+                if (string.IsNullOrEmpty(item.accessKeyId)) continue;
+
+                decimal amout = 0;
+                try
+                {
+                    AliyunCore core = new AliyunCore(item);
+                    var regions = item.regions.Split(',');
+                    foreach (var region in regions)
+                    {
+                        var instances = core.GetInstances(region);
+                        foreach (var instance in instances.Instance)
+                        {
+                            var data = new AliyunEcsDTO();
+
+                            data.cpu = (int)instance.Cpu;
+                            data.instance_id = instance.InstanceId;
+                            data.instance_name = instance.InstanceName;
+                            data.host_name = instance.HostName;
+
+                            if (DateTime.TryParse(instance.ExpiredTime, out DateTime expired_time))
+                            {
+                                data.expired_time = expired_time;
+                            }
+                            if (DateTime.TryParse(instance.CreationTime, out DateTime creation_time))
+                            {
+                                data.creation_time = creation_time;
+                            }
+                            data.memory = (int)instance.Memory / 1024;
+                            data.region_id = instance.RegionId;
+                            data.instance_type = instance.InstanceType;
+
+                            var networks = core.DescribeNetworkInterfaces(data.region_id, data.instance_id);
+
+                            data.AddressIps = new List<AddressIpDTO>();
+                            foreach (var networkInterface in networks)
+                            {
+                                //找出网卡信息
+                                string networkInterfaceId = networkInterface.NetworkInterfaceId;
+                                var privateIps = networkInterface?.PrivateIpSets;
+                                foreach (var taraget in privateIps.PrivateIpSet)
+                                {
+                                    //找到当前公网IP
+                                    string currentPublicIp = taraget?.AssociatedPublicIp?.PublicIpAddress;
+
+                                    data.AddressIps.Add(new AddressIpDTO
+                                    {
+                                        networkInterfaceId = networkInterfaceId,
+                                        privateIp = taraget.PrivateIpAddress,
+                                        publicIp = currentPublicIp,
+                                    });
+                                }
+                            }
+                            result.Add(data);
+                        }
+
+                    }
+
+                }
+                catch (Exception ex)
+                {
+                    new LoggerLibrary("aliyun", "balance_error")
+                    .Info($"Account:{item.id}\tbalance:{amout}")
+                    .Info(ex.Message, ex.StackTrace)
+                    .SaveAsync();
+                }
+            }
+            return result;
+        }
+
+    }
+
+
+}
+

+ 230 - 230
molilian.core/Core/cps/CpsPoolCore.cs

@@ -1,230 +1,230 @@
-using dodohold.core;
-using System.Threading.Channels;
-
-namespace molilian.core
-{
-
-    public partial class CpsPoolCore
-    {
-        private static string _end_point;
-        private static Dictionary<string, decimal> _incomeAmt = new();
-        private static Dictionary<string, int> _calls = new();
-
-        static Random _random = new Random();
-        static CpsPoolCore()
-        {
-            _end_point = Environment.GetEnvironmentVariable("EndPoint");
-        }
-
-        private static readonly object _lockObj = new();
-        private static IEnumerable<CpsLinksDTO> _links_cached;
-        private static IEnumerable<CpsApiStyleDTO> _api_style_cached;
-        private static IEnumerable<CpsPoolDTO> _cached;
-
-        public static CpsLinksDTO? GetOne(string channel)
-        {
-            var list = List();
-            if (!list.Any()) return null;
-
-            var pool = list.Where(e => e.channel_code == channel && e.status).FirstOrDefault();
-            if (pool == null) return null;
-
-            var links = LinksList();
-            if (!links.Any()) return null;
-
-            // 获取符合条件的链接
-            var eligibleLinks = links.Where(e => IsEligible(e, channel)).ToList();
-            if (!eligibleLinks.Any()) return null;
-
-            // 计算总权重
-            int totalWeight = eligibleLinks.Sum(item => item.priority_weight);
-            if (totalWeight == 0) return null;
-
-            int randomValue = _random.Next(totalWeight);
-
-            // 按权重查找
-            int accumulatedWeight = 0;
-            foreach (var link in eligibleLinks.OrderByDescending(e => e.priority_weight))
-            {
-                accumulatedWeight += link.priority_weight;
-                if (randomValue < accumulatedWeight)
-                    return link;
-            }
-            // 保险起见返回第一个
-            return eligibleLinks.First();
-        }
-
-
-
-        public static CpsLinksDTO? GetOne2(string channel)
-        {
-            var list = List();
-            if (!list.Any()) return null;
-
-            var pool = list.Where(e => e.channel_code == channel && e.status).FirstOrDefault();
-            if (pool == null) return null;
-
-            var links = LinksList();
-            if (!links.Any()) return null;
-
-            //int totalWeight = links.Sum(item => item.priority_weight);
-
-            //int randomValue = _random.Next(0,totalWeight);
-
-            //// 累积权重直到找到选中项
-            //double accumulatedWeight = 0;
-            //foreach (var item in items)
-            //{
-            //    accumulatedWeight += Math.Pow(2, weightSelector(item));
-            //    if (randomValue <= accumulatedWeight)
-            //        return item;
-            //}
-
-
-            return links.Where(e => IsEligible(e, channel))
-                .OrderByDescending(e => e.priority_weight)
-                .ThenBy(l => Guid.NewGuid())
-                .FirstOrDefault();
-        }
-
-
-        private static bool IsEligible(CpsLinksDTO item, string channel)
-        {
-            if (item.channel_code != channel) return false;
-
-            // 使用初始化参数创建工作时间表
-            if (!new WorkSchedule(item.time_range).IsWorkHour()) return false;
-
-            var now = DateTime.Now;
-            if (item.start_time > now || now > item.end_time) return false;
-
-
-            if (item.daily_calls_limit > 0)
-            {
-                int daily_num = RiskControlCore.GetCalls(TkChannelEnum.cps, item.id, DateTime.Now.ToString("yyyyMMdd"));
-                if (daily_num >= item.daily_calls_limit) return false;
-            }
-            if (item.hourly_calls_limit > 0)
-            {
-                int hourly_num = RiskControlCore.GetCalls(TkChannelEnum.cps, item.id, DateTime.Now.ToString("yyyyMMddHH"));
-                if (hourly_num >= item.hourly_calls_limit) return false;
-            }
-            return true;
-        }
-
-        public static IEnumerable<CpsPoolDTO> List(bool force = false)
-        {
-
-            if (!force && _cached != null) return _cached;
-
-            string cache_key = $"cache:cps_pool";
-            var list = RedisHelper.Get<IEnumerable<CpsPoolDTO>>(cache_key);
-            if (force || list == null)
-            {
-                lock (_lockObj)
-                {
-                    list = new DBContext.Table("cps_pool")
-                        .Where("status=@status", new { status = 1 })
-                        .Select<CpsPoolDTO>();
-                    if (list == null) return default;
-                    RedisHelper.Set(cache_key, list, 30 * 86400);
-                }
-            }
-            _cached = list;
-            return list;
-
-        }
-        public static IEnumerable<CpsLinksDTO> LinksList(bool force = false)
-        {
-            if (!force && _links_cached != null) return _links_cached;
-
-            string cache_key = $"cache:cps_links";
-            var list = RedisHelper.Get<IEnumerable<CpsLinksDTO>>(cache_key);
-            if (force || list == null)
-            {
-                lock (_lockObj)
-                {
-                    list = new DBContext.Table("cps_links")
-                        .Where("status=@status", new { status = 1 })
-                        .Select<CpsLinksDTO>();
-                    if (list == null) return default;
-                    foreach (var item in list)
-                    {
-                        RiskControlCore.SetCallsAsync(TkChannelEnum.cps, item.id, DateTime.Now.ToString("yyyyMMddHH"), item.current_hourly_calls);
-                        RiskControlCore.SetCallsAsync(TkChannelEnum.cps, item.id, DateTime.Now.ToString("yyyyMMdd"), item.current_daily_calls);
-                    }
-                    RedisHelper.Set(cache_key, list, 30 * 86400);
-                }
-            }
-            _links_cached = list;
-            return list;
-        }
-
-        public static IEnumerable<CpsApiStyleDTO> ApiStyleList(bool force = false)
-        {
-            if (!force && _api_style_cached != null) return _api_style_cached;
-
-            string cache_key = $"cache:cps_api_style";
-            var list = RedisHelper.Get<IEnumerable<CpsApiStyleDTO>>(cache_key);
-            if (force || list == null)
-            {
-                lock (_lockObj)
-                {
-                    list = new DBContext.Table("cps_api_syle")
-                        .Where("status=@status", new { status = 1 })
-                        .Select<CpsApiStyleDTO>();
-                    if (list == null) return default;
-                    RedisHelper.Set(cache_key, list, 30 * 86400);
-                }
-            }
-            _api_style_cached = list;
-            return list;
-        }
-        public static int Update(CpsLinksDTO account)
-        {
-            return new DBContext.Table("cps_links")
-                .Add("current_hourly_calls", account.current_hourly_calls)
-                .Add("current_daily_calls", account.current_daily_calls)
-                .Where("id=@id", new { account.id })
-                .Update();
-        }
-
-        public static void Refresh()
-        {
-            _ = List(true);
-            _ = LinksList(true);
-            _ = ApiStyleList(true);
-        }
-
-        internal static void AccountExhausted()
-        {
-            string cache_key = $"cache:tk_pool:account:exhausted";
-            long count = RedisHelper.IncrBy(cache_key);
-            if (count > 1) return;
-            RedisHelper.Expire(cache_key, 3600);
-            NotifyCore.Notify(new NifyMessage
-            {
-                message = $"【CPS优惠券】没有匹配账号",
-                priority = NifyMessagePriority.high,
-                tags = ["red_circle"]
-            });
-            _ = NotifyCore.QYWeixinPushNotifyAsync("没账号", $"【CPS优惠券】没有匹配账号");
-        }
-
-        public static CpsApiStyleDTO? GetTitle(CpsLinksDTO account, string style)
-        {
-            CpsApiStyleDTO result = null;
-            try
-            {
-                if (string.IsNullOrEmpty(account.style_code)) return null;
-                var list = ApiStyleList();
-                var record = list?.Where(e => e.style_code == account.style_code && e.style_name == style).FirstOrDefault();
-                if (record != null) result = record;
-            }
-            catch { }
-            return result;
-        }
-    }
-
-
-}
+using dodohold.core;
+using System.Threading.Channels;
+
+namespace molilian.core
+{
+
+    public partial class CpsPoolCore
+    {
+        private static string _end_point;
+        private static Dictionary<string, decimal> _incomeAmt = new();
+        private static Dictionary<string, int> _calls = new();
+
+        static Random _random = new Random();
+        static CpsPoolCore()
+        {
+            _end_point = Environment.GetEnvironmentVariable("EndPoint");
+        }
+
+        private static readonly object _lockObj = new();
+        private static IEnumerable<CpsLinksDTO> _links_cached;
+        private static IEnumerable<CpsApiStyleDTO> _api_style_cached;
+        private static IEnumerable<CpsPoolDTO> _cached;
+
+        public static CpsLinksDTO? GetOne(string channel)
+        {
+            var list = List();
+            if (!list.Any()) return null;
+
+            var pool = list.Where(e => e.channel_code == channel && e.status).FirstOrDefault();
+            if (pool == null) return null;
+
+            var links = LinksList();
+            if (!links.Any()) return null;
+
+            // 获取符合条件的链接
+            var eligibleLinks = links.Where(e => IsEligible(e, channel)).ToList();
+            if (!eligibleLinks.Any()) return null;
+
+            // 计算总权重
+            int totalWeight = eligibleLinks.Sum(item => item.priority_weight);
+            if (totalWeight == 0) return null;
+
+            int randomValue = _random.Next(totalWeight);
+
+            // 按权重查找
+            int accumulatedWeight = 0;
+            foreach (var link in eligibleLinks.OrderByDescending(e => e.priority_weight))
+            {
+                accumulatedWeight += link.priority_weight;
+                if (randomValue < accumulatedWeight)
+                    return link;
+            }
+            // 保险起见返回第一个
+            return eligibleLinks.First();
+        }
+
+
+
+        public static CpsLinksDTO? GetOne2(string channel)
+        {
+            var list = List();
+            if (!list.Any()) return null;
+
+            var pool = list.Where(e => e.channel_code == channel && e.status).FirstOrDefault();
+            if (pool == null) return null;
+
+            var links = LinksList();
+            if (!links.Any()) return null;
+
+            //int totalWeight = links.Sum(item => item.priority_weight);
+
+            //int randomValue = _random.Next(0,totalWeight);
+
+            //// 累积权重直到找到选中项
+            //double accumulatedWeight = 0;
+            //foreach (var item in items)
+            //{
+            //    accumulatedWeight += Math.Pow(2, weightSelector(item));
+            //    if (randomValue <= accumulatedWeight)
+            //        return item;
+            //}
+
+
+            return links.Where(e => IsEligible(e, channel))
+                .OrderByDescending(e => e.priority_weight)
+                .ThenBy(l => Guid.NewGuid())
+                .FirstOrDefault();
+        }
+
+
+        private static bool IsEligible(CpsLinksDTO item, string channel)
+        {
+            if (item.channel_code != channel) return false;
+
+            // 使用初始化参数创建工作时间表
+            if (!new WorkSchedule(item.time_range).IsWorkHour()) return false;
+
+            var now = DateTime.Now;
+            if (item.start_time > now || now > item.end_time) return false;
+
+
+            if (item.daily_calls_limit > 0)
+            {
+                int daily_num = RiskControlCore.GetCalls(TkChannelEnum.cps, item.id, DateTime.Now.ToString("yyyyMMdd"));
+                if (daily_num >= item.daily_calls_limit) return false;
+            }
+            if (item.hourly_calls_limit > 0)
+            {
+                int hourly_num = RiskControlCore.GetCalls(TkChannelEnum.cps, item.id, DateTime.Now.ToString("yyyyMMddHH"));
+                if (hourly_num >= item.hourly_calls_limit) return false;
+            }
+            return true;
+        }
+
+        public static IEnumerable<CpsPoolDTO> List(bool force = false)
+        {
+
+            if (!force && _cached != null) return _cached;
+
+            string cache_key = $"cache:cps_pool";
+            var list = RedisHelper.Get<IEnumerable<CpsPoolDTO>>(cache_key);
+            if (force || list == null)
+            {
+                lock (_lockObj)
+                {
+                    list = new DBContext.Table("cps_pool")
+                        .Where("status=@status", new { status = 1 })
+                        .Select<CpsPoolDTO>();
+                    if (list == null) return default;
+                    RedisHelper.Set(cache_key, list, 30 * 86400);
+                }
+            }
+            _cached = list;
+            return list;
+
+        }
+        public static IEnumerable<CpsLinksDTO> LinksList(bool force = false)
+        {
+            if (!force && _links_cached != null) return _links_cached;
+
+            string cache_key = $"cache:cps_links";
+            var list = RedisHelper.Get<IEnumerable<CpsLinksDTO>>(cache_key);
+            if (force || list == null)
+            {
+                lock (_lockObj)
+                {
+                    list = new DBContext.Table("cps_links")
+                        .Where("status=@status", new { status = 1 })
+                        .Select<CpsLinksDTO>();
+                    if (list == null) return default;
+                    foreach (var item in list)
+                    {
+                        RiskControlCore.SetCallsAsync(TkChannelEnum.cps, item.id, DateTime.Now.ToString("yyyyMMddHH"), item.current_hourly_calls);
+                        RiskControlCore.SetCallsAsync(TkChannelEnum.cps, item.id, DateTime.Now.ToString("yyyyMMdd"), item.current_daily_calls);
+                    }
+                    RedisHelper.Set(cache_key, list, 30 * 86400);
+                }
+            }
+            _links_cached = list;
+            return list;
+        }
+
+        public static IEnumerable<CpsApiStyleDTO> ApiStyleList(bool force = false)
+        {
+            if (!force && _api_style_cached != null) return _api_style_cached;
+
+            string cache_key = $"cache:cps_api_style";
+            var list = RedisHelper.Get<IEnumerable<CpsApiStyleDTO>>(cache_key);
+            if (force || list == null)
+            {
+                lock (_lockObj)
+                {
+                    list = new DBContext.Table("cps_api_syle")
+                        .Where("status=@status", new { status = 1 })
+                        .Select<CpsApiStyleDTO>();
+                    if (list == null) return default;
+                    RedisHelper.Set(cache_key, list, 30 * 86400);
+                }
+            }
+            _api_style_cached = list;
+            return list;
+        }
+        public static int Update(CpsLinksDTO account)
+        {
+            return new DBContext.Table("cps_links")
+                .Add("current_hourly_calls", account.current_hourly_calls)
+                .Add("current_daily_calls", account.current_daily_calls)
+                .Where("id=@id", new { account.id })
+                .Update();
+        }
+
+        public static void Refresh()
+        {
+            _ = List(true);
+            _ = LinksList(true);
+            _ = ApiStyleList(true);
+        }
+
+        internal static void AccountExhausted()
+        {
+            string cache_key = $"cache:tk_pool:account:exhausted";
+            long count = RedisHelper.IncrBy(cache_key);
+            if (count > 1) return;
+            RedisHelper.Expire(cache_key, 3600);
+            NotifyCore.Notify(new NifyMessage
+            {
+                message = $"【CPS优惠券】没有匹配账号",
+                priority = NifyMessagePriority.high,
+                tags = ["red_circle"]
+            });
+            _ = NotifyCore.QYWeixinPushNotifyAsync("没账号", $"【CPS优惠券】没有匹配账号");
+        }
+
+        public static CpsApiStyleDTO? GetTitle(CpsLinksDTO account, string style)
+        {
+            CpsApiStyleDTO result = null;
+            try
+            {
+                if (string.IsNullOrEmpty(account.style_code)) return null;
+                var list = ApiStyleList();
+                var record = list?.Where(e => e.style_code == account.style_code && e.style_name == style).FirstOrDefault();
+                if (record != null) result = record;
+            }
+            catch { }
+            return result;
+        }
+    }
+
+
+}

+ 105 - 105
molilian.core/Core/cps/ElePoolCore.cs

@@ -1,105 +1,105 @@
-using dodohold.core;
-
-namespace molilian.core
-{
-
-    public partial class ElePoolCore
-    {
-        private static string _end_point;
-        private static Dictionary<string, decimal> _incomeAmt = new();
-        static ElePoolCore()
-        {
-            _end_point = Environment.GetEnvironmentVariable("EndPoint");
-        }
-
-        private static readonly object _lockObj = new();
-        private static IEnumerable<ElePoolDTO> _cached;
-
-        public static ElePoolDTO? GetOne()
-        {
-            var list = List();
-            if (!list.Any()) return null;
-            return list.Where(IsNotExceedDailyIncomeLimit).OrderBy(l => Guid.NewGuid()).FirstOrDefault();
-        }
-
-
-        public static ElePoolDTO? GetOne(int id)
-        {
-            var list = List();
-            if (!list.Any()) return null;
-            return list.Where(e => e.id == id && IsNotExceedDailyIncomeLimit(e)).FirstOrDefault();
-        }
-
-        private static bool IsNotExceedDailyIncomeLimit(ElePoolDTO item)
-        {
-            if (!string.IsNullOrEmpty(_end_point) && !string.IsNullOrEmpty(item.end_point))
-            {
-                if (item.end_point != _end_point) return false;
-            }
-
-            // 使用初始化参数创建工作时间表
-            if (!new WorkSchedule(item.time_range).IsWorkHour()) return false;
-
-            if (item.daily_calls_limit > 0)
-            {
-                int daily_num = RiskControlCore.GetCalls(TkChannelEnum.eleme, item.id, DateTime.Now.ToString("yyyyMMdd"));
-                if (daily_num >= item.daily_calls_limit) return false;
-            }
-            if (item.hourly_calls_limit > 0)
-            {
-                int hourly_num = RiskControlCore.GetCalls(TkChannelEnum.eleme, item.id, DateTime.Now.ToString("yyyyMMddHH"));
-                if (hourly_num >= item.hourly_calls_limit) return false;
-            }
-            return true;
-        }
-
-        public static IEnumerable<ElePoolDTO> List(bool force = false)
-        {
-            if (!force && _cached != null) return _cached;
-
-            string cache_key = $"cache:ele_pool";
-            var list = RedisHelper.Get<IEnumerable<ElePoolDTO>>(cache_key);
-            if (force || list == null)
-            {
-                lock (_lockObj)
-                {
-                    list = new DBContext.Table("ele_pool")
-                        .Where("status=@status", new { status = 1 })
-                        .Select<ElePoolDTO>();
-                    if (list == null) return default;
-
-                    foreach (var item in list)
-                    {
-                        RiskControlCore.SetCallsAsync(TkChannelEnum.eleme, item.id, DateTime.Now.ToString("yyyyMMddHH"), item.current_hourly_calls);
-                        RiskControlCore.SetCallsAsync(TkChannelEnum.eleme, item.id, DateTime.Now.ToString("yyyyMMdd"), item.current_daily_calls);
-                    }
-                    RedisHelper.Set(cache_key, list, 30 * 86400);
-                }
-            }
-            _cached = list;
-            return list;
-        }
-        public static void Refresh()
-        {
-            _ = List(true);
-        }
-
-        internal static void AccountExhausted()
-        {
-            string cache_key = $"cache:tk_pool:account:exhausted";
-            long count = RedisHelper.IncrBy(cache_key);
-            if (count > 1) return;
-            RedisHelper.Expire(cache_key, 3600);
-            NotifyCore.Notify(new NifyMessage
-            {
-                message = $"【饿了么联盟】没有匹配账号",
-                priority = NifyMessagePriority.high,
-                tags = ["red_circle"]
-            });
-            NotifyCore.QYWeixinPushNotifyAsync("没账号", $"【饿了么联盟】没有匹配账号");
-        }
-
-    }
-
-
-}
+using dodohold.core;
+
+namespace molilian.core
+{
+
+    public partial class ElePoolCore
+    {
+        private static string _end_point;
+        private static Dictionary<string, decimal> _incomeAmt = new();
+        static ElePoolCore()
+        {
+            _end_point = Environment.GetEnvironmentVariable("EndPoint");
+        }
+
+        private static readonly object _lockObj = new();
+        private static IEnumerable<ElePoolDTO> _cached;
+
+        public static ElePoolDTO? GetOne()
+        {
+            var list = List();
+            if (!list.Any()) return null;
+            return list.Where(IsNotExceedDailyIncomeLimit).OrderBy(l => Guid.NewGuid()).FirstOrDefault();
+        }
+
+
+        public static ElePoolDTO? GetOne(int id)
+        {
+            var list = List();
+            if (!list.Any()) return null;
+            return list.Where(e => e.id == id && IsNotExceedDailyIncomeLimit(e)).FirstOrDefault();
+        }
+
+        private static bool IsNotExceedDailyIncomeLimit(ElePoolDTO item)
+        {
+            if (!string.IsNullOrEmpty(_end_point) && !string.IsNullOrEmpty(item.end_point))
+            {
+                if (item.end_point != _end_point) return false;
+            }
+
+            // 使用初始化参数创建工作时间表
+            if (!new WorkSchedule(item.time_range).IsWorkHour()) return false;
+
+            if (item.daily_calls_limit > 0)
+            {
+                int daily_num = RiskControlCore.GetCalls(TkChannelEnum.eleme, item.id, DateTime.Now.ToString("yyyyMMdd"));
+                if (daily_num >= item.daily_calls_limit) return false;
+            }
+            if (item.hourly_calls_limit > 0)
+            {
+                int hourly_num = RiskControlCore.GetCalls(TkChannelEnum.eleme, item.id, DateTime.Now.ToString("yyyyMMddHH"));
+                if (hourly_num >= item.hourly_calls_limit) return false;
+            }
+            return true;
+        }
+
+        public static IEnumerable<ElePoolDTO> List(bool force = false)
+        {
+            if (!force && _cached != null) return _cached;
+
+            string cache_key = $"cache:ele_pool";
+            var list = RedisHelper.Get<IEnumerable<ElePoolDTO>>(cache_key);
+            if (force || list == null)
+            {
+                lock (_lockObj)
+                {
+                    list = new DBContext.Table("ele_pool")
+                        .Where("status=@status", new { status = 1 })
+                        .Select<ElePoolDTO>();
+                    if (list == null) return default;
+
+                    foreach (var item in list)
+                    {
+                        RiskControlCore.SetCallsAsync(TkChannelEnum.eleme, item.id, DateTime.Now.ToString("yyyyMMddHH"), item.current_hourly_calls);
+                        RiskControlCore.SetCallsAsync(TkChannelEnum.eleme, item.id, DateTime.Now.ToString("yyyyMMdd"), item.current_daily_calls);
+                    }
+                    RedisHelper.Set(cache_key, list, 30 * 86400);
+                }
+            }
+            _cached = list;
+            return list;
+        }
+        public static void Refresh()
+        {
+            _ = List(true);
+        }
+
+        internal static void AccountExhausted()
+        {
+            string cache_key = $"cache:tk_pool:account:exhausted";
+            long count = RedisHelper.IncrBy(cache_key);
+            if (count > 1) return;
+            RedisHelper.Expire(cache_key, 3600);
+            NotifyCore.Notify(new NifyMessage
+            {
+                message = $"【饿了么联盟】没有匹配账号",
+                priority = NifyMessagePriority.high,
+                tags = ["red_circle"]
+            });
+            NotifyCore.QYWeixinPushNotifyAsync("没账号", $"【饿了么联盟】没有匹配账号");
+        }
+
+    }
+
+
+}

+ 101 - 101
molilian.core/Core/cps/MeituanPoolCore.cs

@@ -1,101 +1,101 @@
-using dodohold.core;
-
-namespace molilian.core
-{
-
-    public partial class MeituanPoolCore
-    {
-        private static string _end_point;
-        static MeituanPoolCore()
-        {
-            _end_point = Environment.GetEnvironmentVariable("EndPoint");
-        }
-        private static readonly object _lockObj = new();
-        private static IEnumerable<MeituanPoolDTO> _cached;
-
-        public static MeituanPoolDTO? GetOne()
-        {
-            var list = List();
-            if (!list.Any()) return null;
-            return list.Where(IsNotExceedDailyIncomeLimit).OrderBy(l => Guid.NewGuid()).FirstOrDefault();
-        }
-
-
-        public static MeituanPoolDTO? GetOne(int id)
-        {
-            var list = List();
-            if (!list.Any()) return null;
-            return list.Where(e => e.id == id && IsNotExceedDailyIncomeLimit(e)).FirstOrDefault();
-        }
-        private static bool IsNotExceedDailyIncomeLimit(MeituanPoolDTO item)
-        {
-            if (!string.IsNullOrEmpty(_end_point) && !string.IsNullOrEmpty(item.end_point))
-            {
-                if (item.end_point != _end_point) return false;
-            }
-
-            // 使用初始化参数创建工作时间表
-            if (!new WorkSchedule(item.time_range).IsWorkHour()) return false;
-
-            if (item.daily_calls_limit > 0)
-            {
-                int daily_num = RiskControlCore.GetCalls(TkChannelEnum.meituan, item.id, DateTime.Now.ToString("yyyyMMdd"));
-                if (daily_num >= item.daily_calls_limit) return false;
-            }
-            if (item.hourly_calls_limit > 0)
-            {
-                int hourly_num = RiskControlCore.GetCalls(TkChannelEnum.meituan, item.id, DateTime.Now.ToString("yyyyMMddHH"));
-                if (hourly_num >= item.hourly_calls_limit) return false;
-            }
-            return true;
-        }
-
-        public static IEnumerable<MeituanPoolDTO> List(bool force = false)
-        {
-            if (!force && _cached != null) return _cached;
-
-            string cache_key = $"cache:meituan_pool";
-            var list = RedisHelper.Get<IEnumerable<MeituanPoolDTO>>(cache_key);
-            if (force || list == null)
-            {
-                lock (_lockObj)
-                {
-                    list = new DBContext.Table("meituan_pool")
-                        .Where("status=@status", new { status = 1 })
-                        .Select<MeituanPoolDTO>();
-                    if (list == null) return default;
-                    foreach (var item in list)
-                    {
-                        RiskControlCore.SetCallsAsync(TkChannelEnum.meituan, item.id, DateTime.Now.ToString("yyyyMMddHH"), item.current_hourly_calls);
-                        RiskControlCore.SetCallsAsync(TkChannelEnum.meituan, item.id, DateTime.Now.ToString("yyyyMMdd"), item.current_daily_calls);
-                    }
-                    RedisHelper.Set(cache_key, list, 30 * 86400);
-                }
-            }
-            _cached = list;
-            return list;
-        }
-        public static void Refresh()
-        {
-            _ = List(true);
-        }
-
-        internal static void AccountExhausted()
-        {
-            string cache_key = $"cache:tk_pool:account:exhausted";
-            long count = RedisHelper.IncrBy(cache_key);
-            if (count > 1) return;
-            RedisHelper.Expire(cache_key, 3600);
-            NotifyCore.Notify(new NifyMessage
-            {
-                message = $"【美团联盟】没有匹配账号",
-                priority = NifyMessagePriority.high,
-                tags = ["red_circle"]
-            });
-            _ = NotifyCore.QYWeixinPushNotifyAsync("没账号", $"【美团联盟】没有匹配账号");
-        }
-
-    }
-
-
-}
+using dodohold.core;
+
+namespace molilian.core
+{
+
+    public partial class MeituanPoolCore
+    {
+        private static string _end_point;
+        static MeituanPoolCore()
+        {
+            _end_point = Environment.GetEnvironmentVariable("EndPoint");
+        }
+        private static readonly object _lockObj = new();
+        private static IEnumerable<MeituanPoolDTO> _cached;
+
+        public static MeituanPoolDTO? GetOne()
+        {
+            var list = List();
+            if (!list.Any()) return null;
+            return list.Where(IsNotExceedDailyIncomeLimit).OrderBy(l => Guid.NewGuid()).FirstOrDefault();
+        }
+
+
+        public static MeituanPoolDTO? GetOne(int id)
+        {
+            var list = List();
+            if (!list.Any()) return null;
+            return list.Where(e => e.id == id && IsNotExceedDailyIncomeLimit(e)).FirstOrDefault();
+        }
+        private static bool IsNotExceedDailyIncomeLimit(MeituanPoolDTO item)
+        {
+            if (!string.IsNullOrEmpty(_end_point) && !string.IsNullOrEmpty(item.end_point))
+            {
+                if (item.end_point != _end_point) return false;
+            }
+
+            // 使用初始化参数创建工作时间表
+            if (!new WorkSchedule(item.time_range).IsWorkHour()) return false;
+
+            if (item.daily_calls_limit > 0)
+            {
+                int daily_num = RiskControlCore.GetCalls(TkChannelEnum.meituan, item.id, DateTime.Now.ToString("yyyyMMdd"));
+                if (daily_num >= item.daily_calls_limit) return false;
+            }
+            if (item.hourly_calls_limit > 0)
+            {
+                int hourly_num = RiskControlCore.GetCalls(TkChannelEnum.meituan, item.id, DateTime.Now.ToString("yyyyMMddHH"));
+                if (hourly_num >= item.hourly_calls_limit) return false;
+            }
+            return true;
+        }
+
+        public static IEnumerable<MeituanPoolDTO> List(bool force = false)
+        {
+            if (!force && _cached != null) return _cached;
+
+            string cache_key = $"cache:meituan_pool";
+            var list = RedisHelper.Get<IEnumerable<MeituanPoolDTO>>(cache_key);
+            if (force || list == null)
+            {
+                lock (_lockObj)
+                {
+                    list = new DBContext.Table("meituan_pool")
+                        .Where("status=@status", new { status = 1 })
+                        .Select<MeituanPoolDTO>();
+                    if (list == null) return default;
+                    foreach (var item in list)
+                    {
+                        RiskControlCore.SetCallsAsync(TkChannelEnum.meituan, item.id, DateTime.Now.ToString("yyyyMMddHH"), item.current_hourly_calls);
+                        RiskControlCore.SetCallsAsync(TkChannelEnum.meituan, item.id, DateTime.Now.ToString("yyyyMMdd"), item.current_daily_calls);
+                    }
+                    RedisHelper.Set(cache_key, list, 30 * 86400);
+                }
+            }
+            _cached = list;
+            return list;
+        }
+        public static void Refresh()
+        {
+            _ = List(true);
+        }
+
+        internal static void AccountExhausted()
+        {
+            string cache_key = $"cache:tk_pool:account:exhausted";
+            long count = RedisHelper.IncrBy(cache_key);
+            if (count > 1) return;
+            RedisHelper.Expire(cache_key, 3600);
+            NotifyCore.Notify(new NifyMessage
+            {
+                message = $"【美团联盟】没有匹配账号",
+                priority = NifyMessagePriority.high,
+                tags = ["red_circle"]
+            });
+            _ = NotifyCore.QYWeixinPushNotifyAsync("没账号", $"【美团联盟】没有匹配账号");
+        }
+
+    }
+
+
+}

+ 509 - 509
molilian.core/Core/cps/UnionCpsCore.cs

@@ -1,509 +1,509 @@
-using Microsoft.AspNetCore.Http;
-using Microsoft.AspNetCore.Mvc.Controllers;
-using Microsoft.AspNetCore.Mvc.Filters;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using dodohold.core;
-using Dataoke;
-using System.Text.RegularExpressions;
-using Org.BouncyCastle.Ocsp;
-using System.Diagnostics;
-using TencentCloud.Fmu.V20191213.Models;
-using Microsoft.VisualBasic;
-using System.Text.Json;
-using static System.Runtime.InteropServices.JavaScript.JSType;
-using System.Security.Cryptography;
-using Google.Protobuf.WellKnownTypes;
-using System.Threading.Channels;
-using TencentCloud.Tione.V20211111.Models;
-using COSXML.Network;
-
-
-namespace molilian.core
-{
-    public partial class UnionCpsCore
-    {
-        private static string _end_point;
-        static UnionCpsCore()
-        {
-            _end_point = Environment.GetEnvironmentVariable("EndPoint");
-        }
-
-        public static UnionCpsDTO GetFormattedObject(string channel, string ip = "", string oaid = "")
-        {
-            return new()
-            {
-                channel = channel,
-                end_point = _end_point,
-                ip = ip,
-                oaid = oaid
-            };
-        }
-
-        #region CPS 相关 
-        public static async Task<APIResult> UnionCpsAsync(string channelStr, string style, string ip, string oaid)
-        {
-            var channel_list = channelStr.Split(',');
-            List<dynamic> list = new List<dynamic>();
-            foreach (var channel in channel_list)
-            {
-                var result = await CpsCouponAsync(channel, style, ip, oaid);
-                list.Add(new
-                {
-                    result.success,
-                    result.message,
-                    result.title,
-                    result.sub_title,
-                    result.deeplink_url,
-                    result.token,
-                    result.channel,
-                });
-            }
-
-            return new APIResult(new
-            {
-                data = list,
-            });
-        }
-
-        public static async Task<UnionCpsDTO> CpsCouponAsync(string channel, string style, string ip, string oaid)
-        {
-            UnionCpsDTO result = GetFormattedObject(channel, ip, oaid);
-            if (string.IsNullOrEmpty(result.channel))
-            {
-                result.success = false;
-                result.message = "放弃转链";
-                result.reason = "无效渠道";
-                _ = TkLogCore.CpsLogAsync(result);
-                return result;
-            }
-
-            var config = TkConfigCore.Get();
-            //============================== 放弃转链-地区过滤 ==============================
-            if (CpsShouldIgnoreRequest(config, result.channel, ip, oaid, out string reason))
-            {
-                result.success = false;
-                result.message = "流量控制";
-                result.reason = reason;
-                _ = TkLogCore.CpsLogAsync(result);
-                return result;
-            }
-
-            var account = CpsPoolCore.GetOne(result.channel);
-            if (account == null)
-            {
-                result.success = false;
-                result.message = "放弃转链";
-                result.reason = "没有匹配账号";
-                _ = TkLogCore.CpsLogAsync(result);
-                return result;
-            }
-            result.accountId = account.id;
-            result.accountName = account.name;
-            result.deeplink_url = account.deeplink;
-            result.shortLinkurl = account.shortLinkurl;
-            result.token = account.token;
-            result.category = account.category;
-            result.pid = account.pid;
-            result.title = account.title;
-            var style_record = CpsPoolCore.GetTitle(account, style);
-            if (style_record != null)
-            {
-                result.title = style_record.title;
-                result.sub_title = style_record.sub_title;
-            }
-            result.success = true;
-            result.message = "OK";
-
-            _ = TkLogCore.CpsLogAsync(result);
-            return result;
-            //return new APIResult(new
-            //{
-            //    result.success,
-            //    result.message,
-            //    result.reason,
-            //    result.channel,
-            //});
-            //return new APIResult(new
-            //{
-            //    result.success,
-            //    result.message,
-            //    result.title,
-            //    result.deeplink_url,
-            //    result.token,
-            //    result.channel,
-            //});
-        }
-
-
-
-        public static async Task<APIResult> CpsCouponAsync22(string channel, string style, string ip, string oaid)
-        {
-            UnionCpsDTO result = GetFormattedObject(channel, ip, oaid);
-            if (string.IsNullOrEmpty(result.channel))
-            {
-                result.success = false;
-                result.message = "放弃转链";
-                result.reason = "无效渠道";
-                _ = TkLogCore.CpsLogAsync(result);
-                return new APIResult(new
-                {
-                    result.success,
-                    result.message,
-                    result.reason,
-                    channel,
-                });
-            }
-
-            var config = TkConfigCore.Get();
-            //============================== 放弃转链-地区过滤 ==============================
-            if (CpsShouldIgnoreRequest(config, result.channel, ip, oaid, out string reason))
-            {
-                result.success = false;
-                result.message = "流量控制";
-                result.reason = reason;
-                _ = TkLogCore.CpsLogAsync(result);
-                return new APIResult(new
-                {
-                    result.success,
-                    result.message,
-                    result.reason,
-                    result.channel,
-                });
-            }
-
-            var account = CpsPoolCore.GetOne(result.channel);
-            if (account == null)
-            {
-                result.success = false;
-                result.message = "放弃转链";
-                result.reason = "没有匹配账号";
-                _ = TkLogCore.CpsLogAsync(result);
-                return new APIResult(new
-                {
-                    result.success,
-                    result.message,
-                    result.reason,
-                    result.channel,
-                });
-            }
-            result.accountId = account.id;
-            result.accountName = account.name;
-            result.deeplink_url = account.deeplink;
-            result.token = account.token;
-            result.category = account.category;
-            result.pid = account.pid;
-            result.title = account.title;
-            var style_record = CpsPoolCore.GetTitle(account, style);
-            if (style_record != null)
-            {
-                result.title = style_record.title;
-                result.sub_title = style_record.sub_title;
-            }
-            result.success = true;
-            result.message = "OK";
-
-            _ = TkLogCore.CpsLogAsync(result);
-            return new APIResult(new
-            {
-                result.success,
-                result.message,
-                result.title,
-                result.sub_title,
-                result.deeplink_url,
-                result.token,
-                result.channel,
-            });
-        }
-
-
-        public static async Task<APIResult> TbtCouponAsync(string content, string channel, string ip, string oaid)
-        {
-            UnionCpsDTO result = GetFormattedObject(channel, ip, oaid);
-            if (string.IsNullOrEmpty(result.channel))
-            {
-                result.success = false;
-                result.message = "放弃转链";
-                result.reason = "无效渠道";
-                _ = TkLogCore.CpsLogAsync(result);
-                return new APIResult(new
-                {
-                    result.success,
-                    result.message,
-                    result.reason,
-                    channel,
-                });
-            }
-
-            AlimamaPlus alimama = null;
-            content = content.UrlDecode();
-
-            content = AlimamaPlus.FakeItemUrl(content, string.Empty);
-            result.rawContent = content;
-
-            if (string.IsNullOrEmpty(content))
-            {
-                result.success = false;
-                result.message = "放弃转链";
-                result.reason = "无效的参数";
-                result.rawContent = content;
-                _ = TkLogCore.CpsLogAsync(result);
-                return new APIResult(new
-                {
-                    result.success,
-                    result.reason,
-                    result.message,
-                    channel = result.channel.ToString(),
-                });
-            }
-
-            try
-            {
-                string reason;
-                if (AlimamaPlus.FilterRiskLink(ip, oaid, content, out reason) ||
-                    AlimamaPlus.ShouldIgnoreRequest(ip, oaid, string.Empty, 0, out reason))
-                {
-                    result.success = false;
-                    result.message = "放弃转链";
-                    result.reason = reason;
-                    _ = TkLogCore.CpsLogAsync(result);
-                    return new APIResult(new
-                    {
-                        result.success,
-                        result.message,
-                        result.reason,
-                        channel = result.channel.ToString(),
-                    });
-                }
-
-                var account = await TkPoolCore.GetOneAsync(TkPoolCore.TkAction.parse, false, string.Empty, 0);
-                if (account == null)
-                {
-                    result.success = false;
-                    result.message = "放弃转链";
-                    result.reason = "没有匹配账号";
-                    _ = TkLogCore.CpsLogAsync(result);
-                    return new APIResult(new
-                    {
-                        result.success,
-                        result.message,
-                        result.reason,
-                        channel = result.channel.ToString(),
-                    });
-                }
-                result.accountId = account.id;
-                result.accountName = account.name;
-                alimama = new AlimamaPlus(account);
-                //result = await alimama.UnionCoupon2Async(content, result);
-
-
-                TkDataDTO data = new();
-                data = await alimama.alimamaParseAsync(content, data);
-
-                result.success = data.success;
-                result.reason = data.reason;
-                result.message = data.success ? "OK" : data.message;
-
-                if (result.success)
-                {
-                    result.token = data.taoToken;
-                    //result.deeplink_url = data.deeplink_url;
-                    result.couponAmount = data.couponAmount;
-                    result.shortLinkurl = data.shortLinkurl;
-                    result.itemId = data.itemId;
-                }
-            }
-            catch (Exception ex)
-            {
-                if (ex.Message.Contains("was canceled"))
-                {
-                    result.success = false;
-                    result.message = "放弃转链";
-                    result.reason = "请求超时";
-                }
-                else
-                {
-                    _ = new LoggerLibrary("unionCoupon", "tb_error")
-                        .Info(ip, oaid)
-                        .Info(content)
-                        .Info(ex.Message, ex.StackTrace)
-                        .SaveAsync();
-
-                    NotifyCore.Notify(new NifyMessage
-                    {
-                        message = $"【转链异常TB】\n{content}\n\n{ex.Message}\n{ex.StackTrace}",
-                        priority = NifyMessagePriority.high,
-                        tags = ["red_circle"]
-                    });
-                    result.success = false;
-                    result.message = "放弃转链";
-                    result.reason = "转链接口异常";
-                }
-            }
-            _ = TkLogCore.CpsLogAsync(result);
-            return new APIResult(new
-            {
-                result.success,
-                result.message,
-                result.title,
-                result.sub_title,
-                result.deeplink_url,
-                result.token,
-                result.channel,
-            });
-        }
-
-        //public static async Task<APIResult> EleAsync(string ip, string oaid)
-        //{
-        //    UnionCpsDTO result = GetFormattedObject(CpsChannelEnum.eleme, ip, oaid);
-        //    var config = TkConfigCore.Get();
-
-        //    //============================== 放弃转链-地区过滤 ==============================
-        //    if (CpsShouldIgnoreRequest(config, result.channel, ip, oaid, out string reason))
-        //    {
-        //        result.success = false;
-        //        result.message = "流量控制";
-        //        result.reason = reason;
-        //        _ = TkLogCore.CpsLogAsync(result);
-        //        return new APIResult(new
-        //        {
-        //            result.success,
-        //            result.message,
-        //            result.reason,
-        //            channel = result.channel.ToString(),
-        //        });
-        //    }
-
-        //    var account = ElePoolCore.GetOne();
-        //    if (account == null)
-        //    {
-        //        result.success = false;
-        //        result.message = "放弃转链";
-        //        result.reason = "没有匹配账号";
-        //        _ = TkLogCore.CpsLogAsync(result);
-        //        return new APIResult(new
-        //        {
-        //            result.success,
-        //            result.message,
-        //            result.reason,
-        //            channel = result.channel.ToString(),
-        //        });
-        //    }
-        //    result.accountId = account.id;
-        //    result.accountName = account.name;
-        //    result.deeplink_url = account.deeplink;
-        //    result.success = true;
-        //    result.message = "OK";
-
-        //    _ = TkLogCore.CpsLogAsync(result);
-        //    return new APIResult(new
-        //    {
-        //        result.success,
-        //        result.message,
-        //        result.deeplink_url,
-        //        channel = result.channel.ToString(),
-        //    });
-        //}
-
-        //public static async Task<APIResult> MeituanAsync(string ip, string oaid)
-        //{
-        //    UnionCpsDTO result = GetFormattedObject(CpsChannelEnum.meituan, ip, oaid);
-        //    var config = TkConfigCore.Get();
-
-        //    //============================== 放弃转链-地区过滤 ==============================
-        //    if (CpsShouldIgnoreRequest(config, result.channel, ip, oaid, out string reason))
-        //    {
-        //        result.success = false;
-        //        result.message = "流量控制";
-        //        result.reason = reason;
-        //        _ = TkLogCore.CpsLogAsync(result);
-        //        return new APIResult(new
-        //        {
-        //            result.success,
-        //            result.message,
-        //            result.reason,
-        //            channel = result.channel.ToString(),
-        //        });
-        //    }
-
-        //    var account = MeituanPoolCore.GetOne();
-        //    if (account == null)
-        //    {
-        //        result.success = false;
-        //        result.message = "放弃转链";
-        //        result.reason = "没有匹配账号";
-        //        _ = TkLogCore.CpsLogAsync(result);
-        //        return new APIResult(new
-        //        {
-        //            result.success,
-        //            result.message,
-        //            result.reason,
-        //            channel = result.channel.ToString(),
-        //        });
-        //    }
-        //    result.accountId = account.id;
-        //    result.accountName = account.name;
-        //    result.deeplink_url = account.deeplink;
-        //    result.success = true;
-        //    result.message = "OK";
-
-        //    _ = TkLogCore.CpsLogAsync(result);
-        //    return new APIResult(new
-        //    {
-        //        result.success,
-        //        result.message,
-        //        result.deeplink_url,
-        //        channel = result.channel.ToString(),
-        //    });
-        //}
-
-        public static bool CpsShouldIgnoreRequest(TkConfigDTO config, string channel, string ip, string oaid, out string reason)
-        {
-            reason = string.Empty;
-            try
-            {
-                // IP和流量控制
-                string ignorePercentageCity = config.cpsIgnorePercentageCity;
-                string? ipInfo = IP2RegionPlus.Search(ip);
-                if (!TestParseCore.InWhitelist(ip, oaid) && AlimamaPlus.IgnoreRegionIncluded(ignorePercentageCity, ipInfo, out string regionInfo))
-                {
-                    reason = $"地区控制:{regionInfo}";
-                    return true;
-                }
-
-                int limit_num = config.cps_limit_per_ip_24h;
-                if (limit_num > 0)
-                {
-                    int num = TkLogCore.getClientRequestTotalByIp(channel, ip);
-                    if (num > limit_num)
-                    {
-                        reason = "IP控制";
-                        return true;
-                    }
-                }
-
-                limit_num = config.cps_limit_per_oaid_24h;
-                if (limit_num > 0)
-                {
-                    int num = TkLogCore.getClientRequestTotalByOAID(channel, oaid);
-                    if (num > limit_num)
-                    {
-                        reason = "OAID控制";
-                        return true;
-                    }
-                }
-            }
-            catch (Exception ex)
-            {
-                reason = "配置异常";
-            }
-            return false;
-        }
-        #endregion
-
-
-
-    }
-}
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc.Controllers;
+using Microsoft.AspNetCore.Mvc.Filters;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using dodohold.core;
+using Dataoke;
+using System.Text.RegularExpressions;
+using Org.BouncyCastle.Ocsp;
+using System.Diagnostics;
+using TencentCloud.Fmu.V20191213.Models;
+using Microsoft.VisualBasic;
+using System.Text.Json;
+using static System.Runtime.InteropServices.JavaScript.JSType;
+using System.Security.Cryptography;
+using Google.Protobuf.WellKnownTypes;
+using System.Threading.Channels;
+using TencentCloud.Tione.V20211111.Models;
+using COSXML.Network;
+
+
+namespace molilian.core
+{
+    public partial class UnionCpsCore
+    {
+        private static string _end_point;
+        static UnionCpsCore()
+        {
+            _end_point = Environment.GetEnvironmentVariable("EndPoint");
+        }
+
+        public static UnionCpsDTO GetFormattedObject(string channel, string ip = "", string oaid = "")
+        {
+            return new()
+            {
+                channel = channel,
+                end_point = _end_point,
+                ip = ip,
+                oaid = oaid
+            };
+        }
+
+        #region CPS 相关 
+        public static async Task<APIResult> UnionCpsAsync(string channelStr, string style, string ip, string oaid)
+        {
+            var channel_list = channelStr.Split(',');
+            List<dynamic> list = new List<dynamic>();
+            foreach (var channel in channel_list)
+            {
+                var result = await CpsCouponAsync(channel, style, ip, oaid);
+                list.Add(new
+                {
+                    result.success,
+                    result.message,
+                    result.title,
+                    result.sub_title,
+                    result.deeplink_url,
+                    result.token,
+                    result.channel,
+                });
+            }
+
+            return new APIResult(new
+            {
+                data = list,
+            });
+        }
+
+        public static async Task<UnionCpsDTO> CpsCouponAsync(string channel, string style, string ip, string oaid)
+        {
+            UnionCpsDTO result = GetFormattedObject(channel, ip, oaid);
+            if (string.IsNullOrEmpty(result.channel))
+            {
+                result.success = false;
+                result.message = "放弃转链";
+                result.reason = "无效渠道";
+                _ = TkLogCore.CpsLogAsync(result);
+                return result;
+            }
+
+            var config = TkConfigCore.Get();
+            //============================== 放弃转链-地区过滤 ==============================
+            if (CpsShouldIgnoreRequest(config, result.channel, ip, oaid, out string reason))
+            {
+                result.success = false;
+                result.message = "流量控制";
+                result.reason = reason;
+                _ = TkLogCore.CpsLogAsync(result);
+                return result;
+            }
+
+            var account = CpsPoolCore.GetOne(result.channel);
+            if (account == null)
+            {
+                result.success = false;
+                result.message = "放弃转链";
+                result.reason = "没有匹配账号";
+                _ = TkLogCore.CpsLogAsync(result);
+                return result;
+            }
+            result.accountId = account.id;
+            result.accountName = account.name;
+            result.deeplink_url = account.deeplink;
+            result.shortLinkurl = account.shortLinkurl;
+            result.token = account.token;
+            result.category = account.category;
+            result.pid = account.pid;
+            result.title = account.title;
+            var style_record = CpsPoolCore.GetTitle(account, style);
+            if (style_record != null)
+            {
+                result.title = style_record.title;
+                result.sub_title = style_record.sub_title;
+            }
+            result.success = true;
+            result.message = "OK";
+
+            _ = TkLogCore.CpsLogAsync(result);
+            return result;
+            //return new APIResult(new
+            //{
+            //    result.success,
+            //    result.message,
+            //    result.reason,
+            //    result.channel,
+            //});
+            //return new APIResult(new
+            //{
+            //    result.success,
+            //    result.message,
+            //    result.title,
+            //    result.deeplink_url,
+            //    result.token,
+            //    result.channel,
+            //});
+        }
+
+
+
+        public static async Task<APIResult> CpsCouponAsync22(string channel, string style, string ip, string oaid)
+        {
+            UnionCpsDTO result = GetFormattedObject(channel, ip, oaid);
+            if (string.IsNullOrEmpty(result.channel))
+            {
+                result.success = false;
+                result.message = "放弃转链";
+                result.reason = "无效渠道";
+                _ = TkLogCore.CpsLogAsync(result);
+                return new APIResult(new
+                {
+                    result.success,
+                    result.message,
+                    result.reason,
+                    channel,
+                });
+            }
+
+            var config = TkConfigCore.Get();
+            //============================== 放弃转链-地区过滤 ==============================
+            if (CpsShouldIgnoreRequest(config, result.channel, ip, oaid, out string reason))
+            {
+                result.success = false;
+                result.message = "流量控制";
+                result.reason = reason;
+                _ = TkLogCore.CpsLogAsync(result);
+                return new APIResult(new
+                {
+                    result.success,
+                    result.message,
+                    result.reason,
+                    result.channel,
+                });
+            }
+
+            var account = CpsPoolCore.GetOne(result.channel);
+            if (account == null)
+            {
+                result.success = false;
+                result.message = "放弃转链";
+                result.reason = "没有匹配账号";
+                _ = TkLogCore.CpsLogAsync(result);
+                return new APIResult(new
+                {
+                    result.success,
+                    result.message,
+                    result.reason,
+                    result.channel,
+                });
+            }
+            result.accountId = account.id;
+            result.accountName = account.name;
+            result.deeplink_url = account.deeplink;
+            result.token = account.token;
+            result.category = account.category;
+            result.pid = account.pid;
+            result.title = account.title;
+            var style_record = CpsPoolCore.GetTitle(account, style);
+            if (style_record != null)
+            {
+                result.title = style_record.title;
+                result.sub_title = style_record.sub_title;
+            }
+            result.success = true;
+            result.message = "OK";
+
+            _ = TkLogCore.CpsLogAsync(result);
+            return new APIResult(new
+            {
+                result.success,
+                result.message,
+                result.title,
+                result.sub_title,
+                result.deeplink_url,
+                result.token,
+                result.channel,
+            });
+        }
+
+
+        public static async Task<APIResult> TbtCouponAsync(string content, string channel, string ip, string oaid)
+        {
+            UnionCpsDTO result = GetFormattedObject(channel, ip, oaid);
+            if (string.IsNullOrEmpty(result.channel))
+            {
+                result.success = false;
+                result.message = "放弃转链";
+                result.reason = "无效渠道";
+                _ = TkLogCore.CpsLogAsync(result);
+                return new APIResult(new
+                {
+                    result.success,
+                    result.message,
+                    result.reason,
+                    channel,
+                });
+            }
+
+            AlimamaPlus alimama = null;
+            content = content.UrlDecode();
+
+            content = AlimamaPlus.FakeItemUrl(content, string.Empty);
+            result.rawContent = content;
+
+            if (string.IsNullOrEmpty(content))
+            {
+                result.success = false;
+                result.message = "放弃转链";
+                result.reason = "无效的参数";
+                result.rawContent = content;
+                _ = TkLogCore.CpsLogAsync(result);
+                return new APIResult(new
+                {
+                    result.success,
+                    result.reason,
+                    result.message,
+                    channel = result.channel.ToString(),
+                });
+            }
+
+            try
+            {
+                string reason;
+                if (AlimamaPlus.FilterRiskLink(ip, oaid, content, out reason) ||
+                    AlimamaPlus.ShouldIgnoreRequest(ip, oaid, string.Empty, 0, out reason))
+                {
+                    result.success = false;
+                    result.message = "放弃转链";
+                    result.reason = reason;
+                    _ = TkLogCore.CpsLogAsync(result);
+                    return new APIResult(new
+                    {
+                        result.success,
+                        result.message,
+                        result.reason,
+                        channel = result.channel.ToString(),
+                    });
+                }
+
+                var account = await TkPoolCore.GetOneAsync(TkPoolCore.TkAction.parse, false, string.Empty, 0);
+                if (account == null)
+                {
+                    result.success = false;
+                    result.message = "放弃转链";
+                    result.reason = "没有匹配账号";
+                    _ = TkLogCore.CpsLogAsync(result);
+                    return new APIResult(new
+                    {
+                        result.success,
+                        result.message,
+                        result.reason,
+                        channel = result.channel.ToString(),
+                    });
+                }
+                result.accountId = account.id;
+                result.accountName = account.name;
+                alimama = new AlimamaPlus(account);
+                //result = await alimama.UnionCoupon2Async(content, result);
+
+
+                TkDataDTO data = new();
+                data = await alimama.alimamaParseAsync(content, data);
+
+                result.success = data.success;
+                result.reason = data.reason;
+                result.message = data.success ? "OK" : data.message;
+
+                if (result.success)
+                {
+                    result.token = data.taoToken;
+                    //result.deeplink_url = data.deeplink_url;
+                    result.couponAmount = data.couponAmount;
+                    result.shortLinkurl = data.shortLinkurl;
+                    result.itemId = data.itemId;
+                }
+            }
+            catch (Exception ex)
+            {
+                if (ex.Message.Contains("was canceled"))
+                {
+                    result.success = false;
+                    result.message = "放弃转链";
+                    result.reason = "请求超时";
+                }
+                else
+                {
+                    _ = new LoggerLibrary("unionCoupon", "tb_error")
+                        .Info(ip, oaid)
+                        .Info(content)
+                        .Info(ex.Message, ex.StackTrace)
+                        .SaveAsync();
+
+                    NotifyCore.Notify(new NifyMessage
+                    {
+                        message = $"【转链异常TB】\n{content}\n\n{ex.Message}\n{ex.StackTrace}",
+                        priority = NifyMessagePriority.high,
+                        tags = ["red_circle"]
+                    });
+                    result.success = false;
+                    result.message = "放弃转链";
+                    result.reason = "转链接口异常";
+                }
+            }
+            _ = TkLogCore.CpsLogAsync(result);
+            return new APIResult(new
+            {
+                result.success,
+                result.message,
+                result.title,
+                result.sub_title,
+                result.deeplink_url,
+                result.token,
+                result.channel,
+            });
+        }
+
+        //public static async Task<APIResult> EleAsync(string ip, string oaid)
+        //{
+        //    UnionCpsDTO result = GetFormattedObject(CpsChannelEnum.eleme, ip, oaid);
+        //    var config = TkConfigCore.Get();
+
+        //    //============================== 放弃转链-地区过滤 ==============================
+        //    if (CpsShouldIgnoreRequest(config, result.channel, ip, oaid, out string reason))
+        //    {
+        //        result.success = false;
+        //        result.message = "流量控制";
+        //        result.reason = reason;
+        //        _ = TkLogCore.CpsLogAsync(result);
+        //        return new APIResult(new
+        //        {
+        //            result.success,
+        //            result.message,
+        //            result.reason,
+        //            channel = result.channel.ToString(),
+        //        });
+        //    }
+
+        //    var account = ElePoolCore.GetOne();
+        //    if (account == null)
+        //    {
+        //        result.success = false;
+        //        result.message = "放弃转链";
+        //        result.reason = "没有匹配账号";
+        //        _ = TkLogCore.CpsLogAsync(result);
+        //        return new APIResult(new
+        //        {
+        //            result.success,
+        //            result.message,
+        //            result.reason,
+        //            channel = result.channel.ToString(),
+        //        });
+        //    }
+        //    result.accountId = account.id;
+        //    result.accountName = account.name;
+        //    result.deeplink_url = account.deeplink;
+        //    result.success = true;
+        //    result.message = "OK";
+
+        //    _ = TkLogCore.CpsLogAsync(result);
+        //    return new APIResult(new
+        //    {
+        //        result.success,
+        //        result.message,
+        //        result.deeplink_url,
+        //        channel = result.channel.ToString(),
+        //    });
+        //}
+
+        //public static async Task<APIResult> MeituanAsync(string ip, string oaid)
+        //{
+        //    UnionCpsDTO result = GetFormattedObject(CpsChannelEnum.meituan, ip, oaid);
+        //    var config = TkConfigCore.Get();
+
+        //    //============================== 放弃转链-地区过滤 ==============================
+        //    if (CpsShouldIgnoreRequest(config, result.channel, ip, oaid, out string reason))
+        //    {
+        //        result.success = false;
+        //        result.message = "流量控制";
+        //        result.reason = reason;
+        //        _ = TkLogCore.CpsLogAsync(result);
+        //        return new APIResult(new
+        //        {
+        //            result.success,
+        //            result.message,
+        //            result.reason,
+        //            channel = result.channel.ToString(),
+        //        });
+        //    }
+
+        //    var account = MeituanPoolCore.GetOne();
+        //    if (account == null)
+        //    {
+        //        result.success = false;
+        //        result.message = "放弃转链";
+        //        result.reason = "没有匹配账号";
+        //        _ = TkLogCore.CpsLogAsync(result);
+        //        return new APIResult(new
+        //        {
+        //            result.success,
+        //            result.message,
+        //            result.reason,
+        //            channel = result.channel.ToString(),
+        //        });
+        //    }
+        //    result.accountId = account.id;
+        //    result.accountName = account.name;
+        //    result.deeplink_url = account.deeplink;
+        //    result.success = true;
+        //    result.message = "OK";
+
+        //    _ = TkLogCore.CpsLogAsync(result);
+        //    return new APIResult(new
+        //    {
+        //        result.success,
+        //        result.message,
+        //        result.deeplink_url,
+        //        channel = result.channel.ToString(),
+        //    });
+        //}
+
+        public static bool CpsShouldIgnoreRequest(TkConfigDTO config, string channel, string ip, string oaid, out string reason)
+        {
+            reason = string.Empty;
+            try
+            {
+                // IP和流量控制
+                string ignorePercentageCity = config.cpsIgnorePercentageCity;
+                string? ipInfo = IP2RegionPlus.Search(ip);
+                if (!TestParseCore.InWhitelist(ip, oaid) && AlimamaPlus.IgnoreRegionIncluded(ignorePercentageCity, ipInfo, out string regionInfo))
+                {
+                    reason = $"地区控制:{regionInfo}";
+                    return true;
+                }
+
+                int limit_num = config.cps_limit_per_ip_24h;
+                if (limit_num > 0)
+                {
+                    int num = TkLogCore.getClientRequestTotalByIp(channel, ip);
+                    if (num > limit_num)
+                    {
+                        reason = "IP控制";
+                        return true;
+                    }
+                }
+
+                limit_num = config.cps_limit_per_oaid_24h;
+                if (limit_num > 0)
+                {
+                    int num = TkLogCore.getClientRequestTotalByOAID(channel, oaid);
+                    if (num > limit_num)
+                    {
+                        reason = "OAID控制";
+                        return true;
+                    }
+                }
+            }
+            catch (Exception ex)
+            {
+                reason = "配置异常";
+            }
+            return false;
+        }
+        #endregion
+
+
+
+    }
+}

+ 180 - 180
molilian.core/Core/dataoke/DataokeCore.cs

@@ -1,180 +1,180 @@
-using Microsoft.AspNetCore.Http;
-using Microsoft.AspNetCore.Mvc.Controllers;
-using Microsoft.AspNetCore.Mvc.Filters;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using dodohold.core;
-using Dataoke;
-using Google.Protobuf.WellKnownTypes;
-using System.Diagnostics;
-using System.Text.Json;
-using TencentCloud.Tcm.V20210413.Models;
-
-
-namespace molilian.core
-{
-    public partial class DataokeCore
-    {
-        private static readonly object _lockObj = new();
-        private static IEnumerable<DataokeDTO> _cached;
-        public static DataokeDTO? GetOne(string domain = "")
-        {
-            var list = List();
-            if (!list.Any()) return null;
-            domain = domain.ToLower();
-            return list.Where(e => FilterReferer(e, domain)).OrderBy(l => Guid.NewGuid()).FirstOrDefault();
-        }
-
-        public static IEnumerable<DataokeDTO> List(bool force = false)
-        {
-            if (!force && _cached != null) return _cached;
-
-            string cache_key = $"cache:dataoke_pool";
-            var list = RedisHelper.Get<IEnumerable<DataokeDTO>>(cache_key);
-            if (force || list == null)
-            {
-                lock (_lockObj)
-                {
-                    list = new DBContext.Table("dataoke_pool")
-                        .Where("status=@status", new { status = 1 })
-                        .Select<DataokeDTO>();
-                    if (list == null) return default;
-                    RedisHelper.Set(cache_key, list, 30 * 86400);
-                }
-            }
-            _cached = list;
-            return list;
-        }
-
-
-        private static bool FilterReferer(DataokeDTO item, string domain)
-        {
-            if (string.IsNullOrEmpty(domain)) return true;
-            if (string.IsNullOrEmpty(item.domain)) return true;
-
-            if (domain.Contains(item.domain)) return true;
-            return false;
-        }
-
-
-
-        public static void Refresh()
-        {
-            _ = List(true);
-        }
-
-        public static void Disabled(string name)
-        {
-            string cache_key = $"cache:dataoke_pool:{name}:disabled";
-            long count = RedisHelper.IncrBy(cache_key);
-            RedisHelper.Expire(cache_key, 10);
-            if (count > 1) return;
-
-            new DBContext.Table("dataoke_pool")
-                .Add("status", 0)
-                .Where("name=@name", new { name })
-                .Update();
-            _ = List(true);
-
-            NotifyCore.Notify(new NifyMessage
-            {
-                message = $"【大淘客:{name}】调用异常",
-                priority = NifyMessagePriority.high,
-                tags = ["red_circle"]
-            });
-        }
-
-
-        public static JdDataDTO JdParse(JdDataDTO result, DataokeDTO api, JdPoolDTO account, string content)
-        {
-            string message = string.Empty;
-            string url = JdUnionPlus.GetLink(content);
-            string shortLinkurl = url;
-
-            var plus = new DataokePlus(api.app_key, api.app_secret);
-            string body = plus.JDParse(account.unionid, url);
-
-            //result.link_type = plus.IsAffLlink(url) ? LinkTypeEnum.other_aff : LinkTypeEnum.goods;
-            result.link_type = JdUnionPlus.GetLinkType(url, out string out_url, out string itemId);
-            url = out_url;
-            result.itemId = itemId;
-
-            if (result.link_type == LinkTypeEnum.other_aff)
-            {
-                result.success = false;
-                result.link_type = LinkTypeEnum.unknown;
-                result.channel_type = ChannelTypeEnum.jd;
-                message = "其他推广链接";
-                result.accountName = string.Empty;
-                result.content = content;
-                result.shortLinkurl = url;
-                result.deeplink_url = JdUnionPlus.GetDefaultStyleDeeplink(shortLinkurl);
-            }
-            else
-            {
-                JsonElement root;
-                try
-                {
-                    root = body.Convert2JsonElement();
-                }
-                catch
-                {
-                    throw new APIException(body);
-                }
-                int code = root.Read("code", -1);
-                bool success = code == 0;
-                if (success)
-                {
-                    shortLinkurl = root.PathRead("data.shortUrl", string.Empty);
-                }
-                else
-                {
-                    message = body;
-                }
-
-                result.content = shortLinkurl;
-                result.shortLinkurl = shortLinkurl;
-                result.success = success;
-                result.message = message;
-                result.accountName = account.name;
-
-                GoodsDetailDTO goods = null;
-                if (success) goods = plus.GetGoods(shortLinkurl);
-                result.deeplink_url = JdUnionPlus.GetDefaultStyleDeeplink(shortLinkurl);
-
-                if (goods != null)
-                {
-                    result.itemName = goods.skuName;
-                }
-            }
-
-            return result;
-        }
-
-        public static GoodsDetailDTO? GetCoupon(DataokeDTO api, JdPoolDTO account, string url, string itemId)
-        {
-            var plus = new DataokePlus(api.app_key, api.app_secret);
-            var item = plus.GetDetails([itemId]);
-            if (item != null)
-            {
-                try
-                {
-                    string body = plus.JDParse(account.unionid, url, item.couponLink);
-                    JsonElement root = body.Convert2JsonElement();
-                    int code = root.Read("code", -1);
-                    if (code != 0) return null;
-
-                    item.couponLink = root.PathRead("data.shortUrl", string.Empty);
-                }
-                catch
-                {
-                    return null;
-                }
-            }
-            return item;
-        }
-
-    }
-}
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc.Controllers;
+using Microsoft.AspNetCore.Mvc.Filters;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using dodohold.core;
+using Dataoke;
+using Google.Protobuf.WellKnownTypes;
+using System.Diagnostics;
+using System.Text.Json;
+using TencentCloud.Tcm.V20210413.Models;
+
+
+namespace molilian.core
+{
+    public partial class DataokeCore
+    {
+        private static readonly object _lockObj = new();
+        private static IEnumerable<DataokeDTO> _cached;
+        public static DataokeDTO? GetOne(string domain = "")
+        {
+            var list = List();
+            if (!list.Any()) return null;
+            domain = domain.ToLower();
+            return list.Where(e => FilterReferer(e, domain)).OrderBy(l => Guid.NewGuid()).FirstOrDefault();
+        }
+
+        public static IEnumerable<DataokeDTO> List(bool force = false)
+        {
+            if (!force && _cached != null) return _cached;
+
+            string cache_key = $"cache:dataoke_pool";
+            var list = RedisHelper.Get<IEnumerable<DataokeDTO>>(cache_key);
+            if (force || list == null)
+            {
+                lock (_lockObj)
+                {
+                    list = new DBContext.Table("dataoke_pool")
+                        .Where("status=@status", new { status = 1 })
+                        .Select<DataokeDTO>();
+                    if (list == null) return default;
+                    RedisHelper.Set(cache_key, list, 30 * 86400);
+                }
+            }
+            _cached = list;
+            return list;
+        }
+
+
+        private static bool FilterReferer(DataokeDTO item, string domain)
+        {
+            if (string.IsNullOrEmpty(domain)) return true;
+            if (string.IsNullOrEmpty(item.domain)) return true;
+
+            if (domain.Contains(item.domain)) return true;
+            return false;
+        }
+
+
+
+        public static void Refresh()
+        {
+            _ = List(true);
+        }
+
+        public static void Disabled(string name)
+        {
+            string cache_key = $"cache:dataoke_pool:{name}:disabled";
+            long count = RedisHelper.IncrBy(cache_key);
+            RedisHelper.Expire(cache_key, 10);
+            if (count > 1) return;
+
+            new DBContext.Table("dataoke_pool")
+                .Add("status", 0)
+                .Where("name=@name", new { name })
+                .Update();
+            _ = List(true);
+
+            NotifyCore.Notify(new NifyMessage
+            {
+                message = $"【大淘客:{name}】调用异常",
+                priority = NifyMessagePriority.high,
+                tags = ["red_circle"]
+            });
+        }
+
+
+        public static JdDataDTO JdParse(JdDataDTO result, DataokeDTO api, JdPoolDTO account, string content)
+        {
+            string message = string.Empty;
+            string url = JdUnionPlus.GetLink(content);
+            string shortLinkurl = url;
+
+            var plus = new DataokePlus(api.app_key, api.app_secret);
+            string body = plus.JDParse(account.unionid, url);
+
+            //result.link_type = plus.IsAffLlink(url) ? LinkTypeEnum.other_aff : LinkTypeEnum.goods;
+            result.link_type = JdUnionPlus.GetLinkType(url, out string out_url, out string itemId);
+            url = out_url;
+            result.itemId = itemId;
+
+            if (result.link_type == LinkTypeEnum.other_aff)
+            {
+                result.success = false;
+                result.link_type = LinkTypeEnum.unknown;
+                result.channel_type = ChannelTypeEnum.jd;
+                message = "其他推广链接";
+                result.accountName = string.Empty;
+                result.content = content;
+                result.shortLinkurl = url;
+                result.deeplink_url = JdUnionPlus.GetDefaultStyleDeeplink(shortLinkurl);
+            }
+            else
+            {
+                JsonElement root;
+                try
+                {
+                    root = body.Convert2JsonElement();
+                }
+                catch
+                {
+                    throw new APIException(body);
+                }
+                int code = root.Read("code", -1);
+                bool success = code == 0;
+                if (success)
+                {
+                    shortLinkurl = root.PathRead("data.shortUrl", string.Empty);
+                }
+                else
+                {
+                    message = body;
+                }
+
+                result.content = shortLinkurl;
+                result.shortLinkurl = shortLinkurl;
+                result.success = success;
+                result.message = message;
+                result.accountName = account.name;
+
+                GoodsDetailDTO goods = null;
+                if (success) goods = plus.GetGoods(shortLinkurl);
+                result.deeplink_url = JdUnionPlus.GetDefaultStyleDeeplink(shortLinkurl);
+
+                if (goods != null)
+                {
+                    result.itemName = goods.skuName;
+                }
+            }
+
+            return result;
+        }
+
+        public static GoodsDetailDTO? GetCoupon(DataokeDTO api, JdPoolDTO account, string url, string itemId)
+        {
+            var plus = new DataokePlus(api.app_key, api.app_secret);
+            var item = plus.GetDetails([itemId]);
+            if (item != null)
+            {
+                try
+                {
+                    string body = plus.JDParse(account.unionid, url, item.couponLink);
+                    JsonElement root = body.Convert2JsonElement();
+                    int code = root.Read("code", -1);
+                    if (code != 0) return null;
+
+                    item.couponLink = root.PathRead("data.shortUrl", string.Empty);
+                }
+                catch
+                {
+                    return null;
+                }
+            }
+            return item;
+        }
+
+    }
+}

+ 76 - 76
molilian.core/Core/douyin/PangolinPoolCore.cs

@@ -1,76 +1,76 @@
-using Microsoft.AspNetCore.Http;
-using Microsoft.AspNetCore.Mvc.Controllers;
-using Microsoft.AspNetCore.Mvc.Filters;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using dodohold.core;
-using Dataoke;
-using Google.Protobuf.WellKnownTypes;
-using System.Diagnostics;
-using System.Text.Json;
-using TencentCloud.Tcm.V20210413.Models;
-
-
-namespace molilian.core
-{
-    public partial class PangolinPoolCore
-    {
-        private static readonly object _lockObj = new();
-        private static IEnumerable<PangolinDTO> _cached;
-        public static PangolinDTO? GetOne()
-        {
-            var list = List();
-            if (!list.Any()) return null;
-            return list.OrderBy(l => Guid.NewGuid()).FirstOrDefault();
-        }
-
-        public static IEnumerable<PangolinDTO> List(bool force = false)
-        {
-            if (!force && _cached != null) return _cached;
-
-            string cache_key = $"cache:pangolin_pool";
-            var list = RedisHelper.Get<IEnumerable<PangolinDTO>>(cache_key);
-            if (force || list == null)
-            {
-                lock (_lockObj)
-                {
-                    list = new DBContext.Table("pangolin_pool")
-                        .Where("status=@status", new { status = 1 })
-                        .Select<PangolinDTO>();
-                    if (list == null) return default;
-                    RedisHelper.Set(cache_key, list, 30 * 86400);
-                }
-            }
-            _cached = list;
-            return list;
-        }
-        public static void Refresh()
-        {
-            _ = List(true);
-        }
-
-        public static void Disabled(string name)
-        {
-            string cache_key = $"cache:pangolin_pool:{name}:disabled";
-            long count = RedisHelper.IncrBy(cache_key);
-            RedisHelper.Expire(cache_key, 10);
-            if (count > 1) return;
-
-            new DBContext.Table("pangolin_pool")
-                .Add("status", 0)
-                .Where("name=@name", new { name })
-                .Update();
-            _ = List(true);
-
-            NotifyCore.Notify(new NifyMessage
-            {
-                message = $"【穿山甲:{name}】调用异常",
-                priority = NifyMessagePriority.high,
-                tags = ["red_circle"]
-            });
-        }
-
-    }
-}
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc.Controllers;
+using Microsoft.AspNetCore.Mvc.Filters;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using dodohold.core;
+using Dataoke;
+using Google.Protobuf.WellKnownTypes;
+using System.Diagnostics;
+using System.Text.Json;
+using TencentCloud.Tcm.V20210413.Models;
+
+
+namespace molilian.core
+{
+    public partial class PangolinPoolCore
+    {
+        private static readonly object _lockObj = new();
+        private static IEnumerable<PangolinDTO> _cached;
+        public static PangolinDTO? GetOne()
+        {
+            var list = List();
+            if (!list.Any()) return null;
+            return list.OrderBy(l => Guid.NewGuid()).FirstOrDefault();
+        }
+
+        public static IEnumerable<PangolinDTO> List(bool force = false)
+        {
+            if (!force && _cached != null) return _cached;
+
+            string cache_key = $"cache:pangolin_pool";
+            var list = RedisHelper.Get<IEnumerable<PangolinDTO>>(cache_key);
+            if (force || list == null)
+            {
+                lock (_lockObj)
+                {
+                    list = new DBContext.Table("pangolin_pool")
+                        .Where("status=@status", new { status = 1 })
+                        .Select<PangolinDTO>();
+                    if (list == null) return default;
+                    RedisHelper.Set(cache_key, list, 30 * 86400);
+                }
+            }
+            _cached = list;
+            return list;
+        }
+        public static void Refresh()
+        {
+            _ = List(true);
+        }
+
+        public static void Disabled(string name)
+        {
+            string cache_key = $"cache:pangolin_pool:{name}:disabled";
+            long count = RedisHelper.IncrBy(cache_key);
+            RedisHelper.Expire(cache_key, 10);
+            if (count > 1) return;
+
+            new DBContext.Table("pangolin_pool")
+                .Add("status", 0)
+                .Where("name=@name", new { name })
+                .Update();
+            _ = List(true);
+
+            NotifyCore.Notify(new NifyMessage
+            {
+                message = $"【穿山甲:{name}】调用异常",
+                priority = NifyMessagePriority.high,
+                tags = ["red_circle"]
+            });
+        }
+
+    }
+}

+ 76 - 76
molilian.core/Core/douyin/ReduPoolCore.cs

@@ -1,76 +1,76 @@
-using Microsoft.AspNetCore.Http;
-using Microsoft.AspNetCore.Mvc.Controllers;
-using Microsoft.AspNetCore.Mvc.Filters;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using dodohold.core;
-using Dataoke;
-using Google.Protobuf.WellKnownTypes;
-using System.Diagnostics;
-using System.Text.Json;
-using TencentCloud.Tcm.V20210413.Models;
-
-
-namespace molilian.core
-{
-    public partial class ReduPoolCore
-    {
-        private static readonly object _lockObj = new();
-        private static IEnumerable<ReduPoolDTO> _cached;
-        public static ReduPoolDTO? GetOne()
-        {
-            var list = List();
-            if (!list.Any()) return null;
-            return list.OrderBy(l => Guid.NewGuid()).FirstOrDefault();
-        }
-
-        public static IEnumerable<ReduPoolDTO> List(bool force = false)
-        {
-            if (!force && _cached != null) return _cached;
-
-            string cache_key = $"cache:redu_pool";
-            var list = RedisHelper.Get<IEnumerable<ReduPoolDTO>>(cache_key);
-            if (force || list == null)
-            {
-                lock (_lockObj)
-                {
-                    list = new DBContext.Table("redu_pool")
-                        .Where("status=@status", new { status = 1 })
-                        .Select<ReduPoolDTO>();
-                    if (list == null) return default;
-                    RedisHelper.Set(cache_key, list, 30 * 86400);
-                }
-            }
-            _cached = list;
-            return list;
-        }
-        public static void Refresh()
-        {
-            _ = List(true);
-        }
-
-        public static void Disabled(string name)
-        {
-            string cache_key = $"cache:redu_pool:{name}:disabled";
-            long count = RedisHelper.IncrBy(cache_key);
-            RedisHelper.Expire(cache_key, 10);
-            if (count > 1) return;
-
-            new DBContext.Table("redu_pool")
-                .Add("status", 0)
-                .Where("name=@name", new { name })
-                .Update();
-            _ = List(true);
-
-            NotifyCore.Notify(new NifyMessage
-            {
-                message = $"【热度:{name}】调用异常",
-                priority = NifyMessagePriority.high,
-                tags = ["red_circle"]
-            });
-        }
-
-    }
-}
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc.Controllers;
+using Microsoft.AspNetCore.Mvc.Filters;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using dodohold.core;
+using Dataoke;
+using Google.Protobuf.WellKnownTypes;
+using System.Diagnostics;
+using System.Text.Json;
+using TencentCloud.Tcm.V20210413.Models;
+
+
+namespace molilian.core
+{
+    public partial class ReduPoolCore
+    {
+        private static readonly object _lockObj = new();
+        private static IEnumerable<ReduPoolDTO> _cached;
+        public static ReduPoolDTO? GetOne()
+        {
+            var list = List();
+            if (!list.Any()) return null;
+            return list.OrderBy(l => Guid.NewGuid()).FirstOrDefault();
+        }
+
+        public static IEnumerable<ReduPoolDTO> List(bool force = false)
+        {
+            if (!force && _cached != null) return _cached;
+
+            string cache_key = $"cache:redu_pool";
+            var list = RedisHelper.Get<IEnumerable<ReduPoolDTO>>(cache_key);
+            if (force || list == null)
+            {
+                lock (_lockObj)
+                {
+                    list = new DBContext.Table("redu_pool")
+                        .Where("status=@status", new { status = 1 })
+                        .Select<ReduPoolDTO>();
+                    if (list == null) return default;
+                    RedisHelper.Set(cache_key, list, 30 * 86400);
+                }
+            }
+            _cached = list;
+            return list;
+        }
+        public static void Refresh()
+        {
+            _ = List(true);
+        }
+
+        public static void Disabled(string name)
+        {
+            string cache_key = $"cache:redu_pool:{name}:disabled";
+            long count = RedisHelper.IncrBy(cache_key);
+            RedisHelper.Expire(cache_key, 10);
+            if (count > 1) return;
+
+            new DBContext.Table("redu_pool")
+                .Add("status", 0)
+                .Where("name=@name", new { name })
+                .Update();
+            _ = List(true);
+
+            NotifyCore.Notify(new NifyMessage
+            {
+                message = $"【热度:{name}】调用异常",
+                priority = NifyMessagePriority.high,
+                tags = ["red_circle"]
+            });
+        }
+
+    }
+}

+ 404 - 404
molilian.core/Core/jd/JdPoolCore.cs

@@ -1,404 +1,404 @@
-using Microsoft.AspNetCore.Http;
-using Microsoft.AspNetCore.Mvc.Controllers;
-using Microsoft.AspNetCore.Mvc.Filters;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using dodohold.core;
-using Dataoke;
-using Google.Protobuf.WellKnownTypes;
-using System.Xml.Linq;
-using Mysqlx.Crud;
-using ZstdSharp.Unsafe;
-using YunhuiKit;
-
-
-namespace molilian.core
-{
-
-    public partial class JdPoolCore
-    {
-        public enum JdAction
-        {
-            all,
-            api,
-            parse,
-            coupon,
-            promotionQuery
-        }
-        private static string _end_point;
-        private static Dictionary<string, decimal> _incomeAmt = new();
-        static JdPoolCore()
-        {
-            _end_point = Environment.GetEnvironmentVariable("EndPoint");
-        }
-
-        private static readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
-
-        private static IEnumerable<JdPoolDTO> _cached;
-        private static IEnumerable<JdPoolDTO> _all_cached;
-        public static async Task<JdPoolDTO?> GetOneAsync()
-        {
-            var list = await ListAsync();
-            if (!list.Any()) return null;
-            return list.OrderBy(l => Guid.NewGuid()).FirstOrDefault();
-        }
-
-        public static async Task<JdPoolDTO?> GetSpecialOneAsync(int accountid)
-        {
-            var list = await AllListAsync();
-            if (!list.Any()) return null;
-            return list.Where(e => e.id == accountid).OrderBy(l => Guid.NewGuid()).FirstOrDefault();
-        }
-
-
-        public static async Task<JdPoolDTO?> GetOneAsync(int id, JdAction action)
-        {
-            var list = await ListAsync();
-            if (!list.Any()) return null;
-
-            var eligibleItems = new List<JdPoolDTO>();
-            foreach (var item in list.Where(e => e.id == id))
-            {
-                if (await IsNotExceedDailyIncomeLimitAsync(item, action))
-                {
-                    eligibleItems.Add(item);
-                }
-            }
-
-            return eligibleItems.OrderBy(l => Guid.NewGuid()).FirstOrDefault();
-        }
-
-        public static async Task<JdPoolDTO?> GetOneAsync(JdAction action, bool is_numeric, UnionParseRequest request)
-        {
-            var list = await ListAsync();
-            if (!list.Any()) return null;
-
-            if (!string.IsNullOrEmpty(request.Type))
-            {
-                list = list.Where(item => request.Type.Equals(item.parse_type)).ToList();
-                if (!list.Any()) return null;
-            }
-            else
-            {
-                if (!string.IsNullOrEmpty(request.RiskStrategy) && !"os".Equals(request.RiskStrategy) && request.LaunchScene != -1)
-                {
-                    list = list.Where(item => string.IsNullOrEmpty(item.parse_type) && request.RiskStrategy.Equals(item.riskStrategy) && item.launchScene == request.LaunchScene).ToList();
-                    if (!list.Any()) return null;
-                }
-                else
-                {
-                    list = list.Where(item => string.IsNullOrEmpty(item.parse_type) && string.IsNullOrEmpty(item.riskStrategy)).ToList();
-                    if (!list.Any()) return null;
-                }
-
-            }
-
-            var eligibleItems = new List<JdPoolDTO>();
-            foreach (var item in list)
-            {
-                if (await IsNotExceedDailyIncomeLimitAsync(item, action))
-                {
-                    // 如果 is_numeric 为 true,则排除 work_mode = Ddx (4) 的记录
-                    if (is_numeric == true && item.work_mode == JdUnionWorkMode.Ddx)
-                        continue;
-                    eligibleItems.Add(item);
-                }
-            }
-
-            return eligibleItems.OrderBy(l => Guid.NewGuid()).FirstOrDefault();
-        }
-
-
-        private static async Task<bool> IsNotExceedDailyIncomeLimitAsync(JdPoolDTO item, JdAction action)
-        {
-            if (!string.IsNullOrEmpty(_end_point) && !string.IsNullOrEmpty(item.end_point))
-            {
-                if (item.end_point != _end_point) return false;
-            }
-            switch (action)
-            {
-                case JdAction.api:
-                    if (item.work_mode != JdUnionWorkMode.SiteApi) return false;
-                    break;
-                case JdAction.parse:
-                    if (!item.enable_parse) return false;
-                    break;
-                case JdAction.coupon:
-                    if (!item.enable_coupon) return false;
-                    break;
-            }
-
-            // 使用初始化参数创建工作时间表
-            if (!new WorkSchedule(item.time_range).IsWorkHour()) return false;
-
-            if (item.daily_calls_limit > 0)
-            {
-                int daily_num = RiskControlCore.GetCalls(TkChannelEnum.jd, item.id, DateTime.Now.ToString("yyyyMMdd"));
-                if (daily_num >= item.daily_calls_limit) return false;
-            }
-            if (item.hourly_calls_limit > 0)
-            {
-                int hourly_num = RiskControlCore.GetCalls(TkChannelEnum.jd, item.id, DateTime.Now.ToString("yyyyMMddHH"));
-                if (hourly_num >= item.hourly_calls_limit) return false;
-            }
-            return true;
-        }
-
-
-        public static async Task<IEnumerable<JdPoolDTO>> AllListAsync(bool force = false)
-        {
-            if (!force && _all_cached != default) return _all_cached;
-
-            try
-            {
-                await _semaphore.WaitAsync();
-
-                string cache_key = $"cache:all_jd_pool";
-                var list = await RedisKit.GetAsync<IEnumerable<JdPoolDTO>>(cache_key);
-
-                if (force || list == default)
-                {
-                    list = new DBContext.Table("jd_pool").Select<JdPoolDTO>();
-                    if (list == null) return default;
-
-                    // 等待 Redis 写入完成
-                    await RedisKit.SetAsync(cache_key, list, 30 * 86400);
-                }
-
-                _all_cached = list;
-                return list;
-            }
-            catch (Exception)
-            {
-                // 发生异常时返回上一次的缓存,如果没有则返回默认值
-                return _all_cached ?? default;
-            }
-            finally
-            {
-                _semaphore.Release();
-            }
-        }
-
-        public static async Task<IEnumerable<JdPoolDTO>> ListAsync(bool force = false)
-        {
-            if (!force && _cached != null) return _cached;
-            try
-            {
-                await _semaphore.WaitAsync();
-
-                // 如果缓存存在且未强制刷新,直接返回
-                if (!force && _cached != null) return _cached;
-
-                string cache_key = $"cache:jd_pool";
-                IEnumerable<JdPoolDTO>? list = null;
-
-                // 尝试从Redis获取数据
-                try
-                {
-                    list = await RedisKit.GetAsync<IEnumerable<JdPoolDTO>>(cache_key);
-                }
-                catch (Exception ex)
-                {
-                    // 记录Redis错误
-                    _ = new LoggerLibrary("JdPool", "Redis").Info(ex.Message, ex.StackTrace).SaveAsync();
-                }
-
-                // 如果Redis获取失败或需要强制刷新
-                if (force || list == null)
-                {
-                    try
-                    {
-                        list = new DBContext.Table("jd_pool")
-                            .Where("status=@status", new { status = 1 })
-                            .Select<JdPoolDTO>();
-
-                        if (list != null && list.Any())
-                        {
-                            // 更新计数器
-                            foreach (var item in list)
-                            {
-                                try
-                                {
-                                    await RiskControlCore.SetCallsAsync(TkChannelEnum.jd, item.id,
-                                        DateTime.Now.ToString("yyyyMMddHH"), item.current_hourly_calls);
-                                    await RiskControlCore.SetCallsAsync(TkChannelEnum.jd, item.id,
-                                        DateTime.Now.ToString("yyyyMMdd"), item.current_daily_calls);
-                                }
-                                catch (Exception ex)
-                                {
-                                    // 记录计数器更新错误
-                                    _ = new LoggerLibrary("JdPool", "Counter").Info(ex.Message, ex.StackTrace).SaveAsync();
-                                }
-                            }
-
-                            // 尝试更新Redis缓存
-                            try
-                            {
-                                await RedisKit.SetAsync(cache_key, list, 30 * 86400);
-                            }
-                            catch (Exception ex)
-                            {
-                                // 记录Redis更新错误
-                                _ = new LoggerLibrary("JdPool", "Redis").Info(ex.Message, ex.StackTrace).SaveAsync();
-                            }
-                        }
-                    }
-                    catch (Exception ex)
-                    {
-                        // 记录数据库查询错误
-                        _ = new LoggerLibrary("JdPool", "Database").Info(ex.Message, ex.StackTrace).SaveAsync();
-
-                        // 如果数据库查询失败但缓存还在,继续使用缓存
-                        if (_cached != null) return _cached;
-                        throw; // 如果没有任何可用数据,则抛出异常
-                    }
-                }
-                _cached = list;
-                return list ?? [];
-            }
-            finally
-            {
-                _semaphore.Release();
-            }
-        }
-
-        public static void Refresh()
-        {
-            _all_cached = null;
-            _cached = null;
-            _ = ListAsync(true);
-            _ = AllListAsync(true);
-        }
-
-        public static int Update(JdPoolDTO account)
-        {
-            return new DBContext.Table("jd_pool")
-                .Add("current_hourly_calls", account.current_hourly_calls)
-                .Add("current_daily_calls", account.current_daily_calls)
-
-                .Add("today_clickNum", account.today_clickNum)
-                .Add("today_cosFee", account.today_cosFee)
-                .Add("today_cosPrice", account.today_cosPrice)
-                .Add("today_finishCosFee", account.today_finishCosFee)
-                .Add("today_finishCosPrice", account.today_finishCosPrice)
-                .Add("today_finishOrderNum", account.today_finishOrderNum)
-                .Add("today_orderNum", account.today_orderNum)
-                .Where("id=@id", new { account.id })
-                .Update();
-        }
-
-
-
-        public static int UpdateCookies(string cookies, string union_cookies, string user_agent, string h5st, string envStr)
-        {
-            if (string.IsNullOrEmpty(cookies)) return 0;
-            string pin = cookies.GetContentPart("pin=", ";");
-            pin = pin.UrlDecode();
-
-            string company = pin;
-            int accountId = 0;
-            if (string.IsNullOrEmpty(pin)) return 0;
-            var exist = new DBContext.Table("jd_pool").Get<JdPoolDTO>("pin=@pin", new { pin });
-            if (exist != null)
-            {
-                var status = exist.status;
-                var work_mode = exist.work_mode;
-                accountId = exist.id;
-                if (work_mode == JdUnionWorkMode.Crawler) status = true;
-                var update = new DBContext.Table("jd_pool")
-                    .Add("pin", pin)
-                    .Add("cookies", cookies)
-                    .Add("union_cookies", union_cookies)
-                    .Add("status", status)
-                    .Add("last_time", DateTime.Now)
-                    .Add("login_time", DateTime.Now)
-                    .Where("pin=@pin", new { pin });
-                if (!string.IsNullOrEmpty(user_agent)) update.Add("user_agent", user_agent);
-                if (!string.IsNullOrEmpty(h5st)) update.Add("h5st", h5st);
-                if (!string.IsNullOrEmpty(envStr)) update.Add("envStr", envStr);
-
-                update.Update();
-                if (status) _ = ListAsync(true);
-            }
-            else
-            {
-                var update = new DBContext.Table("jd_pool")
-                    .Add("pin", pin)
-                    .Add("name", pin)
-                    .Add("company", pin)
-                    .Add("description", "由cookies上报创建此记录")
-                    .Add("cookies", cookies)
-                    .Add("union_cookies", union_cookies)
-                    .Add("create_time", DateTime.Now)
-                    .Add("last_time", DateTime.Now)
-                    .Add("login_time", DateTime.Now)
-                    .Add("status", 0);
-                if (!string.IsNullOrEmpty(user_agent)) update.Add("user_agent", user_agent);
-                if (!string.IsNullOrEmpty(h5st)) update.Add("h5st", h5st);
-                if (!string.IsNullOrEmpty(envStr)) update.Add("envStr", envStr);
-                accountId = update.Create();
-            }
-
-            NotifyCore.Notify(new NifyMessage
-            {
-                message = $"【京东{accountId}:{company}】cookie 上线",
-                tags = ["green_circle"]
-            });
-            _ = EndPointCore.NotifyReload(true);
-            //NotifyCore.QYWeixinPushNotifyAsync("上线", $"【淘宝联盟:{dnk}】cookie 上报更新");
-            return accountId;
-        }
-
-
-        public static async Task DisabledAsync(int accountId, string name, string content)
-        {
-            string cache_key = $"cache:jd_pool:{name}:disabled";
-            long count = await RedisKit.IncrByAsync(cache_key);
-            _ = RedisKit.ExpireAsync(cache_key, 10);
-            if (count > 1) return;
-
-            var update = new DBContext.Table("jd_pool").Add("status", 0);
-            if (accountId > 0)
-            {
-                update.Where("id=@accountId", new { accountId }).Update();
-            }
-            else
-            {
-                update.Where("name=@name", new { name }).Update();
-            }
-            _ = ListAsync(true);
-            _ = AllListAsync(true);
-            NotifyCore.Notify(new NifyMessage
-            {
-                message = $"【京东联盟{accountId}:{name}】cookie 掉线\n\n{content}",
-                priority = NifyMessagePriority.high,
-                tags = ["red_circle"]
-            });
-
-            //NotifyCore.QYWeixinPushNotifyAsync("掉线", $"【京东{accountId}:{name}】cookie 掉线");
-            _ = EndPointCore.NotifyReload(true);
-        }
-
-
-
-
-        internal static async Task AccountExhaustedAsync()
-        {
-            string cache_key = $"cache:tk_pool:account:exhausted";
-            long count = await RedisKit.IncrByAsync(cache_key);
-            if (count > 1) return;
-            _ = RedisKit.ExpireAsync(cache_key, 3600);
-            NotifyCore.Notify(new NifyMessage
-            {
-                message = $"【京东联盟】{EndPointCore.CurrentEndPoint}没有匹配账号",
-                priority = NifyMessagePriority.high,
-                tags = ["red_circle"]
-            });
-            _ = NotifyCore.QYWeixinPushNotifyAsync("没账号", $"【京东联盟】没有匹配账号");
-        }
-    }
-
-
-}
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc.Controllers;
+using Microsoft.AspNetCore.Mvc.Filters;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using dodohold.core;
+using Dataoke;
+using Google.Protobuf.WellKnownTypes;
+using System.Xml.Linq;
+using Mysqlx.Crud;
+using ZstdSharp.Unsafe;
+using YunhuiKit;
+
+
+namespace molilian.core
+{
+
+    public partial class JdPoolCore
+    {
+        public enum JdAction
+        {
+            all,
+            api,
+            parse,
+            coupon,
+            promotionQuery
+        }
+        private static string _end_point;
+        private static Dictionary<string, decimal> _incomeAmt = new();
+        static JdPoolCore()
+        {
+            _end_point = Environment.GetEnvironmentVariable("EndPoint");
+        }
+
+        private static readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
+
+        private static IEnumerable<JdPoolDTO> _cached;
+        private static IEnumerable<JdPoolDTO> _all_cached;
+        public static async Task<JdPoolDTO?> GetOneAsync()
+        {
+            var list = await ListAsync();
+            if (!list.Any()) return null;
+            return list.OrderBy(l => Guid.NewGuid()).FirstOrDefault();
+        }
+
+        public static async Task<JdPoolDTO?> GetSpecialOneAsync(int accountid)
+        {
+            var list = await AllListAsync();
+            if (!list.Any()) return null;
+            return list.Where(e => e.id == accountid).OrderBy(l => Guid.NewGuid()).FirstOrDefault();
+        }
+
+
+        public static async Task<JdPoolDTO?> GetOneAsync(int id, JdAction action)
+        {
+            var list = await ListAsync();
+            if (!list.Any()) return null;
+
+            var eligibleItems = new List<JdPoolDTO>();
+            foreach (var item in list.Where(e => e.id == id))
+            {
+                if (await IsNotExceedDailyIncomeLimitAsync(item, action))
+                {
+                    eligibleItems.Add(item);
+                }
+            }
+
+            return eligibleItems.OrderBy(l => Guid.NewGuid()).FirstOrDefault();
+        }
+
+        public static async Task<JdPoolDTO?> GetOneAsync(JdAction action, bool is_numeric, UnionParseRequest request)
+        {
+            var list = await ListAsync();
+            if (!list.Any()) return null;
+
+            if (!string.IsNullOrEmpty(request.Type))
+            {
+                list = list.Where(item => request.Type.Equals(item.parse_type)).ToList();
+                if (!list.Any()) return null;
+            }
+            else
+            {
+                if (!string.IsNullOrEmpty(request.RiskStrategy) && !"os".Equals(request.RiskStrategy) && request.LaunchScene != -1)
+                {
+                    list = list.Where(item => string.IsNullOrEmpty(item.parse_type) && request.RiskStrategy.Equals(item.riskStrategy) && item.launchScene == request.LaunchScene).ToList();
+                    if (!list.Any()) return null;
+                }
+                else
+                {
+                    list = list.Where(item => string.IsNullOrEmpty(item.parse_type) && string.IsNullOrEmpty(item.riskStrategy)).ToList();
+                    if (!list.Any()) return null;
+                }
+
+            }
+
+            var eligibleItems = new List<JdPoolDTO>();
+            foreach (var item in list)
+            {
+                if (await IsNotExceedDailyIncomeLimitAsync(item, action))
+                {
+                    // 如果 is_numeric 为 true,则排除 work_mode = Ddx (4) 的记录
+                    if (is_numeric == true && item.work_mode == JdUnionWorkMode.Ddx)
+                        continue;
+                    eligibleItems.Add(item);
+                }
+            }
+
+            return eligibleItems.OrderBy(l => Guid.NewGuid()).FirstOrDefault();
+        }
+
+
+        private static async Task<bool> IsNotExceedDailyIncomeLimitAsync(JdPoolDTO item, JdAction action)
+        {
+            if (!string.IsNullOrEmpty(_end_point) && !string.IsNullOrEmpty(item.end_point))
+            {
+                if (item.end_point != _end_point) return false;
+            }
+            switch (action)
+            {
+                case JdAction.api:
+                    if (item.work_mode != JdUnionWorkMode.SiteApi) return false;
+                    break;
+                case JdAction.parse:
+                    if (!item.enable_parse) return false;
+                    break;
+                case JdAction.coupon:
+                    if (!item.enable_coupon) return false;
+                    break;
+            }
+
+            // 使用初始化参数创建工作时间表
+            if (!new WorkSchedule(item.time_range).IsWorkHour()) return false;
+
+            if (item.daily_calls_limit > 0)
+            {
+                int daily_num = RiskControlCore.GetCalls(TkChannelEnum.jd, item.id, DateTime.Now.ToString("yyyyMMdd"));
+                if (daily_num >= item.daily_calls_limit) return false;
+            }
+            if (item.hourly_calls_limit > 0)
+            {
+                int hourly_num = RiskControlCore.GetCalls(TkChannelEnum.jd, item.id, DateTime.Now.ToString("yyyyMMddHH"));
+                if (hourly_num >= item.hourly_calls_limit) return false;
+            }
+            return true;
+        }
+
+
+        public static async Task<IEnumerable<JdPoolDTO>> AllListAsync(bool force = false)
+        {
+            if (!force && _all_cached != default) return _all_cached;
+
+            try
+            {
+                await _semaphore.WaitAsync();
+
+                string cache_key = $"cache:all_jd_pool";
+                var list = await RedisKit.GetAsync<IEnumerable<JdPoolDTO>>(cache_key);
+
+                if (force || list == default)
+                {
+                    list = new DBContext.Table("jd_pool").Select<JdPoolDTO>();
+                    if (list == null) return default;
+
+                    // 等待 Redis 写入完成
+                    await RedisKit.SetAsync(cache_key, list, 30 * 86400);
+                }
+
+                _all_cached = list;
+                return list;
+            }
+            catch (Exception)
+            {
+                // 发生异常时返回上一次的缓存,如果没有则返回默认值
+                return _all_cached ?? default;
+            }
+            finally
+            {
+                _semaphore.Release();
+            }
+        }
+
+        public static async Task<IEnumerable<JdPoolDTO>> ListAsync(bool force = false)
+        {
+            if (!force && _cached != null) return _cached;
+            try
+            {
+                await _semaphore.WaitAsync();
+
+                // 如果缓存存在且未强制刷新,直接返回
+                if (!force && _cached != null) return _cached;
+
+                string cache_key = $"cache:jd_pool";
+                IEnumerable<JdPoolDTO>? list = null;
+
+                // 尝试从Redis获取数据
+                try
+                {
+                    list = await RedisKit.GetAsync<IEnumerable<JdPoolDTO>>(cache_key);
+                }
+                catch (Exception ex)
+                {
+                    // 记录Redis错误
+                    _ = new LoggerLibrary("JdPool", "Redis").Info(ex.Message, ex.StackTrace).SaveAsync();
+                }
+
+                // 如果Redis获取失败或需要强制刷新
+                if (force || list == null)
+                {
+                    try
+                    {
+                        list = new DBContext.Table("jd_pool")
+                            .Where("status=@status", new { status = 1 })
+                            .Select<JdPoolDTO>();
+
+                        if (list != null && list.Any())
+                        {
+                            // 更新计数器
+                            foreach (var item in list)
+                            {
+                                try
+                                {
+                                    await RiskControlCore.SetCallsAsync(TkChannelEnum.jd, item.id,
+                                        DateTime.Now.ToString("yyyyMMddHH"), item.current_hourly_calls);
+                                    await RiskControlCore.SetCallsAsync(TkChannelEnum.jd, item.id,
+                                        DateTime.Now.ToString("yyyyMMdd"), item.current_daily_calls);
+                                }
+                                catch (Exception ex)
+                                {
+                                    // 记录计数器更新错误
+                                    _ = new LoggerLibrary("JdPool", "Counter").Info(ex.Message, ex.StackTrace).SaveAsync();
+                                }
+                            }
+
+                            // 尝试更新Redis缓存
+                            try
+                            {
+                                await RedisKit.SetAsync(cache_key, list, 30 * 86400);
+                            }
+                            catch (Exception ex)
+                            {
+                                // 记录Redis更新错误
+                                _ = new LoggerLibrary("JdPool", "Redis").Info(ex.Message, ex.StackTrace).SaveAsync();
+                            }
+                        }
+                    }
+                    catch (Exception ex)
+                    {
+                        // 记录数据库查询错误
+                        _ = new LoggerLibrary("JdPool", "Database").Info(ex.Message, ex.StackTrace).SaveAsync();
+
+                        // 如果数据库查询失败但缓存还在,继续使用缓存
+                        if (_cached != null) return _cached;
+                        throw; // 如果没有任何可用数据,则抛出异常
+                    }
+                }
+                _cached = list;
+                return list ?? [];
+            }
+            finally
+            {
+                _semaphore.Release();
+            }
+        }
+
+        public static void Refresh()
+        {
+            _all_cached = null;
+            _cached = null;
+            _ = ListAsync(true);
+            _ = AllListAsync(true);
+        }
+
+        public static int Update(JdPoolDTO account)
+        {
+            return new DBContext.Table("jd_pool")
+                .Add("current_hourly_calls", account.current_hourly_calls)
+                .Add("current_daily_calls", account.current_daily_calls)
+
+                .Add("today_clickNum", account.today_clickNum)
+                .Add("today_cosFee", account.today_cosFee)
+                .Add("today_cosPrice", account.today_cosPrice)
+                .Add("today_finishCosFee", account.today_finishCosFee)
+                .Add("today_finishCosPrice", account.today_finishCosPrice)
+                .Add("today_finishOrderNum", account.today_finishOrderNum)
+                .Add("today_orderNum", account.today_orderNum)
+                .Where("id=@id", new { account.id })
+                .Update();
+        }
+
+
+
+        public static int UpdateCookies(string cookies, string union_cookies, string user_agent, string h5st, string envStr)
+        {
+            if (string.IsNullOrEmpty(cookies)) return 0;
+            string pin = cookies.GetContentPart("pin=", ";");
+            pin = pin.UrlDecode();
+
+            string company = pin;
+            int accountId = 0;
+            if (string.IsNullOrEmpty(pin)) return 0;
+            var exist = new DBContext.Table("jd_pool").Get<JdPoolDTO>("pin=@pin", new { pin });
+            if (exist != null)
+            {
+                var status = exist.status;
+                var work_mode = exist.work_mode;
+                accountId = exist.id;
+                if (work_mode == JdUnionWorkMode.Crawler) status = true;
+                var update = new DBContext.Table("jd_pool")
+                    .Add("pin", pin)
+                    .Add("cookies", cookies)
+                    .Add("union_cookies", union_cookies)
+                    .Add("status", status)
+                    .Add("last_time", DateTime.Now)
+                    .Add("login_time", DateTime.Now)
+                    .Where("pin=@pin", new { pin });
+                if (!string.IsNullOrEmpty(user_agent)) update.Add("user_agent", user_agent);
+                if (!string.IsNullOrEmpty(h5st)) update.Add("h5st", h5st);
+                if (!string.IsNullOrEmpty(envStr)) update.Add("envStr", envStr);
+
+                update.Update();
+                if (status) _ = ListAsync(true);
+            }
+            else
+            {
+                var update = new DBContext.Table("jd_pool")
+                    .Add("pin", pin)
+                    .Add("name", pin)
+                    .Add("company", pin)
+                    .Add("description", "由cookies上报创建此记录")
+                    .Add("cookies", cookies)
+                    .Add("union_cookies", union_cookies)
+                    .Add("create_time", DateTime.Now)
+                    .Add("last_time", DateTime.Now)
+                    .Add("login_time", DateTime.Now)
+                    .Add("status", 0);
+                if (!string.IsNullOrEmpty(user_agent)) update.Add("user_agent", user_agent);
+                if (!string.IsNullOrEmpty(h5st)) update.Add("h5st", h5st);
+                if (!string.IsNullOrEmpty(envStr)) update.Add("envStr", envStr);
+                accountId = update.Create();
+            }
+
+            NotifyCore.Notify(new NifyMessage
+            {
+                message = $"【京东{accountId}:{company}】cookie 上线",
+                tags = ["green_circle"]
+            });
+            _ = EndPointCore.NotifyReload(true);
+            //NotifyCore.QYWeixinPushNotifyAsync("上线", $"【淘宝联盟:{dnk}】cookie 上报更新");
+            return accountId;
+        }
+
+
+        public static async Task DisabledAsync(int accountId, string name, string content)
+        {
+            string cache_key = $"cache:jd_pool:{name}:disabled";
+            long count = await RedisKit.IncrByAsync(cache_key);
+            _ = RedisKit.ExpireAsync(cache_key, 10);
+            if (count > 1) return;
+
+            var update = new DBContext.Table("jd_pool").Add("status", 0);
+            if (accountId > 0)
+            {
+                update.Where("id=@accountId", new { accountId }).Update();
+            }
+            else
+            {
+                update.Where("name=@name", new { name }).Update();
+            }
+            _ = ListAsync(true);
+            _ = AllListAsync(true);
+            NotifyCore.Notify(new NifyMessage
+            {
+                message = $"【京东联盟{accountId}:{name}】cookie 掉线\n\n{content}",
+                priority = NifyMessagePriority.high,
+                tags = ["red_circle"]
+            });
+
+            //NotifyCore.QYWeixinPushNotifyAsync("掉线", $"【京东{accountId}:{name}】cookie 掉线");
+            _ = EndPointCore.NotifyReload(true);
+        }
+
+
+
+
+        internal static async Task AccountExhaustedAsync()
+        {
+            string cache_key = $"cache:tk_pool:account:exhausted";
+            long count = await RedisKit.IncrByAsync(cache_key);
+            if (count > 1) return;
+            _ = RedisKit.ExpireAsync(cache_key, 3600);
+            NotifyCore.Notify(new NifyMessage
+            {
+                message = $"【京东联盟】{EndPointCore.CurrentEndPoint}没有匹配账号",
+                priority = NifyMessagePriority.high,
+                tags = ["red_circle"]
+            });
+            _ = NotifyCore.QYWeixinPushNotifyAsync("没账号", $"【京东联盟】没有匹配账号");
+        }
+    }
+
+
+}

+ 261 - 261
molilian.core/Core/ks/KsPoolCore.cs

@@ -1,261 +1,261 @@
-using Microsoft.AspNetCore.Http;
-using Microsoft.AspNetCore.Mvc.Controllers;
-using Microsoft.AspNetCore.Mvc.Filters;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using dodohold.core;
-using Dataoke;
-using Google.Protobuf.WellKnownTypes;
-using System.Xml.Linq;
-
-
-namespace molilian.core
-{
-
-    public partial class KsPoolCore
-    {
-        public enum KsAction
-        {
-            all,
-            parse,
-            coupon,
-            promotionQuery
-        }
-        private static string _end_point;
-        private static Dictionary<string, decimal> _incomeAmt = new();
-        static KsPoolCore()
-        {
-            _end_point = Environment.GetEnvironmentVariable("EndPoint");
-        }
-
-        private static readonly object _lockObj = new();
-        private static IEnumerable<KsPoolDTO> _cached;
-        public static KsPoolDTO? GetOne()
-        {
-            var list = List();
-            if (!list.Any()) return null;
-            return list.OrderBy(l => Guid.NewGuid()).FirstOrDefault();
-        }
-
-        public static KsPoolDTO? GetOne(KsAction action)
-        {
-            var list = List();
-            if (!list.Any()) return null;
-            return list.Where(e => IsNotExceedDailyIncomeLimit(e, action)).OrderBy(l => Guid.NewGuid()).FirstOrDefault();
-        }
-
-
-        public static KsPoolDTO? GetOne(int id, KsAction action)
-        {
-            var list = List();
-            if (!list.Any()) return null;
-            return list.Where(e => e.id == id && IsNotExceedDailyIncomeLimit(e, action)).FirstOrDefault();
-        }
-        private static bool IsNotExceedDailyIncomeLimit(KsPoolDTO item, KsAction action)
-        {
-
-            if (!string.IsNullOrEmpty(_end_point) && !string.IsNullOrEmpty(item.end_point))
-            {
-                if (item.end_point != _end_point) return false;
-            }
-            switch (action)
-            {
-                case KsAction.parse:
-                    if (!item.enable_parse) return false;
-                    break;
-                case KsAction.coupon:
-                    if (!item.enable_coupon) return false;
-                    break;
-            }
-
-            // 使用初始化参数创建工作时间表
-            if (!new WorkSchedule(item.time_range).IsWorkHour()) return false;
-
-            if (item.daily_calls_limit > 0)
-            {
-                int daily_num = RiskControlCore.GetCalls(TkChannelEnum.ks, item.id, DateTime.Now.ToString("yyyyMMdd"));
-                if (daily_num >= item.daily_calls_limit) return false;
-            }
-            if (item.hourly_calls_limit > 0)
-            {
-                int hourly_num = RiskControlCore.GetCalls(TkChannelEnum.ks, item.id, DateTime.Now.ToString("yyyyMMddHH"));
-                if (hourly_num >= item.hourly_calls_limit) return false;
-            }
-            return true;
-        }
-
-        public static IEnumerable<KsPoolDTO> List(bool force = false)
-        {
-            if (!force && _cached != null) return _cached;
-
-            string cache_key = $"cache:ks_pool";
-            var list = RedisHelper.Get<IEnumerable<KsPoolDTO>>(cache_key);
-            if (force || list == null)
-            {
-                lock (_lockObj)
-                {
-                    list = new DBContext.Table("ks_pool")
-                        .Where("status=@status", new { status = 1 })
-                        .Select<KsPoolDTO>();
-                    if (list == null) return default;
-
-                    foreach (var item in list)
-                    {
-                        RiskControlCore.SetCallsAsync(TkChannelEnum.ks, item.id, DateTime.Now.ToString("yyyyMMddHH"), item.current_hourly_calls);
-                        RiskControlCore.SetCallsAsync(TkChannelEnum.ks, item.id, DateTime.Now.ToString("yyyyMMdd"), item.current_daily_calls);
-                    }
-                    RedisHelper.Set(cache_key, list, 30 * 86400);
-                }
-            }
-            _cached = list;
-            return list;
-        }
-        public static void Refresh()
-        {
-            _ = List(true);
-        }
-
-        public static int Update(KsPoolDTO account)
-        {
-            return new DBContext.Table("ks_pool")
-                .Add("current_hourly_calls", account.current_hourly_calls)
-                .Add("current_daily_calls", account.current_daily_calls)
-
-                .Add("today_clickNum", account.today_clickNum)
-                .Add("today_cosFee", account.today_cosFee)
-                .Add("today_cosPrice", account.today_cosPrice)
-                .Add("today_finishCosFee", account.today_finishCosFee)
-                .Add("today_finishCosPrice", account.today_finishCosPrice)
-                .Add("today_finishOrderNum", account.today_finishOrderNum)
-                .Add("today_orderNum", account.today_orderNum)
-
-                .Where("id=@id", new { account.id })
-                .Update();
-        }
-
-
-
-        public static int UpdateCookies(string cookies, string user_agent)
-        {
-            if (string.IsNullOrEmpty(cookies)) return 0;
-            string pin = cookies.GetContentPart("pin=", ";");
-            pin = pin.UrlDecode();
-
-            string company = pin;
-            int accountId = 0;
-            if (string.IsNullOrEmpty(pin)) return 0;
-            var exist = new DBContext.Table("ks_pool").Get<KsPoolDTO>("pin=@pin", new { pin });
-            if (exist != null)
-            {
-                var status = exist.status;
-                var work_mode = exist.work_mode;
-                accountId = exist.id;
-                if (work_mode == KsUnionWorkMode.Crawler) status = true;
-                new DBContext.Table("ks_pool")
-                   .Add("pin", pin)
-                   .Add("cookies", cookies)
-                   .Add("user_agent", user_agent)
-                   .Add("status", status)
-                   .Add("last_time", DateTime.Now)
-                   .Add("login_time", DateTime.Now)
-                   .Where("id=@id", new { exist.id })
-                   .Update();
-                if (status) _ = List(true);
-            }
-            else
-            {
-                accountId = new DBContext.Table("ks_pool")
-                    .Add("pin", pin)
-                    .Add("name", pin)
-                    .Add("company", pin)
-                    .Add("description", "由cookies上报创建此记录")
-                    .Add("cookies", cookies)
-                    .Add("user_agent", user_agent)
-                    .Add("create_time", DateTime.Now)
-                    .Add("last_time", DateTime.Now)
-                    .Add("login_time", DateTime.Now)
-                    .Add("status", 0)
-                    .Create();
-            }
-
-            NotifyCore.Notify(new NifyMessage
-            {
-                message = $"【快手{accountId}:{company}】cookie 上线",
-                tags = ["green_circle"]
-            });
-            EndPointCore.NotifyReload(true);
-            //NotifyCore.QYWeixinPushNotifyAsync("上线", $"【淘宝联盟:{dnk}】cookie 上报更新");
-            return accountId;
-        }
-
-
-        public static void Disabled(int accountId, string name, string content)
-        {
-            string cache_key = $"cache:ks_pool:{name}:disabled";
-            long count = RedisHelper.IncrBy(cache_key);
-            RedisHelper.Expire(cache_key, 10);
-            if (count > 1) return;
-
-            var update = new DBContext.Table("ks_pool").Add("status", 0);
-            if (accountId > 0)
-            {
-                update.Where("id=@accountId", new { accountId }).Update();
-            }
-            else
-            {
-                update.Where("name=@name", new { name }).Update();
-            }
-
-            _ = List(true);
-
-            NotifyCore.Notify(new NifyMessage
-            {
-                message = $"【快手联盟{accountId}:{name}】cookie 掉线\n\n{content}",
-                priority = NifyMessagePriority.high,
-                tags = ["red_circle"]
-            });
-
-            //NotifyCore.QYWeixinPushNotifyAsync("掉线", $"【快手{accountId}:{name}】cookie 掉线");
-            EndPointCore.NotifyReload(true);
-        }
-
-
-        internal static void AccountExhausted()
-        {
-            string cache_key = $"cache:ks_pool:account:exhausted";
-            long count = RedisHelper.IncrBy(cache_key);
-            if (count > 1) return;
-            RedisHelper.Expire(cache_key, 3600);
-            NotifyCore.Notify(new NifyMessage
-            {
-                message = $"【快手联盟】没有匹配账号",
-                priority = NifyMessagePriority.high,
-                tags = ["red_circle"]
-            });
-            _ = NotifyCore.QYWeixinPushNotifyAsync("没账号", $"【快手联盟】没有匹配账号");
-        }
-
-
-        internal static void NotifyInterfaceError(string reason)
-        {
-            string cache_key = $"cache:ks_pool:account:InterfaceError";
-            long count = RedisHelper.IncrBy(cache_key);
-            if (count > 1) return;
-            RedisHelper.Expire(cache_key, 3600);
-            NotifyCore.Notify(new NifyMessage
-            {
-                message = $"【快手联盟】{reason}",
-                priority = NifyMessagePriority.high,
-                tags = ["red_circle"]
-            });
-        }
-
-
-
-
-    }
-
-
-}
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc.Controllers;
+using Microsoft.AspNetCore.Mvc.Filters;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using dodohold.core;
+using Dataoke;
+using Google.Protobuf.WellKnownTypes;
+using System.Xml.Linq;
+
+
+namespace molilian.core
+{
+
+    public partial class KsPoolCore
+    {
+        public enum KsAction
+        {
+            all,
+            parse,
+            coupon,
+            promotionQuery
+        }
+        private static string _end_point;
+        private static Dictionary<string, decimal> _incomeAmt = new();
+        static KsPoolCore()
+        {
+            _end_point = Environment.GetEnvironmentVariable("EndPoint");
+        }
+
+        private static readonly object _lockObj = new();
+        private static IEnumerable<KsPoolDTO> _cached;
+        public static KsPoolDTO? GetOne()
+        {
+            var list = List();
+            if (!list.Any()) return null;
+            return list.OrderBy(l => Guid.NewGuid()).FirstOrDefault();
+        }
+
+        public static KsPoolDTO? GetOne(KsAction action)
+        {
+            var list = List();
+            if (!list.Any()) return null;
+            return list.Where(e => IsNotExceedDailyIncomeLimit(e, action)).OrderBy(l => Guid.NewGuid()).FirstOrDefault();
+        }
+
+
+        public static KsPoolDTO? GetOne(int id, KsAction action)
+        {
+            var list = List();
+            if (!list.Any()) return null;
+            return list.Where(e => e.id == id && IsNotExceedDailyIncomeLimit(e, action)).FirstOrDefault();
+        }
+        private static bool IsNotExceedDailyIncomeLimit(KsPoolDTO item, KsAction action)
+        {
+
+            if (!string.IsNullOrEmpty(_end_point) && !string.IsNullOrEmpty(item.end_point))
+            {
+                if (item.end_point != _end_point) return false;
+            }
+            switch (action)
+            {
+                case KsAction.parse:
+                    if (!item.enable_parse) return false;
+                    break;
+                case KsAction.coupon:
+                    if (!item.enable_coupon) return false;
+                    break;
+            }
+
+            // 使用初始化参数创建工作时间表
+            if (!new WorkSchedule(item.time_range).IsWorkHour()) return false;
+
+            if (item.daily_calls_limit > 0)
+            {
+                int daily_num = RiskControlCore.GetCalls(TkChannelEnum.ks, item.id, DateTime.Now.ToString("yyyyMMdd"));
+                if (daily_num >= item.daily_calls_limit) return false;
+            }
+            if (item.hourly_calls_limit > 0)
+            {
+                int hourly_num = RiskControlCore.GetCalls(TkChannelEnum.ks, item.id, DateTime.Now.ToString("yyyyMMddHH"));
+                if (hourly_num >= item.hourly_calls_limit) return false;
+            }
+            return true;
+        }
+
+        public static IEnumerable<KsPoolDTO> List(bool force = false)
+        {
+            if (!force && _cached != null) return _cached;
+
+            string cache_key = $"cache:ks_pool";
+            var list = RedisHelper.Get<IEnumerable<KsPoolDTO>>(cache_key);
+            if (force || list == null)
+            {
+                lock (_lockObj)
+                {
+                    list = new DBContext.Table("ks_pool")
+                        .Where("status=@status", new { status = 1 })
+                        .Select<KsPoolDTO>();
+                    if (list == null) return default;
+
+                    foreach (var item in list)
+                    {
+                        RiskControlCore.SetCallsAsync(TkChannelEnum.ks, item.id, DateTime.Now.ToString("yyyyMMddHH"), item.current_hourly_calls);
+                        RiskControlCore.SetCallsAsync(TkChannelEnum.ks, item.id, DateTime.Now.ToString("yyyyMMdd"), item.current_daily_calls);
+                    }
+                    RedisHelper.Set(cache_key, list, 30 * 86400);
+                }
+            }
+            _cached = list;
+            return list;
+        }
+        public static void Refresh()
+        {
+            _ = List(true);
+        }
+
+        public static int Update(KsPoolDTO account)
+        {
+            return new DBContext.Table("ks_pool")
+                .Add("current_hourly_calls", account.current_hourly_calls)
+                .Add("current_daily_calls", account.current_daily_calls)
+
+                .Add("today_clickNum", account.today_clickNum)
+                .Add("today_cosFee", account.today_cosFee)
+                .Add("today_cosPrice", account.today_cosPrice)
+                .Add("today_finishCosFee", account.today_finishCosFee)
+                .Add("today_finishCosPrice", account.today_finishCosPrice)
+                .Add("today_finishOrderNum", account.today_finishOrderNum)
+                .Add("today_orderNum", account.today_orderNum)
+
+                .Where("id=@id", new { account.id })
+                .Update();
+        }
+
+
+
+        public static int UpdateCookies(string cookies, string user_agent)
+        {
+            if (string.IsNullOrEmpty(cookies)) return 0;
+            string pin = cookies.GetContentPart("pin=", ";");
+            pin = pin.UrlDecode();
+
+            string company = pin;
+            int accountId = 0;
+            if (string.IsNullOrEmpty(pin)) return 0;
+            var exist = new DBContext.Table("ks_pool").Get<KsPoolDTO>("pin=@pin", new { pin });
+            if (exist != null)
+            {
+                var status = exist.status;
+                var work_mode = exist.work_mode;
+                accountId = exist.id;
+                if (work_mode == KsUnionWorkMode.Crawler) status = true;
+                new DBContext.Table("ks_pool")
+                   .Add("pin", pin)
+                   .Add("cookies", cookies)
+                   .Add("user_agent", user_agent)
+                   .Add("status", status)
+                   .Add("last_time", DateTime.Now)
+                   .Add("login_time", DateTime.Now)
+                   .Where("id=@id", new { exist.id })
+                   .Update();
+                if (status) _ = List(true);
+            }
+            else
+            {
+                accountId = new DBContext.Table("ks_pool")
+                    .Add("pin", pin)
+                    .Add("name", pin)
+                    .Add("company", pin)
+                    .Add("description", "由cookies上报创建此记录")
+                    .Add("cookies", cookies)
+                    .Add("user_agent", user_agent)
+                    .Add("create_time", DateTime.Now)
+                    .Add("last_time", DateTime.Now)
+                    .Add("login_time", DateTime.Now)
+                    .Add("status", 0)
+                    .Create();
+            }
+
+            NotifyCore.Notify(new NifyMessage
+            {
+                message = $"【快手{accountId}:{company}】cookie 上线",
+                tags = ["green_circle"]
+            });
+            EndPointCore.NotifyReload(true);
+            //NotifyCore.QYWeixinPushNotifyAsync("上线", $"【淘宝联盟:{dnk}】cookie 上报更新");
+            return accountId;
+        }
+
+
+        public static void Disabled(int accountId, string name, string content)
+        {
+            string cache_key = $"cache:ks_pool:{name}:disabled";
+            long count = RedisHelper.IncrBy(cache_key);
+            RedisHelper.Expire(cache_key, 10);
+            if (count > 1) return;
+
+            var update = new DBContext.Table("ks_pool").Add("status", 0);
+            if (accountId > 0)
+            {
+                update.Where("id=@accountId", new { accountId }).Update();
+            }
+            else
+            {
+                update.Where("name=@name", new { name }).Update();
+            }
+
+            _ = List(true);
+
+            NotifyCore.Notify(new NifyMessage
+            {
+                message = $"【快手联盟{accountId}:{name}】cookie 掉线\n\n{content}",
+                priority = NifyMessagePriority.high,
+                tags = ["red_circle"]
+            });
+
+            //NotifyCore.QYWeixinPushNotifyAsync("掉线", $"【快手{accountId}:{name}】cookie 掉线");
+            EndPointCore.NotifyReload(true);
+        }
+
+
+        internal static void AccountExhausted()
+        {
+            string cache_key = $"cache:ks_pool:account:exhausted";
+            long count = RedisHelper.IncrBy(cache_key);
+            if (count > 1) return;
+            RedisHelper.Expire(cache_key, 3600);
+            NotifyCore.Notify(new NifyMessage
+            {
+                message = $"【快手联盟】没有匹配账号",
+                priority = NifyMessagePriority.high,
+                tags = ["red_circle"]
+            });
+            _ = NotifyCore.QYWeixinPushNotifyAsync("没账号", $"【快手联盟】没有匹配账号");
+        }
+
+
+        internal static void NotifyInterfaceError(string reason)
+        {
+            string cache_key = $"cache:ks_pool:account:InterfaceError";
+            long count = RedisHelper.IncrBy(cache_key);
+            if (count > 1) return;
+            RedisHelper.Expire(cache_key, 3600);
+            NotifyCore.Notify(new NifyMessage
+            {
+                message = $"【快手联盟】{reason}",
+                priority = NifyMessagePriority.high,
+                tags = ["red_circle"]
+            });
+        }
+
+
+
+
+    }
+
+
+}

+ 596 - 596
molilian.core/Core/log/base.cs

@@ -1,85 +1,85 @@
-using dodohold.core;
-using CSRedis;
-using System.Data;
-using System.Diagnostics;
-using YunhuiKit;
-using static ICSharpCode.SharpZipLib.Zip.ExtendedUnixData;
-
-
-namespace molilian.core
-{
-    public partial class TkLogCore
-    {
-
-        public static bool save_dailys_log = false;
-
-        static TkLogCore()
-        {
-            int flag = RedisHelper.Get<int>("turn:save_dailys_log");
-            if (flag == 1) save_dailys_log = true;
-        }
-        private static readonly SemaphoreSlim semaphore = new SemaphoreSlim(10, 10);
-
-        public static async Task<int> BatchInsertLogDBAsync(int limit)
-        {
-            await semaphore.WaitAsync().ConfigureAwait(false);
-            try
-            {
-                var tasks = EndPointCore.List()
-                    .Where(node => node.is_public_api && !string.IsNullOrEmpty(node.redis_server))
-                    .Where(node => CenterHub.IsCenter ? !node.is_coupon_api : node.is_coupon_api)
-                    .Select(async node =>
-                    {
-                        try
-                        {
-                            var redisServer = EndPointCore.GetRedisServer(node);
-                            if (string.IsNullOrEmpty(redisServer)) return 0;
-                            await using var scope = await RedisClientFactory.CreateScopeAsync(redisServer);
-                            return BatchInsertLogDB(limit, scope.Client);
-                        }
-                        catch (Exception ex)
-                        {
-                            // TODO: 添加日志记录
-                            return 0;
-                        }
-                    });
-
-                var results = await Task.WhenAll(tasks).ConfigureAwait(false);
-                return results.Sum();
-            }
-            catch (Exception)
-            {
-                return 0;
-            }
-            finally
-            {
-                semaphore.Release();
-            }
-        }
-
-        public static int BatchInsertLogDB(int limit, YunhuiKit.RedisClient redis)
-        {
-            int total = 0;
-            //using var connection = DBContext.GetOpenConnection();
-            //connection.Open();
-            //using var transaction = connection.BeginTransaction();
-            using IDbTransaction transaction = null;
-            using IDbConnection connection = null;
-
-            try
-            {
-                Stopwatch stopwatch = new Stopwatch(); // 创建一个计时器
-
-                var tasks = new List<Task<int>>
-                {
-                    RunTaskWithLoggingAsync(() => InsertPromotionImgAsync(limit, redis), "task_insert_promotion_img_logs"),
-                    RunTaskWithLoggingAsync(() => InsertParseTkLogAsync(limit, redis), "task_insert_parse_tb_logs"),
-                    RunTaskWithLoggingAsync(() => InsertParseJDLogAsync(limit, redis), "task_insert_parse_jd_logs"),
-                    RunTaskWithLoggingAsync(() => InsertParsePddLogAsync(limit, redis), "task_insert_parse_pdd_logs"),
-                    RunTaskWithLoggingAsync(() => InsertParseDyLogAsync(limit, redis), "task_insert_parse_dy_logs"),
-                    RunTaskWithLoggingAsync(() => InsertParseKsLogAsync(limit, redis), "task_insert_parse_ks_logs"),
-
-
+using dodohold.core;
+using CSRedis;
+using System.Data;
+using System.Diagnostics;
+using YunhuiKit;
+using static ICSharpCode.SharpZipLib.Zip.ExtendedUnixData;
+
+
+namespace molilian.core
+{
+    public partial class TkLogCore
+    {
+
+        public static bool save_dailys_log = false;
+
+        static TkLogCore()
+        {
+            int flag = RedisHelper.Get<int>("turn:save_dailys_log");
+            if (flag == 1) save_dailys_log = true;
+        }
+        private static readonly SemaphoreSlim semaphore = new SemaphoreSlim(10, 10);
+
+        public static async Task<int> BatchInsertLogDBAsync(int limit)
+        {
+            await semaphore.WaitAsync().ConfigureAwait(false);
+            try
+            {
+                var tasks = EndPointCore.List()
+                    .Where(node => node.is_public_api && !string.IsNullOrEmpty(node.redis_server))
+                    .Where(node => CenterHub.IsCenter ? !node.is_coupon_api : node.is_coupon_api)
+                    .Select(async node =>
+                    {
+                        try
+                        {
+                            var redisServer = EndPointCore.GetRedisServer(node);
+                            if (string.IsNullOrEmpty(redisServer)) return 0;
+                            await using var scope = await RedisClientFactory.CreateScopeAsync(redisServer);
+                            return BatchInsertLogDB(limit, scope.Client);
+                        }
+                        catch (Exception ex)
+                        {
+                            // TODO: 添加日志记录
+                            return 0;
+                        }
+                    });
+
+                var results = await Task.WhenAll(tasks).ConfigureAwait(false);
+                return results.Sum();
+            }
+            catch (Exception)
+            {
+                return 0;
+            }
+            finally
+            {
+                semaphore.Release();
+            }
+        }
+
+        public static int BatchInsertLogDB(int limit, YunhuiKit.RedisClient redis)
+        {
+            int total = 0;
+            //using var connection = DBContext.GetOpenConnection();
+            //connection.Open();
+            //using var transaction = connection.BeginTransaction();
+            using IDbTransaction transaction = null;
+            using IDbConnection connection = null;
+
+            try
+            {
+                Stopwatch stopwatch = new Stopwatch(); // 创建一个计时器
+
+                var tasks = new List<Task<int>>
+                {
+                    RunTaskWithLoggingAsync(() => InsertPromotionImgAsync(limit, redis), "task_insert_promotion_img_logs"),
+                    RunTaskWithLoggingAsync(() => InsertParseTkLogAsync(limit, redis), "task_insert_parse_tb_logs"),
+                    RunTaskWithLoggingAsync(() => InsertParseJDLogAsync(limit, redis), "task_insert_parse_jd_logs"),
+                    RunTaskWithLoggingAsync(() => InsertParsePddLogAsync(limit, redis), "task_insert_parse_pdd_logs"),
+                    RunTaskWithLoggingAsync(() => InsertParseDyLogAsync(limit, redis), "task_insert_parse_dy_logs"),
+                    RunTaskWithLoggingAsync(() => InsertParseKsLogAsync(limit, redis), "task_insert_parse_ks_logs"),
+
+
                     RunTaskWithLoggingAsync(() => InsertToolLogAsync(limit, redis), "task_insert_parse_tool_logs"),
                     RunTaskWithLoggingAsync(() => InsertDeeplinkLogAsync(limit, redis), "task_insert_parse_deeplink_logs"),
 
@@ -87,517 +87,517 @@ namespace molilian.core
                     RunTaskWithLoggingAsync(() => InsertActivityLogAsync(limit, redis), "task_insert_activity_logs"),
                     RunTaskWithLoggingAsync(() => InsertCpsLogAsync(limit, redis), "task_insert_parse_cps_logs"),
                     RunTaskWithLoggingAsync(() => TracksCore.InsertTrackRequestLogAsync(limit, redis), "task_insert_track_request_logs"),
-
-
-
-                    //Task.Run(() => RunTaskWithLogging(() => task_insert_tk_logs(limit, redis), "task_insert_tk_logs")),
-                    //Task.Run(() => RunTaskWithLogging(() => task_insert_parse_tb_logs(limit, redis), "task_insert_parse_tb_logs")),
-                    //Task.Run(() => RunTaskWithLogging(() => task_insert_parse_jd_logs(limit, redis), "task_insert_parse_jd_logs")),
-                    //Task.Run(() => RunTaskWithLogging(() => task_insert_parse_pdd_logs(limit, redis), "task_insert_parse_pdd_logs")),
-                    //Task.Run(() => RunTaskWithLogging(() => task_insert_parse_dy_logs(limit, redis), "task_insert_parse_dy_logs")),
-                    //Task.Run(() => RunTaskWithLogging(() => task_insert_parse_tool_logs(limit, redis), "task_insert_parse_tool_logs")),
-                    //Task.Run(() => RunTaskWithLogging(() => task_insert_parse_deeplink_logs(limit, redis), "task_insert_parse_deeplink_logs")),
-                    //Task.Run(() => RunTaskWithLogging(() => task_insert_parse_coupon_logs(limit, redis), "task_insert_parse_coupon_logs")),
-                    //Task.Run(() => RunTaskWithLogging(() => task_insert_parse_cps_logs(limit, redis), "task_insert_parse_cps_logs")),
-                    //Task.Run(() => RunTaskWithLogging(() => task_insert_promotion_img_logs(limit, redis), "task_insert_promotion_img_logs")),
-                    //Task.Run(() => RunTaskWithLogging(() => task_insert_parse_ks_logs(limit, redis), "task_insert_parse_ks_logs"))
-                };
-
-                // 等待所有任务完成
-                Task.WhenAll(tasks).Wait();//252行
-
-                // 计算所有任务的结果总和
-                total = tasks.Select(t => t.Result).Sum();
-
-                //transaction.Commit();
-            }
-            catch (Exception ex)
-            {
-                //transaction.Rollback();
-
-
-                // 构建异常详细信息字符串
-                string detailedError = $"{ex.Message}\n" +
-                                       $"堆栈跟踪: {ex.StackTrace}\n";
-
-                if (ex.InnerException != null)
-                {
-                    detailedError += $"内部异常: {ex.InnerException.Message}\n" +
-                                     $"内部堆栈跟踪: {ex.InnerException.StackTrace}\n";
-                }
-
-                // 如果异常包含其他数据,也可以记录下来
-                if (ex.Data != null && ex.Data.Count > 0)
-                {
-                    detailedError += "附加数据:\n";
-                    foreach (var key in ex.Data.Keys)
-                    {
-                        detailedError += $"{key}: {ex.Data[key]}\n";
-                    }
-                }
-
-
-                _ = new LoggerLibrary("database_error", "parse_log")
-                    .Info(detailedError)
-                    .SaveAsync();
-
-
-                NotifyCore.Notify(new NifyMessage
-                {
-                    message = $"【Exception】\n{detailedError}",
-                    priority = NifyMessagePriority.high,
-                    tags = ["red_circle"]
-                });
-            }
-            finally
-            {
-                //connection.Close();
-            }
-            return total;
-        }
-
-
-
-        private static async Task<int> RunTaskWithLoggingAsync(Func<Task<int>> taskFunc, string taskName)
-        {
-            LoggerLibrary log = new LoggerLibrary("debug", "BatchInsertLogDB"); // 创建日志对象
-            Stopwatch stopwatch = new Stopwatch();
-            stopwatch.Start();
-            int taskTotal = await taskFunc();
-            stopwatch.Stop();
-            log.Info($"{taskName} 耗时: {stopwatch.ElapsedMilliseconds} ms,\t插入记录数: {taskTotal}");
-            log.SaveAsync();
-            return taskTotal;
-        }
-
-        /// <summary>
-        /// 过时方法 随时删除
-        /// </summary>
-        /// <param name="channel"></param>
-        /// <param name="accountId"></param>
-        /// <param name="accountName"></param>
-        /// <param name="success"></param>
-        /// <param name="message"></param>
-        /// <param name="reason"></param>
-        private static void saveCache(string channel, int accountId, string accountName, bool success, string message, string reason)
-        {
-            saveAccountCache("all", success, message, reason);
-            saveAccountCache($"{channel}", success, message, reason);
-            saveAccountCache($"{accountName}", success, message, reason);
-            if (accountId != 0)
-            {
-                //todo 放着跑两天,要将读取的地方改成读取accountid
-                saveAccountCache($"{channel}_{accountId}", success, message, reason);
-            }
-        }
-
-        private static void saveAccountCache(string accountName, bool success, string message, string reason)
-        {
-            RedisHelper.IncrBy($":total:{accountName}:{DateTime.Now:yyyyMM}");
-            RedisHelper.IncrBy($":total:{accountName}:{DateTime.Now:yyyyMMdd}");
-            RedisHelper.IncrBy($":total:{accountName}:{DateTime.Now:yyyyMMddHH}");
-
-
-            string result = success ? "success" : "fail";
-            RedisHelper.IncrBy($":total:{accountName}:{result}:{DateTime.Now:yyyyMM}");
-            RedisHelper.IncrBy($":total:{accountName}:{result}:{DateTime.Now:yyyyMMdd}");
-            RedisHelper.IncrBy($":total:{accountName}:{result}:{DateTime.Now:yyyyMMddHH}");
-
-
-            if (!string.IsNullOrEmpty(message))
-            {
-                RedisHelper.SAdd($":total:{accountName}:message:{DateTime.Now:yyyyMM}", message);
-                RedisHelper.SAdd($":total:{accountName}:message:{DateTime.Now:yyyyMMdd}", message);
-                RedisHelper.SAdd($":total:{accountName}:message:{DateTime.Now:yyyyMMddHH}", message);
-
-                RedisHelper.IncrBy($":total:{accountName}:{message}:{DateTime.Now:yyyyMM}");
-                RedisHelper.IncrBy($":total:{accountName}:{message}:{DateTime.Now:yyyyMMdd}");
-                RedisHelper.IncrBy($":total:{accountName}:{message}:{DateTime.Now:yyyyMMddHH}");
-
-                RedisHelper.IncrBy($":total:{accountName}:message:{message}:{DateTime.Now:yyyyMM}");
-                RedisHelper.IncrBy($":total:{accountName}:message:{message}:{DateTime.Now:yyyyMMdd}");
-                RedisHelper.IncrBy($":total:{accountName}:message:{message}:{DateTime.Now:yyyyMMddHH}");
-            }
-
-            if (!string.IsNullOrEmpty(reason))
-            {
-                RedisHelper.SAdd($":total:{accountName}:reason:{DateTime.Now:yyyyMM}", reason);
-                RedisHelper.SAdd($":total:{accountName}:reason:{DateTime.Now:yyyyMMdd}", reason);
-                RedisHelper.SAdd($":total:{accountName}:reason:{DateTime.Now:yyyyMMddHH}", reason);
-
-                RedisHelper.IncrBy($":total:{accountName}:{reason}:{DateTime.Now:yyyyMM}");
-                RedisHelper.IncrBy($":total:{accountName}:{reason}:{DateTime.Now:yyyyMMdd}");
-                RedisHelper.IncrBy($":total:{accountName}:{reason}:{DateTime.Now:yyyyMMddHH}");
-
-
-                RedisHelper.IncrBy($":total:{accountName}:reason:{reason}:{DateTime.Now:yyyyMM}");
-                RedisHelper.IncrBy($":total:{accountName}:reason:{reason}:{DateTime.Now:yyyyMMdd}");
-                RedisHelper.IncrBy($":total:{accountName}:reason:{reason}:{DateTime.Now:yyyyMMddHH}");
-            }
-
-        }
-
-        private static async Task saveUnionCouponParseCacheAsync(TkDataDTO data)
-        {
-            string cacheKey = $":cache:parse:{data.ip}_{data.oaid}_{data.itemId}";
-            await EndPointCore.ProcessEndPointNodesAsync(node =>
-            {
-                if (!node.is_coupon_api) return Task.CompletedTask;
-                if (string.IsNullOrEmpty(node.redis_server)) return Task.CompletedTask;
-
-                var redis = RedisClientManager.GetRedisClient(node.redis_server);
-                redis.Set(cacheKey, 1, 2 * 86400);
-                return Task.CompletedTask;
-            });
-        }
-        private async static Task saveClientRequestTotalAsync(TkChannelEnum channel, string ip, string oaid)
-        {
-            await saveClientRequestTotalAsync(channel.ToString(), ip, oaid);
-        }
-        private static async Task saveClientRequestTotalAsync(string channel, string ip, string oaid)
-        {
-            await EndPointCore.ProcessEndPointNodesAsync(node =>
-            {
-                if (string.IsNullOrEmpty(node.redis_server)) return Task.CompletedTask;
-                if (!node.is_public_api) return Task.CompletedTask;
-
-#if DEBUG
-                switch (node.name)
-                {
-
-                    case "bj":
-                        node.redis_server = "101.200.152.61:6379,password=pKBiS4ka2IpXayIdcx00,defaultDatabase=0,idleTimeout=20000,preheat=3,tryit=2,ssl=false,prefix=webhook";
-                        break;
-                    case "gz":
-                        node.redis_server = "8.138.110.158:6379,password=pKBiS4ka2IpXayIdcx00,defaultDatabase=0,idleTimeout=20000,preheat=3,tryit=2,ssl=false,prefix=webhook";
-                        break;
-                    case "coupon1":
-                        node.redis_server = "123.56.185.166:6379,password=pKBiS4ka2IpXayIdcx00,defaultDatabase=0,idleTimeout=20000,preheat=3,tryit=2,ssl=false,prefix=coupon";
-                        break;
-                    default: return Task.CompletedTask;
-                }
-
-#endif
-
-
-                var redis = RedisClientManager.GetRedisClient(node.redis_server);
-
-                string cacheKey = $":cache:{channel}:ip:{DateTime.Now:yyyyMMdd}:{ip}";
-                redis.IncrBy(cacheKey);
-                redis.Expire(cacheKey, 86400);
-
-                if (!string.IsNullOrEmpty(oaid))
-                {
-                    cacheKey = $":cache:{channel}:oaid:{DateTime.Now:yyyyMMdd}:{oaid}";
-                    redis.IncrBy(cacheKey);
-                    redis.Expire(cacheKey, 86400);
-                }
-                return Task.CompletedTask;
-            });
-
-        }
-        public static bool InBlacklist(string blacklist, string oaid)
-        {
-            if (string.IsNullOrEmpty(blacklist)) return false;
-
-            var arr = blacklist.Split(new[] { "\r\n" }, StringSplitOptions.None)
-                   .Select(s => s.Trim()).ToArray();
-            return arr.Contains(oaid);
-        }
-
-
-        public static int getClientRequestTotalByOAID(TkChannelEnum channel, string oaid)
-        {
-            if (string.IsNullOrEmpty(oaid)) return 0;
-            string cacheKey = $":cache:{channel}:oaid:{DateTime.Now:yyyyMMdd}:{oaid}";
-            return RedisHelper.Get<int>(cacheKey);
-        }
-        public static int getClientRequestTotalByIp(TkChannelEnum channel, string ip)
-        {
-            if (string.IsNullOrEmpty(ip)) return 0;
-            string cacheKey = $":cache:{channel}:ip:{DateTime.Now:yyyyMMdd}:{ip}";
-            return RedisHelper.Get<int>(cacheKey);
-        }
-        public static int getClientRequestTotalByOAID(string channel, string oaid)
-        {
-            if (string.IsNullOrEmpty(oaid)) return 0;
-            string cacheKey = $":cache:{channel}:oaid:{DateTime.Now:yyyyMMdd}:{oaid}";
-            return RedisHelper.Get<int>(cacheKey);
-        }
-        public static int getClientRequestTotalByIp(string channel, string ip)
-        {
-            if (string.IsNullOrEmpty(ip)) return 0;
-            string cacheKey = $":cache:{channel}:ip:{DateTime.Now:yyyyMMdd}:{ip}";
-            return RedisHelper.Get<int>(cacheKey);
-        }
-
-
-        private static void saveClientRequestTotal(CpsChannelEnum channel, string ip, string oaid)
-        {
-            string cacheKey = $":cache:cps_{channel}:ip:{DateTime.Now:yyyyMMdd}:{ip}";
-            RedisHelper.IncrBy(cacheKey);
-            RedisHelper.Expire(cacheKey, 86400);
-
-            if (!string.IsNullOrEmpty(oaid))
-            {
-                cacheKey = $":cache:cps_{channel}:oaid:{DateTime.Now:yyyyMMdd}:{oaid}";
-                RedisHelper.IncrBy(cacheKey);
-                RedisHelper.Expire(cacheKey, 86400);
-            }
-        }
-        public static int getClientRequestTotalByOAID(CpsChannelEnum channel, string oaid)
-        {
-            if (string.IsNullOrEmpty(oaid)) return 0;
-            string cacheKey = $":cache:cps_{channel}:oaid:{DateTime.Now:yyyyMMdd}:{oaid}";
-            return RedisHelper.Get<int>(cacheKey);
-        }
-        public static int getClientRequestTotalByIp(CpsChannelEnum channel, string ip)
-        {
-            if (string.IsNullOrEmpty(ip)) return 0;
-            string cacheKey = $":cache:cps_{channel}:ip:{DateTime.Now:yyyyMMdd}:{ip}";
-            return RedisHelper.Get<int>(cacheKey);
-        }
-
-
-        private static async Task SaveParseCacheAsync(string channel, int accountId, string accountName,
-            bool success, string message, string reason, string deeplink)
-        {
-            string dp_flag = deeplink switch
-            {
-                "" => "none",
-                "tbopen://m.taobao.com/tbopen/index.html" or
-                "pinduoduo://com.xunmeng.pinduoduo/" or
-                "snssdk1128://feed?refer=web" or
-                "bdnetdisk://n/action.EXTERNAL_ACTIVITY" or
-                "openapp.jdmobile://virtual?params=" or "openapp.jdmobile://" => "home",
-                _ => success ? "success" : "fail",
-            };
-
-            //关于dp的缓存
-            await SaveParseAccountCacheAsync($"dp_{dp_flag}:all", success, message, reason);
-            await SaveParseAccountCacheAsync($"dp_{dp_flag}:{channel}", success, message, reason);
-            await SaveParseAccountCacheAsync($"dp_{dp_flag}:{accountName}", success, message, reason);
-            if (accountId != 0)
-            {
-                await SaveParseAccountCacheAsync($"dp_{dp_flag}:{channel}_{accountId}", success, message, reason);
-            }
-
-            await SaveParseAccountCacheAsync("all", success, message, reason);
-            await SaveParseAccountCacheAsync($"dp_channel:{channel}", success, message, reason);
-            await SaveParseAccountCacheAsync($"{channel}", success, message, reason);
-            await SaveParseAccountCacheAsync($"{accountName}", success, message, reason);
-            if (accountId != 0)
-            {
-                //todo 放着跑两天,要将读取的地方改成读取accountid
-                await SaveParseAccountCacheAsync($"{channel}_{accountId}", success, message, reason);
-            }
-        }
-
-        private static async Task SaveParseAccountCacheAsync(string accountName, bool success,
-            string message, string reason)
-        {
-            await RedisKit.IncrByAsync($":parse_total:{accountName}:{DateTime.Now:yyyyMM}");
-            await RedisKit.IncrByAsync($":parse_total:{accountName}:{DateTime.Now:yyyyMMdd}");
-            await RedisKit.IncrByAsync($":parse_total:{accountName}:{DateTime.Now:yyyyMMddHH}");
-
-            string result = success ? "success" : "fail";
-            await RedisKit.IncrByAsync($":parse_total:{accountName}:{result}:{DateTime.Now:yyyyMM}");
-            await RedisKit.IncrByAsync($":parse_total:{accountName}:{result}:{DateTime.Now:yyyyMMdd}");
-            await RedisKit.IncrByAsync($":parse_total:{accountName}:{result}:{DateTime.Now:yyyyMMddHH}");
-
-            if (!string.IsNullOrEmpty(message))
-            {
-                await RedisKit.SAddAsync($":parse_total:{accountName}:message:{DateTime.Now:yyyyMM}", message);
-                await RedisKit.SAddAsync($":parse_total:{accountName}:message:{DateTime.Now:yyyyMMdd}", message);
-                await RedisKit.SAddAsync($":parse_total:{accountName}:message:{DateTime.Now:yyyyMMddHH}", message);
-
-                await RedisKit.IncrByAsync($":parse_total:{accountName}:{message}:{DateTime.Now:yyyyMM}");
-                await RedisKit.IncrByAsync($":parse_total:{accountName}:{message}:{DateTime.Now:yyyyMMdd}");
-                await RedisKit.IncrByAsync($":parse_total:{accountName}:{message}:{DateTime.Now:yyyyMMddHH}");
-                await RedisKit.IncrByAsync($":parse_total:{accountName}:message:{message}:{DateTime.Now:yyyyMM}");
-                await RedisKit.IncrByAsync($":parse_total:{accountName}:message:{message}:{DateTime.Now:yyyyMMdd}");
-                await RedisKit.IncrByAsync($":parse_total:{accountName}:message:{message}:{DateTime.Now:yyyyMMddHH}");
-            }
-
-            if (!string.IsNullOrEmpty(reason))
-            {
-                await RedisKit.SAddAsync($":parse_total:{accountName}:reason:{DateTime.Now:yyyyMM}", reason);
-                await RedisKit.SAddAsync($":parse_total:{accountName}:reason:{DateTime.Now:yyyyMMdd}", reason);
-                await RedisKit.SAddAsync($":parse_total:{accountName}:reason:{DateTime.Now:yyyyMMddHH}", reason);
-
-                await RedisKit.IncrByAsync($":parse_total:{accountName}:{reason}:{DateTime.Now:yyyyMM}");
-                await RedisKit.IncrByAsync($":parse_total:{accountName}:{reason}:{DateTime.Now:yyyyMMdd}");
-                await RedisKit.IncrByAsync($":parse_total:{accountName}:{reason}:{DateTime.Now:yyyyMMddHH}");
-
-                await RedisKit.IncrByAsync($":parse_total:{accountName}:reason:{reason}:{DateTime.Now:yyyyMM}");
-                await RedisKit.IncrByAsync($":parse_total:{accountName}:reason:{reason}:{DateTime.Now:yyyyMMdd}");
-                await RedisKit.IncrByAsync($":parse_total:{accountName}:reason:{reason}:{DateTime.Now:yyyyMMddHH}");
-            }
-        }
-
-
-
-        private static void saveParseCache(string channel, int accountId, string accountName,
-            bool success, string message, string reason, string deeplink)
-        {
-            string dp_flag = deeplink switch
-            {
-                "" => "none",
-                "tbopen://m.taobao.com/tbopen/index.html" or
-                "pinduoduo://com.xunmeng.pinduoduo/" or
-                "snssdk1128://feed?refer=web" or
-                "bdnetdisk://n/action.EXTERNAL_ACTIVITY" or
-                "openapp.jdmobile://virtual?params=" or "openapp.jdmobile://" => "home",
-                _ => success ? "success" : "fail",
-            };
-
-            //关于dp的缓存
-            saveParseAccountCache($"dp_{dp_flag}:all", success, message, reason);
-            saveParseAccountCache($"dp_{dp_flag}:{channel}", success, message, reason);
-            saveParseAccountCache($"dp_{dp_flag}:{accountName}", success, message, reason);
-            if (accountId != 0)
-            {
-                saveParseAccountCache($"dp_{dp_flag}:{channel}_{accountId}", success, message, reason);
-            }
-
-            saveParseAccountCache("all", success, message, reason);
-            saveParseAccountCache($"{channel}", success, message, reason);
-            saveParseAccountCache($"{accountName}", success, message, reason);
-            if (accountId != 0)
-            {
-                //todo 放着跑两天,要将读取的地方改成读取accountid
-                saveParseAccountCache($"{channel}_{accountId}", success, message, reason);
-            }
-        }
-        private static void saveParseAccountCache(string accountName, bool success,
-            string message, string reason)
-        {
-
-            RedisHelper.IncrBy($":parse_total:{accountName}:{DateTime.Now:yyyyMM}");
-            RedisHelper.IncrBy($":parse_total:{accountName}:{DateTime.Now:yyyyMMdd}");
-            RedisHelper.IncrBy($":parse_total:{accountName}:{DateTime.Now:yyyyMMddHH}");
-
-
-            string result = success ? "success" : "fail";
-            RedisHelper.IncrBy($":parse_total:{accountName}:{result}:{DateTime.Now:yyyyMM}");
-            RedisHelper.IncrBy($":parse_total:{accountName}:{result}:{DateTime.Now:yyyyMMdd}");
-            RedisHelper.IncrBy($":parse_total:{accountName}:{result}:{DateTime.Now:yyyyMMddHH}");
-
-
-            if (!string.IsNullOrEmpty(message))
-            {
-                RedisHelper.SAdd($":parse_total:{accountName}:message:{DateTime.Now:yyyyMM}", message);
-                RedisHelper.SAdd($":parse_total:{accountName}:message:{DateTime.Now:yyyyMMdd}", message);
-                RedisHelper.SAdd($":parse_total:{accountName}:message:{DateTime.Now:yyyyMMddHH}", message);
-
-                RedisHelper.IncrBy($":parse_total:{accountName}:{message}:{DateTime.Now:yyyyMM}");
-                RedisHelper.IncrBy($":parse_total:{accountName}:{message}:{DateTime.Now:yyyyMMdd}");
-                RedisHelper.IncrBy($":parse_total:{accountName}:{message}:{DateTime.Now:yyyyMMddHH}");
-                RedisHelper.IncrBy($":parse_total:{accountName}:message:{message}:{DateTime.Now:yyyyMM}");
-                RedisHelper.IncrBy($":parse_total:{accountName}:message:{message}:{DateTime.Now:yyyyMMdd}");
-                RedisHelper.IncrBy($":parse_total:{accountName}:message:{message}:{DateTime.Now:yyyyMMddHH}");
-            }
-
-            if (!string.IsNullOrEmpty(reason))
-            {
-                RedisHelper.SAdd($":parse_total:{accountName}:reason:{DateTime.Now:yyyyMM}", reason);
-                RedisHelper.SAdd($":parse_total:{accountName}:reason:{DateTime.Now:yyyyMMdd}", reason);
-                RedisHelper.SAdd($":parse_total:{accountName}:reason:{DateTime.Now:yyyyMMddHH}", reason);
-
-                RedisHelper.IncrBy($":parse_total:{accountName}:{reason}:{DateTime.Now:yyyyMM}");
-                RedisHelper.IncrBy($":parse_total:{accountName}:{reason}:{DateTime.Now:yyyyMMdd}");
-                RedisHelper.IncrBy($":parse_total:{accountName}:{reason}:{DateTime.Now:yyyyMMddHH}");
-
-                RedisHelper.IncrBy($":parse_total:{accountName}:reason:{reason}:{DateTime.Now:yyyyMM}");
-                RedisHelper.IncrBy($":parse_total:{accountName}:reason:{reason}:{DateTime.Now:yyyyMMdd}");
-                RedisHelper.IncrBy($":parse_total:{accountName}:reason:{reason}:{DateTime.Now:yyyyMMddHH}");
-            }
-        }
-
-        public static async Task<int> GetTotalAsync(string keyname, bool all_node = true)
-        {
-            try
-            {
-                var tasks = EndPointCore.List()
-                    .Where(node => node.is_public_api && !string.IsNullOrEmpty(node.redis_server))
-                    .Where(node => all_node || (CenterHub.IsCenter ? !node.is_coupon_api : node.is_coupon_api))
-                    .Select(async node =>
-                    {
-                        try
-                        {
-
-#if DEBUG
-                            switch (node.name)
-                            {
-
-                                case "bj":
-                                    node.redis_server = "101.200.152.61:6379,password=pKBiS4ka2IpXayIdcx00,defaultDatabase=0,idleTimeout=20000,preheat=3,tryit=2,ssl=false,prefix=webhook";
-                                    break;
-                                case "gz":
-                                    node.redis_server = "8.138.110.158:6379,password=pKBiS4ka2IpXayIdcx00,defaultDatabase=0,idleTimeout=20000,preheat=3,tryit=2,ssl=false,prefix=webhook";
-                                    break;
-                                case "coupon1":
-                                    node.redis_server = "123.56.185.166:6379,password=pKBiS4ka2IpXayIdcx00,defaultDatabase=0,idleTimeout=20000,preheat=3,tryit=2,ssl=false,prefix=coupon";
-                                    break;
-                            }
-
-#endif
-
-
-                            var redisServer = EndPointCore.GetRedisServer(node);
-                            if (string.IsNullOrEmpty(redisServer))
-                                return 0;
-
-                            await using var scope = await RedisClientFactory.CreateScopeAsync(redisServer);
-                            return await scope.Client.GetAsync<int>(keyname);
-                        }
-                        catch (Exception)
-                        {
-                            return 0;
-                        }
-                    });
-
-                var results = await Task.WhenAll(tasks);
-                return results.Sum();
-            }
-            catch (Exception)
-            {
-                return 0;
-            }
-        }
-
-        public static async Task<string[]> GetTotalKeysAsync(string keyname, bool all_node = true)
-        {
-
-            var tasks = EndPointCore.List()
-            .Where(node => node.is_public_api && !string.IsNullOrEmpty(node.redis_server))
-            .Where(node => all_node || (CenterHub.IsCenter ? !node.is_coupon_api : node.is_coupon_api))
-            .Select(async node =>
-            {
-                try
-                {
-                    try
-                    {
-                        var redisServer = EndPointCore.GetRedisServer(node);
-                        if (string.IsNullOrEmpty(redisServer)) return [];
-
-                        using var scope = await RedisClientFactory.CreateScopeAsync(redisServer);
-                        return await scope.Client.SMembersAsync<string>(keyname);
-                    }
-                    catch (Exception ex) { }
-                    return [];
-                }
-                catch (Exception ex)
-                {
-                    return [];
-                }
-            });
-
-            var results = await Task.WhenAll(tasks);
-            return results.SelectMany(x => x).Distinct().ToArray();
-
-        }
-
-
-    }
-}
+
+
+
+                    //Task.Run(() => RunTaskWithLogging(() => task_insert_tk_logs(limit, redis), "task_insert_tk_logs")),
+                    //Task.Run(() => RunTaskWithLogging(() => task_insert_parse_tb_logs(limit, redis), "task_insert_parse_tb_logs")),
+                    //Task.Run(() => RunTaskWithLogging(() => task_insert_parse_jd_logs(limit, redis), "task_insert_parse_jd_logs")),
+                    //Task.Run(() => RunTaskWithLogging(() => task_insert_parse_pdd_logs(limit, redis), "task_insert_parse_pdd_logs")),
+                    //Task.Run(() => RunTaskWithLogging(() => task_insert_parse_dy_logs(limit, redis), "task_insert_parse_dy_logs")),
+                    //Task.Run(() => RunTaskWithLogging(() => task_insert_parse_tool_logs(limit, redis), "task_insert_parse_tool_logs")),
+                    //Task.Run(() => RunTaskWithLogging(() => task_insert_parse_deeplink_logs(limit, redis), "task_insert_parse_deeplink_logs")),
+                    //Task.Run(() => RunTaskWithLogging(() => task_insert_parse_coupon_logs(limit, redis), "task_insert_parse_coupon_logs")),
+                    //Task.Run(() => RunTaskWithLogging(() => task_insert_parse_cps_logs(limit, redis), "task_insert_parse_cps_logs")),
+                    //Task.Run(() => RunTaskWithLogging(() => task_insert_promotion_img_logs(limit, redis), "task_insert_promotion_img_logs")),
+                    //Task.Run(() => RunTaskWithLogging(() => task_insert_parse_ks_logs(limit, redis), "task_insert_parse_ks_logs"))
+                };
+
+                // 等待所有任务完成
+                Task.WhenAll(tasks).Wait();//252行
+
+                // 计算所有任务的结果总和
+                total = tasks.Select(t => t.Result).Sum();
+
+                //transaction.Commit();
+            }
+            catch (Exception ex)
+            {
+                //transaction.Rollback();
+
+
+                // 构建异常详细信息字符串
+                string detailedError = $"{ex.Message}\n" +
+                                       $"堆栈跟踪: {ex.StackTrace}\n";
+
+                if (ex.InnerException != null)
+                {
+                    detailedError += $"内部异常: {ex.InnerException.Message}\n" +
+                                     $"内部堆栈跟踪: {ex.InnerException.StackTrace}\n";
+                }
+
+                // 如果异常包含其他数据,也可以记录下来
+                if (ex.Data != null && ex.Data.Count > 0)
+                {
+                    detailedError += "附加数据:\n";
+                    foreach (var key in ex.Data.Keys)
+                    {
+                        detailedError += $"{key}: {ex.Data[key]}\n";
+                    }
+                }
+
+
+                _ = new LoggerLibrary("database_error", "parse_log")
+                    .Info(detailedError)
+                    .SaveAsync();
+
+
+                NotifyCore.Notify(new NifyMessage
+                {
+                    message = $"【Exception】\n{detailedError}",
+                    priority = NifyMessagePriority.high,
+                    tags = ["red_circle"]
+                });
+            }
+            finally
+            {
+                //connection.Close();
+            }
+            return total;
+        }
+
+
+
+        private static async Task<int> RunTaskWithLoggingAsync(Func<Task<int>> taskFunc, string taskName)
+        {
+            LoggerLibrary log = new LoggerLibrary("debug", "BatchInsertLogDB"); // 创建日志对象
+            Stopwatch stopwatch = new Stopwatch();
+            stopwatch.Start();
+            int taskTotal = await taskFunc();
+            stopwatch.Stop();
+            log.Info($"{taskName} 耗时: {stopwatch.ElapsedMilliseconds} ms,\t插入记录数: {taskTotal}");
+            log.SaveAsync();
+            return taskTotal;
+        }
+
+        /// <summary>
+        /// 过时方法 随时删除
+        /// </summary>
+        /// <param name="channel"></param>
+        /// <param name="accountId"></param>
+        /// <param name="accountName"></param>
+        /// <param name="success"></param>
+        /// <param name="message"></param>
+        /// <param name="reason"></param>
+        private static void saveCache(string channel, int accountId, string accountName, bool success, string message, string reason)
+        {
+            saveAccountCache("all", success, message, reason);
+            saveAccountCache($"{channel}", success, message, reason);
+            saveAccountCache($"{accountName}", success, message, reason);
+            if (accountId != 0)
+            {
+                //todo 放着跑两天,要将读取的地方改成读取accountid
+                saveAccountCache($"{channel}_{accountId}", success, message, reason);
+            }
+        }
+
+        private static void saveAccountCache(string accountName, bool success, string message, string reason)
+        {
+            RedisHelper.IncrBy($":total:{accountName}:{DateTime.Now:yyyyMM}");
+            RedisHelper.IncrBy($":total:{accountName}:{DateTime.Now:yyyyMMdd}");
+            RedisHelper.IncrBy($":total:{accountName}:{DateTime.Now:yyyyMMddHH}");
+
+
+            string result = success ? "success" : "fail";
+            RedisHelper.IncrBy($":total:{accountName}:{result}:{DateTime.Now:yyyyMM}");
+            RedisHelper.IncrBy($":total:{accountName}:{result}:{DateTime.Now:yyyyMMdd}");
+            RedisHelper.IncrBy($":total:{accountName}:{result}:{DateTime.Now:yyyyMMddHH}");
+
+
+            if (!string.IsNullOrEmpty(message))
+            {
+                RedisHelper.SAdd($":total:{accountName}:message:{DateTime.Now:yyyyMM}", message);
+                RedisHelper.SAdd($":total:{accountName}:message:{DateTime.Now:yyyyMMdd}", message);
+                RedisHelper.SAdd($":total:{accountName}:message:{DateTime.Now:yyyyMMddHH}", message);
+
+                RedisHelper.IncrBy($":total:{accountName}:{message}:{DateTime.Now:yyyyMM}");
+                RedisHelper.IncrBy($":total:{accountName}:{message}:{DateTime.Now:yyyyMMdd}");
+                RedisHelper.IncrBy($":total:{accountName}:{message}:{DateTime.Now:yyyyMMddHH}");
+
+                RedisHelper.IncrBy($":total:{accountName}:message:{message}:{DateTime.Now:yyyyMM}");
+                RedisHelper.IncrBy($":total:{accountName}:message:{message}:{DateTime.Now:yyyyMMdd}");
+                RedisHelper.IncrBy($":total:{accountName}:message:{message}:{DateTime.Now:yyyyMMddHH}");
+            }
+
+            if (!string.IsNullOrEmpty(reason))
+            {
+                RedisHelper.SAdd($":total:{accountName}:reason:{DateTime.Now:yyyyMM}", reason);
+                RedisHelper.SAdd($":total:{accountName}:reason:{DateTime.Now:yyyyMMdd}", reason);
+                RedisHelper.SAdd($":total:{accountName}:reason:{DateTime.Now:yyyyMMddHH}", reason);
+
+                RedisHelper.IncrBy($":total:{accountName}:{reason}:{DateTime.Now:yyyyMM}");
+                RedisHelper.IncrBy($":total:{accountName}:{reason}:{DateTime.Now:yyyyMMdd}");
+                RedisHelper.IncrBy($":total:{accountName}:{reason}:{DateTime.Now:yyyyMMddHH}");
+
+
+                RedisHelper.IncrBy($":total:{accountName}:reason:{reason}:{DateTime.Now:yyyyMM}");
+                RedisHelper.IncrBy($":total:{accountName}:reason:{reason}:{DateTime.Now:yyyyMMdd}");
+                RedisHelper.IncrBy($":total:{accountName}:reason:{reason}:{DateTime.Now:yyyyMMddHH}");
+            }
+
+        }
+
+        private static async Task saveUnionCouponParseCacheAsync(TkDataDTO data)
+        {
+            string cacheKey = $":cache:parse:{data.ip}_{data.oaid}_{data.itemId}";
+            await EndPointCore.ProcessEndPointNodesAsync(node =>
+            {
+                if (!node.is_coupon_api) return Task.CompletedTask;
+                if (string.IsNullOrEmpty(node.redis_server)) return Task.CompletedTask;
+
+                var redis = RedisClientManager.GetRedisClient(node.redis_server);
+                redis.Set(cacheKey, 1, 2 * 86400);
+                return Task.CompletedTask;
+            });
+        }
+        private async static Task saveClientRequestTotalAsync(TkChannelEnum channel, string ip, string oaid)
+        {
+            await saveClientRequestTotalAsync(channel.ToString(), ip, oaid);
+        }
+        private static async Task saveClientRequestTotalAsync(string channel, string ip, string oaid)
+        {
+            await EndPointCore.ProcessEndPointNodesAsync(node =>
+            {
+                if (string.IsNullOrEmpty(node.redis_server)) return Task.CompletedTask;
+                if (!node.is_public_api) return Task.CompletedTask;
+
+#if DEBUG
+                switch (node.name)
+                {
+
+                    case "bj":
+                        node.redis_server = "101.200.152.61:6379,password=pKBiS4ka2IpXayIdcx00,defaultDatabase=0,idleTimeout=20000,preheat=3,tryit=2,ssl=false,prefix=webhook";
+                        break;
+                    case "gz":
+                        node.redis_server = "8.138.110.158:6379,password=pKBiS4ka2IpXayIdcx00,defaultDatabase=0,idleTimeout=20000,preheat=3,tryit=2,ssl=false,prefix=webhook";
+                        break;
+                    case "coupon1":
+                        node.redis_server = "123.56.185.166:6379,password=pKBiS4ka2IpXayIdcx00,defaultDatabase=0,idleTimeout=20000,preheat=3,tryit=2,ssl=false,prefix=coupon";
+                        break;
+                    default: return Task.CompletedTask;
+                }
+
+#endif
+
+
+                var redis = RedisClientManager.GetRedisClient(node.redis_server);
+
+                string cacheKey = $":cache:{channel}:ip:{DateTime.Now:yyyyMMdd}:{ip}";
+                redis.IncrBy(cacheKey);
+                redis.Expire(cacheKey, 86400);
+
+                if (!string.IsNullOrEmpty(oaid))
+                {
+                    cacheKey = $":cache:{channel}:oaid:{DateTime.Now:yyyyMMdd}:{oaid}";
+                    redis.IncrBy(cacheKey);
+                    redis.Expire(cacheKey, 86400);
+                }
+                return Task.CompletedTask;
+            });
+
+        }
+        public static bool InBlacklist(string blacklist, string oaid)
+        {
+            if (string.IsNullOrEmpty(blacklist)) return false;
+
+            var arr = blacklist.Split(new[] { "\r\n" }, StringSplitOptions.None)
+                   .Select(s => s.Trim()).ToArray();
+            return arr.Contains(oaid);
+        }
+
+
+        public static int getClientRequestTotalByOAID(TkChannelEnum channel, string oaid)
+        {
+            if (string.IsNullOrEmpty(oaid)) return 0;
+            string cacheKey = $":cache:{channel}:oaid:{DateTime.Now:yyyyMMdd}:{oaid}";
+            return RedisHelper.Get<int>(cacheKey);
+        }
+        public static int getClientRequestTotalByIp(TkChannelEnum channel, string ip)
+        {
+            if (string.IsNullOrEmpty(ip)) return 0;
+            string cacheKey = $":cache:{channel}:ip:{DateTime.Now:yyyyMMdd}:{ip}";
+            return RedisHelper.Get<int>(cacheKey);
+        }
+        public static int getClientRequestTotalByOAID(string channel, string oaid)
+        {
+            if (string.IsNullOrEmpty(oaid)) return 0;
+            string cacheKey = $":cache:{channel}:oaid:{DateTime.Now:yyyyMMdd}:{oaid}";
+            return RedisHelper.Get<int>(cacheKey);
+        }
+        public static int getClientRequestTotalByIp(string channel, string ip)
+        {
+            if (string.IsNullOrEmpty(ip)) return 0;
+            string cacheKey = $":cache:{channel}:ip:{DateTime.Now:yyyyMMdd}:{ip}";
+            return RedisHelper.Get<int>(cacheKey);
+        }
+
+
+        private static void saveClientRequestTotal(CpsChannelEnum channel, string ip, string oaid)
+        {
+            string cacheKey = $":cache:cps_{channel}:ip:{DateTime.Now:yyyyMMdd}:{ip}";
+            RedisHelper.IncrBy(cacheKey);
+            RedisHelper.Expire(cacheKey, 86400);
+
+            if (!string.IsNullOrEmpty(oaid))
+            {
+                cacheKey = $":cache:cps_{channel}:oaid:{DateTime.Now:yyyyMMdd}:{oaid}";
+                RedisHelper.IncrBy(cacheKey);
+                RedisHelper.Expire(cacheKey, 86400);
+            }
+        }
+        public static int getClientRequestTotalByOAID(CpsChannelEnum channel, string oaid)
+        {
+            if (string.IsNullOrEmpty(oaid)) return 0;
+            string cacheKey = $":cache:cps_{channel}:oaid:{DateTime.Now:yyyyMMdd}:{oaid}";
+            return RedisHelper.Get<int>(cacheKey);
+        }
+        public static int getClientRequestTotalByIp(CpsChannelEnum channel, string ip)
+        {
+            if (string.IsNullOrEmpty(ip)) return 0;
+            string cacheKey = $":cache:cps_{channel}:ip:{DateTime.Now:yyyyMMdd}:{ip}";
+            return RedisHelper.Get<int>(cacheKey);
+        }
+
+
+        private static async Task SaveParseCacheAsync(string channel, int accountId, string accountName,
+            bool success, string message, string reason, string deeplink)
+        {
+            string dp_flag = deeplink switch
+            {
+                "" => "none",
+                "tbopen://m.taobao.com/tbopen/index.html" or
+                "pinduoduo://com.xunmeng.pinduoduo/" or
+                "snssdk1128://feed?refer=web" or
+                "bdnetdisk://n/action.EXTERNAL_ACTIVITY" or
+                "openapp.jdmobile://virtual?params=" or "openapp.jdmobile://" => "home",
+                _ => success ? "success" : "fail",
+            };
+
+            //关于dp的缓存
+            await SaveParseAccountCacheAsync($"dp_{dp_flag}:all", success, message, reason);
+            await SaveParseAccountCacheAsync($"dp_{dp_flag}:{channel}", success, message, reason);
+            await SaveParseAccountCacheAsync($"dp_{dp_flag}:{accountName}", success, message, reason);
+            if (accountId != 0)
+            {
+                await SaveParseAccountCacheAsync($"dp_{dp_flag}:{channel}_{accountId}", success, message, reason);
+            }
+
+            await SaveParseAccountCacheAsync("all", success, message, reason);
+            await SaveParseAccountCacheAsync($"dp_channel:{channel}", success, message, reason);
+            await SaveParseAccountCacheAsync($"{channel}", success, message, reason);
+            await SaveParseAccountCacheAsync($"{accountName}", success, message, reason);
+            if (accountId != 0)
+            {
+                //todo 放着跑两天,要将读取的地方改成读取accountid
+                await SaveParseAccountCacheAsync($"{channel}_{accountId}", success, message, reason);
+            }
+        }
+
+        private static async Task SaveParseAccountCacheAsync(string accountName, bool success,
+            string message, string reason)
+        {
+            await RedisKit.IncrByAsync($":parse_total:{accountName}:{DateTime.Now:yyyyMM}");
+            await RedisKit.IncrByAsync($":parse_total:{accountName}:{DateTime.Now:yyyyMMdd}");
+            await RedisKit.IncrByAsync($":parse_total:{accountName}:{DateTime.Now:yyyyMMddHH}");
+
+            string result = success ? "success" : "fail";
+            await RedisKit.IncrByAsync($":parse_total:{accountName}:{result}:{DateTime.Now:yyyyMM}");
+            await RedisKit.IncrByAsync($":parse_total:{accountName}:{result}:{DateTime.Now:yyyyMMdd}");
+            await RedisKit.IncrByAsync($":parse_total:{accountName}:{result}:{DateTime.Now:yyyyMMddHH}");
+
+            if (!string.IsNullOrEmpty(message))
+            {
+                await RedisKit.SAddAsync($":parse_total:{accountName}:message:{DateTime.Now:yyyyMM}", message);
+                await RedisKit.SAddAsync($":parse_total:{accountName}:message:{DateTime.Now:yyyyMMdd}", message);
+                await RedisKit.SAddAsync($":parse_total:{accountName}:message:{DateTime.Now:yyyyMMddHH}", message);
+
+                await RedisKit.IncrByAsync($":parse_total:{accountName}:{message}:{DateTime.Now:yyyyMM}");
+                await RedisKit.IncrByAsync($":parse_total:{accountName}:{message}:{DateTime.Now:yyyyMMdd}");
+                await RedisKit.IncrByAsync($":parse_total:{accountName}:{message}:{DateTime.Now:yyyyMMddHH}");
+                await RedisKit.IncrByAsync($":parse_total:{accountName}:message:{message}:{DateTime.Now:yyyyMM}");
+                await RedisKit.IncrByAsync($":parse_total:{accountName}:message:{message}:{DateTime.Now:yyyyMMdd}");
+                await RedisKit.IncrByAsync($":parse_total:{accountName}:message:{message}:{DateTime.Now:yyyyMMddHH}");
+            }
+
+            if (!string.IsNullOrEmpty(reason))
+            {
+                await RedisKit.SAddAsync($":parse_total:{accountName}:reason:{DateTime.Now:yyyyMM}", reason);
+                await RedisKit.SAddAsync($":parse_total:{accountName}:reason:{DateTime.Now:yyyyMMdd}", reason);
+                await RedisKit.SAddAsync($":parse_total:{accountName}:reason:{DateTime.Now:yyyyMMddHH}", reason);
+
+                await RedisKit.IncrByAsync($":parse_total:{accountName}:{reason}:{DateTime.Now:yyyyMM}");
+                await RedisKit.IncrByAsync($":parse_total:{accountName}:{reason}:{DateTime.Now:yyyyMMdd}");
+                await RedisKit.IncrByAsync($":parse_total:{accountName}:{reason}:{DateTime.Now:yyyyMMddHH}");
+
+                await RedisKit.IncrByAsync($":parse_total:{accountName}:reason:{reason}:{DateTime.Now:yyyyMM}");
+                await RedisKit.IncrByAsync($":parse_total:{accountName}:reason:{reason}:{DateTime.Now:yyyyMMdd}");
+                await RedisKit.IncrByAsync($":parse_total:{accountName}:reason:{reason}:{DateTime.Now:yyyyMMddHH}");
+            }
+        }
+
+
+
+        private static void saveParseCache(string channel, int accountId, string accountName,
+            bool success, string message, string reason, string deeplink)
+        {
+            string dp_flag = deeplink switch
+            {
+                "" => "none",
+                "tbopen://m.taobao.com/tbopen/index.html" or
+                "pinduoduo://com.xunmeng.pinduoduo/" or
+                "snssdk1128://feed?refer=web" or
+                "bdnetdisk://n/action.EXTERNAL_ACTIVITY" or
+                "openapp.jdmobile://virtual?params=" or "openapp.jdmobile://" => "home",
+                _ => success ? "success" : "fail",
+            };
+
+            //关于dp的缓存
+            saveParseAccountCache($"dp_{dp_flag}:all", success, message, reason);
+            saveParseAccountCache($"dp_{dp_flag}:{channel}", success, message, reason);
+            saveParseAccountCache($"dp_{dp_flag}:{accountName}", success, message, reason);
+            if (accountId != 0)
+            {
+                saveParseAccountCache($"dp_{dp_flag}:{channel}_{accountId}", success, message, reason);
+            }
+
+            saveParseAccountCache("all", success, message, reason);
+            saveParseAccountCache($"{channel}", success, message, reason);
+            saveParseAccountCache($"{accountName}", success, message, reason);
+            if (accountId != 0)
+            {
+                //todo 放着跑两天,要将读取的地方改成读取accountid
+                saveParseAccountCache($"{channel}_{accountId}", success, message, reason);
+            }
+        }
+        private static void saveParseAccountCache(string accountName, bool success,
+            string message, string reason)
+        {
+
+            RedisHelper.IncrBy($":parse_total:{accountName}:{DateTime.Now:yyyyMM}");
+            RedisHelper.IncrBy($":parse_total:{accountName}:{DateTime.Now:yyyyMMdd}");
+            RedisHelper.IncrBy($":parse_total:{accountName}:{DateTime.Now:yyyyMMddHH}");
+
+
+            string result = success ? "success" : "fail";
+            RedisHelper.IncrBy($":parse_total:{accountName}:{result}:{DateTime.Now:yyyyMM}");
+            RedisHelper.IncrBy($":parse_total:{accountName}:{result}:{DateTime.Now:yyyyMMdd}");
+            RedisHelper.IncrBy($":parse_total:{accountName}:{result}:{DateTime.Now:yyyyMMddHH}");
+
+
+            if (!string.IsNullOrEmpty(message))
+            {
+                RedisHelper.SAdd($":parse_total:{accountName}:message:{DateTime.Now:yyyyMM}", message);
+                RedisHelper.SAdd($":parse_total:{accountName}:message:{DateTime.Now:yyyyMMdd}", message);
+                RedisHelper.SAdd($":parse_total:{accountName}:message:{DateTime.Now:yyyyMMddHH}", message);
+
+                RedisHelper.IncrBy($":parse_total:{accountName}:{message}:{DateTime.Now:yyyyMM}");
+                RedisHelper.IncrBy($":parse_total:{accountName}:{message}:{DateTime.Now:yyyyMMdd}");
+                RedisHelper.IncrBy($":parse_total:{accountName}:{message}:{DateTime.Now:yyyyMMddHH}");
+                RedisHelper.IncrBy($":parse_total:{accountName}:message:{message}:{DateTime.Now:yyyyMM}");
+                RedisHelper.IncrBy($":parse_total:{accountName}:message:{message}:{DateTime.Now:yyyyMMdd}");
+                RedisHelper.IncrBy($":parse_total:{accountName}:message:{message}:{DateTime.Now:yyyyMMddHH}");
+            }
+
+            if (!string.IsNullOrEmpty(reason))
+            {
+                RedisHelper.SAdd($":parse_total:{accountName}:reason:{DateTime.Now:yyyyMM}", reason);
+                RedisHelper.SAdd($":parse_total:{accountName}:reason:{DateTime.Now:yyyyMMdd}", reason);
+                RedisHelper.SAdd($":parse_total:{accountName}:reason:{DateTime.Now:yyyyMMddHH}", reason);
+
+                RedisHelper.IncrBy($":parse_total:{accountName}:{reason}:{DateTime.Now:yyyyMM}");
+                RedisHelper.IncrBy($":parse_total:{accountName}:{reason}:{DateTime.Now:yyyyMMdd}");
+                RedisHelper.IncrBy($":parse_total:{accountName}:{reason}:{DateTime.Now:yyyyMMddHH}");
+
+                RedisHelper.IncrBy($":parse_total:{accountName}:reason:{reason}:{DateTime.Now:yyyyMM}");
+                RedisHelper.IncrBy($":parse_total:{accountName}:reason:{reason}:{DateTime.Now:yyyyMMdd}");
+                RedisHelper.IncrBy($":parse_total:{accountName}:reason:{reason}:{DateTime.Now:yyyyMMddHH}");
+            }
+        }
+
+        public static async Task<int> GetTotalAsync(string keyname, bool all_node = true)
+        {
+            try
+            {
+                var tasks = EndPointCore.List()
+                    .Where(node => node.is_public_api && !string.IsNullOrEmpty(node.redis_server))
+                    .Where(node => all_node || (CenterHub.IsCenter ? !node.is_coupon_api : node.is_coupon_api))
+                    .Select(async node =>
+                    {
+                        try
+                        {
+
+#if DEBUG
+                            switch (node.name)
+                            {
+
+                                case "bj":
+                                    node.redis_server = "101.200.152.61:6379,password=pKBiS4ka2IpXayIdcx00,defaultDatabase=0,idleTimeout=20000,preheat=3,tryit=2,ssl=false,prefix=webhook";
+                                    break;
+                                case "gz":
+                                    node.redis_server = "8.138.110.158:6379,password=pKBiS4ka2IpXayIdcx00,defaultDatabase=0,idleTimeout=20000,preheat=3,tryit=2,ssl=false,prefix=webhook";
+                                    break;
+                                case "coupon1":
+                                    node.redis_server = "123.56.185.166:6379,password=pKBiS4ka2IpXayIdcx00,defaultDatabase=0,idleTimeout=20000,preheat=3,tryit=2,ssl=false,prefix=coupon";
+                                    break;
+                            }
+
+#endif
+
+
+                            var redisServer = EndPointCore.GetRedisServer(node);
+                            if (string.IsNullOrEmpty(redisServer))
+                                return 0;
+
+                            await using var scope = await RedisClientFactory.CreateScopeAsync(redisServer);
+                            return await scope.Client.GetAsync<int>(keyname);
+                        }
+                        catch (Exception)
+                        {
+                            return 0;
+                        }
+                    });
+
+                var results = await Task.WhenAll(tasks);
+                return results.Sum();
+            }
+            catch (Exception)
+            {
+                return 0;
+            }
+        }
+
+        public static async Task<string[]> GetTotalKeysAsync(string keyname, bool all_node = true)
+        {
+
+            var tasks = EndPointCore.List()
+            .Where(node => node.is_public_api && !string.IsNullOrEmpty(node.redis_server))
+            .Where(node => all_node || (CenterHub.IsCenter ? !node.is_coupon_api : node.is_coupon_api))
+            .Select(async node =>
+            {
+                try
+                {
+                    try
+                    {
+                        var redisServer = EndPointCore.GetRedisServer(node);
+                        if (string.IsNullOrEmpty(redisServer)) return [];
+
+                        using var scope = await RedisClientFactory.CreateScopeAsync(redisServer);
+                        return await scope.Client.SMembersAsync<string>(keyname);
+                    }
+                    catch (Exception ex) { }
+                    return [];
+                }
+                catch (Exception ex)
+                {
+                    return [];
+                }
+            });
+
+            var results = await Task.WhenAll(tasks);
+            return results.SelectMany(x => x).Distinct().ToArray();
+
+        }
+
+
+    }
+}

+ 204 - 204
molilian.core/Core/log/coupon.cs

@@ -1,204 +1,204 @@
-using dodohold.core;
-using CSRedis;
-using System.Data;
-
-namespace molilian.core
-{
-    public partial class TkLogCore
-    {
-        static string queue_coupon_key = "queue:coupon_logs";
-
-
-        public static async Task<int> InsertCouponLogAsync(int limit, YunhuiKit.RedisClient redis)
-        {
-            int total = 0;
-            using var connection = DBContext.GetOpenConnection();
-            connection.Open();
-            using var transaction = connection.BeginTransaction();
-            try
-            {
-                for (int i = 0; i < limit; i++)
-                {
-                    var data = await redis.LPopAsync<UnionCouponDTO>(queue_coupon_key);
-                    if (data == null) break;
-
-
-                    if (TestParseCore.InWhitelist(data.ip, data.oaid))
-                    {
-                        var test_data = data.Convert2Json().Convert2Object<TestUnionCouponDTO>();
-                        connection.Insert(test_data);
-                    }
-                    else
-                    {
-                        connection.Insert(data);
-                        if (data.success)
-                        {
-                            var success_data = data.Convert2Json().Convert2Object<SuccessUnionCouponDTO>();
-                            connection.Insert(success_data);
-                        }
-                    }
-                    total++;
-                }
-                transaction.Commit();
-            }
-            catch (Exception ex)
-            {
-                transaction.Rollback();
-                new LoggerLibrary("TkLogCore_error", "task_insert_parse_coupon_logs")
-                      .Info(ex.Message, ex.StackTrace)
-                      .SaveAsync();
-                throw;
-            }
-            finally
-            {
-                connection.Close();
-            }
-            return total;
-        }
-        public static int task_insert_parse_coupon_logs(int limit, CSRedisClient redis)
-        {
-            int total = 0;
-            using var connection = DBContext.GetOpenConnection();
-            connection.Open();
-            using var transaction = connection.BeginTransaction();
-            try
-            {
-                for (int i = 0; i < limit; i++)
-                {
-                    var data = redis.LPop<UnionCouponDTO>(queue_coupon_key);
-                    if (data == null) break;
-
-                    if (TestParseCore.InWhitelist(data.ip, data.oaid))
-                    {
-                        var test_data = data.Convert2Json().Convert2Object<TestUnionCouponDTO>();
-                        connection.Insert(test_data);
-                    }
-                    else
-                    {
-                        connection.Insert(data);
-                        if (data.success)
-                        {
-                            var success_data = data.Convert2Json().Convert2Object<SuccessUnionCouponDTO>();
-                            connection.Insert(success_data);
-                        }
-                    }
-                    total++;
-                }
-                transaction.Commit();
-            }
-            catch (Exception ex)
-            {
-                transaction.Rollback();
-                new LoggerLibrary("TkLogCore_error", "task_insert_parse_coupon_logs")
-                      .Info(ex.Message, ex.StackTrace)
-                      .SaveAsync();
-                throw;
-            }
-            finally
-            {
-                connection.Close();
-            }
-            return total;
-        }
-        public static async Task CouponLogAsync(UnionCouponDTO response, AlimamaPlus? alimamaPlus = null)
-        {
-            try
-            {
-                var ts = DateTime.Now - response.create_time;
-                response.elapsedTime = (int)ts.TotalMilliseconds;
-                _ = RedisHelper.RPushAsync(queue_coupon_key, response);
-
-
-                if (!TestParseCore.InWhitelist(response.ip, response.oaid))
-                {
-                    saveCouponCache(response.channel.ToString(), response.accountId, response.accountName, response.success, response.message, response.reason);
-                }
-
-                if (response.success) await saveClientRequestTotalAsync(response.channel, response.ip, response.oaid);
-
-                var account = await TkPoolCore.ALLGetOneAsync(response.accountId);
-                bool is_hide = account.is_hide;
-
-                if (!response.success && "nologin".Equals(response.message))
-                {
-                    switch (response.channel)
-                    {
-                        case TkChannelEnum.tb:
-                            if (alimamaPlus != null)
-                            {
-                                (bool success, string message) = alimamaPlus.RenewCookie();
-                                if (success) return;
-                            }
-                            TkPoolCore.Disabled(response.accountId, response.accountName, $"{response.rawContent}", is_hide);
-                            break;
-                    }
-                }
-            }
-            catch (Exception ex)
-            {
-                _ = new LoggerLibrary("unionCoupon", "database_error")
-                .Info(response.rawContent)
-                .Info(response.Convert2Json())
-                .Info(ex.Message, ex.StackTrace)
-                .SaveAsync();
-            }
-        }
-        private static void saveCouponCache(string channel, int accountId, string accountName, bool success, string message, string reason)
-        {
-            saveAccountCouponCache("all", success, message, reason);
-            saveAccountCouponCache($"{channel}", success, message, reason);
-            if (accountId != 0)
-            {
-                saveAccountCouponCache($"{channel}_{accountId}", success, message, reason);
-            }
-        }
-
-        private static void saveAccountCouponCache(string accountName, bool success, string message, string reason)
-        {
-            RedisHelper.IncrBy($":coupon_total:{accountName}:{DateTime.Now:yyyyMM}");
-            RedisHelper.IncrBy($":coupon_total:{accountName}:{DateTime.Now:yyyyMMdd}");
-            RedisHelper.IncrBy($":coupon_total:{accountName}:{DateTime.Now:yyyyMMddHH}");
-
-
-            string result = success ? "success" : "fail";
-            RedisHelper.IncrBy($":coupon_total:{accountName}:{result}:{DateTime.Now:yyyyMM}");
-            RedisHelper.IncrBy($":coupon_total:{accountName}:{result}:{DateTime.Now:yyyyMMdd}");
-            RedisHelper.IncrBy($":coupon_total:{accountName}:{result}:{DateTime.Now:yyyyMMddHH}");
-
-
-            if (!string.IsNullOrEmpty(message))
-            {
-                RedisHelper.SAdd($":coupon_total:{accountName}:message:{DateTime.Now:yyyyMM}", message);
-                RedisHelper.SAdd($":coupon_total:{accountName}:message:{DateTime.Now:yyyyMMdd}", message);
-                RedisHelper.SAdd($":coupon_total:{accountName}:message:{DateTime.Now:yyyyMMddHH}", message);
-
-                RedisHelper.IncrBy($":coupon_total:{accountName}:{message}:{DateTime.Now:yyyyMM}");
-                RedisHelper.IncrBy($":coupon_total:{accountName}:{message}:{DateTime.Now:yyyyMMdd}");
-                RedisHelper.IncrBy($":coupon_total:{accountName}:{message}:{DateTime.Now:yyyyMMddHH}");
-                RedisHelper.IncrBy($":coupon_total:{accountName}:message:{message}:{DateTime.Now:yyyyMM}");
-                RedisHelper.IncrBy($":coupon_total:{accountName}:message:{message}:{DateTime.Now:yyyyMMdd}");
-                RedisHelper.IncrBy($":coupon_total:{accountName}:message:{message}:{DateTime.Now:yyyyMMddHH}");
-            }
-
-            if (!string.IsNullOrEmpty(reason))
-            {
-                RedisHelper.SAdd($":coupon_total:{accountName}:reason:{DateTime.Now:yyyyMM}", reason);
-                RedisHelper.SAdd($":coupon_total:{accountName}:reason:{DateTime.Now:yyyyMMdd}", reason);
-                RedisHelper.SAdd($":coupon_total:{accountName}:reason:{DateTime.Now:yyyyMMddHH}", reason);
-
-                RedisHelper.IncrBy($":coupon_total:{accountName}:{reason}:{DateTime.Now:yyyyMM}");
-                RedisHelper.IncrBy($":coupon_total:{accountName}:{reason}:{DateTime.Now:yyyyMMdd}");
-                RedisHelper.IncrBy($":coupon_total:{accountName}:{reason}:{DateTime.Now:yyyyMMddHH}");
-                RedisHelper.IncrBy($":coupon_total:{accountName}:reason:{reason}:{DateTime.Now:yyyyMM}");
-                RedisHelper.IncrBy($":coupon_total:{accountName}:reason:{reason}:{DateTime.Now:yyyyMMdd}");
-                RedisHelper.IncrBy($":coupon_total:{accountName}:reason:{reason}:{DateTime.Now:yyyyMMddHH}");
-            }
-
-        }
-
-
-
-
-    }
-
-}
+using dodohold.core;
+using CSRedis;
+using System.Data;
+
+namespace molilian.core
+{
+    public partial class TkLogCore
+    {
+        static string queue_coupon_key = "queue:coupon_logs";
+
+
+        public static async Task<int> InsertCouponLogAsync(int limit, YunhuiKit.RedisClient redis)
+        {
+            int total = 0;
+            using var connection = DBContext.GetOpenConnection();
+            connection.Open();
+            using var transaction = connection.BeginTransaction();
+            try
+            {
+                for (int i = 0; i < limit; i++)
+                {
+                    var data = await redis.LPopAsync<UnionCouponDTO>(queue_coupon_key);
+                    if (data == null) break;
+
+
+                    if (TestParseCore.InWhitelist(data.ip, data.oaid))
+                    {
+                        var test_data = data.Convert2Json().Convert2Object<TestUnionCouponDTO>();
+                        connection.Insert(test_data);
+                    }
+                    else
+                    {
+                        connection.Insert(data);
+                        if (data.success)
+                        {
+                            var success_data = data.Convert2Json().Convert2Object<SuccessUnionCouponDTO>();
+                            connection.Insert(success_data);
+                        }
+                    }
+                    total++;
+                }
+                transaction.Commit();
+            }
+            catch (Exception ex)
+            {
+                transaction.Rollback();
+                new LoggerLibrary("TkLogCore_error", "task_insert_parse_coupon_logs")
+                      .Info(ex.Message, ex.StackTrace)
+                      .SaveAsync();
+                throw;
+            }
+            finally
+            {
+                connection.Close();
+            }
+            return total;
+        }
+        public static int task_insert_parse_coupon_logs(int limit, CSRedisClient redis)
+        {
+            int total = 0;
+            using var connection = DBContext.GetOpenConnection();
+            connection.Open();
+            using var transaction = connection.BeginTransaction();
+            try
+            {
+                for (int i = 0; i < limit; i++)
+                {
+                    var data = redis.LPop<UnionCouponDTO>(queue_coupon_key);
+                    if (data == null) break;
+
+                    if (TestParseCore.InWhitelist(data.ip, data.oaid))
+                    {
+                        var test_data = data.Convert2Json().Convert2Object<TestUnionCouponDTO>();
+                        connection.Insert(test_data);
+                    }
+                    else
+                    {
+                        connection.Insert(data);
+                        if (data.success)
+                        {
+                            var success_data = data.Convert2Json().Convert2Object<SuccessUnionCouponDTO>();
+                            connection.Insert(success_data);
+                        }
+                    }
+                    total++;
+                }
+                transaction.Commit();
+            }
+            catch (Exception ex)
+            {
+                transaction.Rollback();
+                new LoggerLibrary("TkLogCore_error", "task_insert_parse_coupon_logs")
+                      .Info(ex.Message, ex.StackTrace)
+                      .SaveAsync();
+                throw;
+            }
+            finally
+            {
+                connection.Close();
+            }
+            return total;
+        }
+        public static async Task CouponLogAsync(UnionCouponDTO response, AlimamaPlus? alimamaPlus = null)
+        {
+            try
+            {
+                var ts = DateTime.Now - response.create_time;
+                response.elapsedTime = (int)ts.TotalMilliseconds;
+                _ = RedisHelper.RPushAsync(queue_coupon_key, response);
+
+
+                if (!TestParseCore.InWhitelist(response.ip, response.oaid))
+                {
+                    saveCouponCache(response.channel.ToString(), response.accountId, response.accountName, response.success, response.message, response.reason);
+                }
+
+                if (response.success) await saveClientRequestTotalAsync(response.channel, response.ip, response.oaid);
+
+                var account = await TkPoolCore.ALLGetOneAsync(response.accountId);
+                bool is_hide = account.is_hide;
+
+                if (!response.success && "nologin".Equals(response.message))
+                {
+                    switch (response.channel)
+                    {
+                        case TkChannelEnum.tb:
+                            if (alimamaPlus != null)
+                            {
+                                (bool success, string message) = alimamaPlus.RenewCookie();
+                                if (success) return;
+                            }
+                            TkPoolCore.Disabled(response.accountId, response.accountName, $"{response.rawContent}", is_hide);
+                            break;
+                    }
+                }
+            }
+            catch (Exception ex)
+            {
+                _ = new LoggerLibrary("unionCoupon", "database_error")
+                .Info(response.rawContent)
+                .Info(response.Convert2Json())
+                .Info(ex.Message, ex.StackTrace)
+                .SaveAsync();
+            }
+        }
+        private static void saveCouponCache(string channel, int accountId, string accountName, bool success, string message, string reason)
+        {
+            saveAccountCouponCache("all", success, message, reason);
+            saveAccountCouponCache($"{channel}", success, message, reason);
+            if (accountId != 0)
+            {
+                saveAccountCouponCache($"{channel}_{accountId}", success, message, reason);
+            }
+        }
+
+        private static void saveAccountCouponCache(string accountName, bool success, string message, string reason)
+        {
+            RedisHelper.IncrBy($":coupon_total:{accountName}:{DateTime.Now:yyyyMM}");
+            RedisHelper.IncrBy($":coupon_total:{accountName}:{DateTime.Now:yyyyMMdd}");
+            RedisHelper.IncrBy($":coupon_total:{accountName}:{DateTime.Now:yyyyMMddHH}");
+
+
+            string result = success ? "success" : "fail";
+            RedisHelper.IncrBy($":coupon_total:{accountName}:{result}:{DateTime.Now:yyyyMM}");
+            RedisHelper.IncrBy($":coupon_total:{accountName}:{result}:{DateTime.Now:yyyyMMdd}");
+            RedisHelper.IncrBy($":coupon_total:{accountName}:{result}:{DateTime.Now:yyyyMMddHH}");
+
+
+            if (!string.IsNullOrEmpty(message))
+            {
+                RedisHelper.SAdd($":coupon_total:{accountName}:message:{DateTime.Now:yyyyMM}", message);
+                RedisHelper.SAdd($":coupon_total:{accountName}:message:{DateTime.Now:yyyyMMdd}", message);
+                RedisHelper.SAdd($":coupon_total:{accountName}:message:{DateTime.Now:yyyyMMddHH}", message);
+
+                RedisHelper.IncrBy($":coupon_total:{accountName}:{message}:{DateTime.Now:yyyyMM}");
+                RedisHelper.IncrBy($":coupon_total:{accountName}:{message}:{DateTime.Now:yyyyMMdd}");
+                RedisHelper.IncrBy($":coupon_total:{accountName}:{message}:{DateTime.Now:yyyyMMddHH}");
+                RedisHelper.IncrBy($":coupon_total:{accountName}:message:{message}:{DateTime.Now:yyyyMM}");
+                RedisHelper.IncrBy($":coupon_total:{accountName}:message:{message}:{DateTime.Now:yyyyMMdd}");
+                RedisHelper.IncrBy($":coupon_total:{accountName}:message:{message}:{DateTime.Now:yyyyMMddHH}");
+            }
+
+            if (!string.IsNullOrEmpty(reason))
+            {
+                RedisHelper.SAdd($":coupon_total:{accountName}:reason:{DateTime.Now:yyyyMM}", reason);
+                RedisHelper.SAdd($":coupon_total:{accountName}:reason:{DateTime.Now:yyyyMMdd}", reason);
+                RedisHelper.SAdd($":coupon_total:{accountName}:reason:{DateTime.Now:yyyyMMddHH}", reason);
+
+                RedisHelper.IncrBy($":coupon_total:{accountName}:{reason}:{DateTime.Now:yyyyMM}");
+                RedisHelper.IncrBy($":coupon_total:{accountName}:{reason}:{DateTime.Now:yyyyMMdd}");
+                RedisHelper.IncrBy($":coupon_total:{accountName}:{reason}:{DateTime.Now:yyyyMMddHH}");
+                RedisHelper.IncrBy($":coupon_total:{accountName}:reason:{reason}:{DateTime.Now:yyyyMM}");
+                RedisHelper.IncrBy($":coupon_total:{accountName}:reason:{reason}:{DateTime.Now:yyyyMMdd}");
+                RedisHelper.IncrBy($":coupon_total:{accountName}:reason:{reason}:{DateTime.Now:yyyyMMddHH}");
+            }
+
+        }
+
+
+
+
+    }
+
+}

+ 216 - 216
molilian.core/Core/log/cps.cs

@@ -1,216 +1,216 @@
-using dodohold.core;
-using CSRedis;
-using System.Data;
-using System.Threading.Channels;
-
-namespace molilian.core
-{
-    public partial class TkLogCore
-    {
-        static string queue_cps_key = "queue:cps_logs";
-
-
-        public static async Task<int> InsertCpsLogAsync(int limit, YunhuiKit.RedisClient redis)
-        {
-            int total = 0;
-            using var connection = DBContext.GetOpenConnection();
-            connection.Open();
-            using var transaction = connection.BeginTransaction();
-            try
-            {
-                for (int i = 0; i < limit; i++)
-                {
-                    var data = await redis.LPopAsync<UnionCpsDTO>(queue_cps_key);
-                    if (data == null) break;
-
-                    if (TestParseCore.InWhitelist(data.ip, data.oaid))
-                    {
-                        var test_data = data.Convert2Json().Convert2Object<TestUnionCpsDTO>();
-                        connection.Insert(test_data);
-                    }
-                    else
-                    {
-                        //按日保存数据
-                        string daily_table = $"tk_cps_logs_{data.create_time:yyyyMMdd}";
-                        save_cps_parse_logs(data, daily_table, connection, transaction);
-                    }
-                    total++;
-                }
-                transaction.Commit();
-            }
-            catch (Exception ex)
-            {
-                transaction.Rollback();
-                new LoggerLibrary("TkLogCore_error", "task_insert_parse_cps_logs")
-                      .Info(ex.Message, ex.StackTrace)
-                      .SaveAsync();
-                throw;
-            }
-            finally
-            {
-                connection.Close();
-            }
-            return total;
-        }
-
-        public static int task_insert_parse_cps_logs(int limit, CSRedisClient redis)
-        {
-            int total = 0;
-            using var connection = DBContext.GetOpenConnection();
-            connection.Open();
-            using var transaction = connection.BeginTransaction();
-            try
-            {
-                for (int i = 0; i < limit; i++)
-                {
-                    var data = redis.LPop<UnionCpsDTO>(queue_cps_key);
-                    if (data == null) break;
-
-                    if (TestParseCore.InWhitelist(data.ip, data.oaid))
-                    {
-                        var test_data = data.Convert2Json().Convert2Object<TestUnionCpsDTO>();
-                        connection.Insert(test_data);
-                    }
-                    else
-                    {
-                        //按日保存数据
-                        string daily_table = $"tk_cps_logs_{data.create_time:yyyyMMdd}";
-                        save_cps_parse_logs(data, daily_table, connection, transaction);
-                    }
-                    total++;
-                }
-                transaction.Commit();
-            }
-            catch (Exception ex)
-            {
-                transaction.Rollback();
-                new LoggerLibrary("TkLogCore_error", "task_insert_parse_cps_logs")
-                      .Info(ex.Message, ex.StackTrace)
-                      .SaveAsync();
-                throw;
-            }
-            finally
-            {
-                connection.Close();
-            }
-            return total;
-        }
-
-        private static int save_cps_parse_logs(UnionCpsDTO data, string tablename, IDbConnection connection, IDbTransaction transaction)
-        {
-            return new DBContext.Table(connection, tablename)
-                .Add("end_point", data.end_point)
-                .Add("channel", data.channel)
-                .Add("accountId", data.accountId)
-                .Add("accountName", data.accountName)
-                .Add("proxy_node", data.proxy_node)
-                .Add("rawContent", data.rawContent)
-                .Add("rawContent2", data.rawContent2)
-                .Add("success", data.success)
-                .Add("message", data.message)
-                .Add("reason", data.reason)
-                .Add("deeplink_url", data.deeplink_url)
-                .Add("create_time", data.create_time)
-                .Add("elapsedTime", data.elapsedTime)
-                .Add("ip", data.ip)
-                .Add("oaid", data.oaid)
-                .Add("token", data.token)
-                .Add("category", data.category)
-                .Add("pid", data.pid)
-                .Add("title", data.title)
-                .Add("sub_title", data.sub_title)
-                .Add("itemId", data.itemId)
-                .Add("couponAmount", data.couponAmount)
-                .Create(DBContext.InsertType.NORMAL, transaction);
-        }
-
-
-        public static async Task CpsLogAsync(UnionCpsDTO response, AlimamaPlus? alimamaPlus = null)
-        {
-            try
-            {
-                var ts = DateTime.Now - response.create_time;
-                response.elapsedTime = (int)ts.TotalMilliseconds;
-                _ = RedisHelper.RPushAsync(queue_cps_key, response);
-
-                if (response.success)
-                {
-                    await RiskControlCore.CallsIncrByAsync(TkChannelEnum.cps, response.accountId);
-                    await saveClientRequestTotalAsync(response.channel, response.ip, response.oaid);
-                }
-
-                if (!TestParseCore.InWhitelist(response.ip, response.oaid))
-                {
-                    saveCpsCache(response.channel, response.accountId,
-                        response.success, response.message, response.reason);
-                }
-            }
-            catch (Exception ex)
-            {
-                _ = new LoggerLibrary("unionCps", "database_error")
-                .Info(response.rawContent)
-                .Info(response.Convert2Json())
-                .Info(ex.Message, ex.StackTrace)
-                .SaveAsync();
-            }
-        }
-
-        private static void saveCpsCache(string channel, int accountId, bool success, string message, string reason)
-        {
-            saveAccountCpsCache("all", success, message, reason);
-            saveAccountCpsCache($"{channel}", success, message, reason);
-            if (accountId != 0)
-            {
-                saveAccountCpsCache($"{channel}_{accountId}", success, message, reason);
-            }
-        }
-
-        private static void saveAccountCpsCache(string flagName, bool success, string message, string reason)
-        {
-            RedisHelper.IncrBy($":cps_total:{flagName}:{DateTime.Now:yyyyMM}");
-            RedisHelper.IncrBy($":cps_total:{flagName}:{DateTime.Now:yyyyMMdd}");
-            RedisHelper.IncrBy($":cps_total:{flagName}:{DateTime.Now:yyyyMMddHH}");
-
-
-            string result = success ? "success" : "fail";
-            RedisHelper.IncrBy($":cps_total:{flagName}:{result}:{DateTime.Now:yyyyMM}");
-            RedisHelper.IncrBy($":cps_total:{flagName}:{result}:{DateTime.Now:yyyyMMdd}");
-            RedisHelper.IncrBy($":cps_total:{flagName}:{result}:{DateTime.Now:yyyyMMddHH}");
-
-
-            if (!string.IsNullOrEmpty(message))
-            {
-                RedisHelper.SAdd($":cps_total:{flagName}:message:{DateTime.Now:yyyyMM}", message);
-                RedisHelper.SAdd($":cps_total:{flagName}:message:{DateTime.Now:yyyyMMdd}", message);
-                RedisHelper.SAdd($":cps_total:{flagName}:message:{DateTime.Now:yyyyMMddHH}", message);
-
-                RedisHelper.IncrBy($":cps_total:{flagName}:{message}:{DateTime.Now:yyyyMM}");
-                RedisHelper.IncrBy($":cps_total:{flagName}:{message}:{DateTime.Now:yyyyMMdd}");
-                RedisHelper.IncrBy($":cps_total:{flagName}:{message}:{DateTime.Now:yyyyMMddHH}");
-                RedisHelper.IncrBy($":cps_total:{flagName}:message:{message}:{DateTime.Now:yyyyMM}");
-                RedisHelper.IncrBy($":cps_total:{flagName}:message:{message}:{DateTime.Now:yyyyMMdd}");
-                RedisHelper.IncrBy($":cps_total:{flagName}:message:{message}:{DateTime.Now:yyyyMMddHH}");
-            }
-
-            if (!string.IsNullOrEmpty(reason))
-            {
-                RedisHelper.SAdd($":cps_total:{flagName}:reason:{DateTime.Now:yyyyMM}", reason);
-                RedisHelper.SAdd($":cps_total:{flagName}:reason:{DateTime.Now:yyyyMMdd}", reason);
-                RedisHelper.SAdd($":cps_total:{flagName}:reason:{DateTime.Now:yyyyMMddHH}", reason);
-
-                RedisHelper.IncrBy($":cps_total:{flagName}:{reason}:{DateTime.Now:yyyyMM}");
-                RedisHelper.IncrBy($":cps_total:{flagName}:{reason}:{DateTime.Now:yyyyMMdd}");
-                RedisHelper.IncrBy($":cps_total:{flagName}:{reason}:{DateTime.Now:yyyyMMddHH}");
-                RedisHelper.IncrBy($":cps_total:{flagName}:reason:{reason}:{DateTime.Now:yyyyMM}");
-                RedisHelper.IncrBy($":cps_total:{flagName}:reason:{reason}:{DateTime.Now:yyyyMMdd}");
-                RedisHelper.IncrBy($":cps_total:{flagName}:reason:{reason}:{DateTime.Now:yyyyMMddHH}");
-            }
-
-        }
-
-
-
-
-    }
-
-}
+using dodohold.core;
+using CSRedis;
+using System.Data;
+using System.Threading.Channels;
+
+namespace molilian.core
+{
+    public partial class TkLogCore
+    {
+        static string queue_cps_key = "queue:cps_logs";
+
+
+        public static async Task<int> InsertCpsLogAsync(int limit, YunhuiKit.RedisClient redis)
+        {
+            int total = 0;
+            using var connection = DBContext.GetOpenConnection();
+            connection.Open();
+            using var transaction = connection.BeginTransaction();
+            try
+            {
+                for (int i = 0; i < limit; i++)
+                {
+                    var data = await redis.LPopAsync<UnionCpsDTO>(queue_cps_key);
+                    if (data == null) break;
+
+                    if (TestParseCore.InWhitelist(data.ip, data.oaid))
+                    {
+                        var test_data = data.Convert2Json().Convert2Object<TestUnionCpsDTO>();
+                        connection.Insert(test_data);
+                    }
+                    else
+                    {
+                        //按日保存数据
+                        string daily_table = $"tk_cps_logs_{data.create_time:yyyyMMdd}";
+                        save_cps_parse_logs(data, daily_table, connection, transaction);
+                    }
+                    total++;
+                }
+                transaction.Commit();
+            }
+            catch (Exception ex)
+            {
+                transaction.Rollback();
+                new LoggerLibrary("TkLogCore_error", "task_insert_parse_cps_logs")
+                      .Info(ex.Message, ex.StackTrace)
+                      .SaveAsync();
+                throw;
+            }
+            finally
+            {
+                connection.Close();
+            }
+            return total;
+        }
+
+        public static int task_insert_parse_cps_logs(int limit, CSRedisClient redis)
+        {
+            int total = 0;
+            using var connection = DBContext.GetOpenConnection();
+            connection.Open();
+            using var transaction = connection.BeginTransaction();
+            try
+            {
+                for (int i = 0; i < limit; i++)
+                {
+                    var data = redis.LPop<UnionCpsDTO>(queue_cps_key);
+                    if (data == null) break;
+
+                    if (TestParseCore.InWhitelist(data.ip, data.oaid))
+                    {
+                        var test_data = data.Convert2Json().Convert2Object<TestUnionCpsDTO>();
+                        connection.Insert(test_data);
+                    }
+                    else
+                    {
+                        //按日保存数据
+                        string daily_table = $"tk_cps_logs_{data.create_time:yyyyMMdd}";
+                        save_cps_parse_logs(data, daily_table, connection, transaction);
+                    }
+                    total++;
+                }
+                transaction.Commit();
+            }
+            catch (Exception ex)
+            {
+                transaction.Rollback();
+                new LoggerLibrary("TkLogCore_error", "task_insert_parse_cps_logs")
+                      .Info(ex.Message, ex.StackTrace)
+                      .SaveAsync();
+                throw;
+            }
+            finally
+            {
+                connection.Close();
+            }
+            return total;
+        }
+
+        private static int save_cps_parse_logs(UnionCpsDTO data, string tablename, IDbConnection connection, IDbTransaction transaction)
+        {
+            return new DBContext.Table(connection, tablename)
+                .Add("end_point", data.end_point)
+                .Add("channel", data.channel)
+                .Add("accountId", data.accountId)
+                .Add("accountName", data.accountName)
+                .Add("proxy_node", data.proxy_node)
+                .Add("rawContent", data.rawContent)
+                .Add("rawContent2", data.rawContent2)
+                .Add("success", data.success)
+                .Add("message", data.message)
+                .Add("reason", data.reason)
+                .Add("deeplink_url", data.deeplink_url)
+                .Add("create_time", data.create_time)
+                .Add("elapsedTime", data.elapsedTime)
+                .Add("ip", data.ip)
+                .Add("oaid", data.oaid)
+                .Add("token", data.token)
+                .Add("category", data.category)
+                .Add("pid", data.pid)
+                .Add("title", data.title)
+                .Add("sub_title", data.sub_title)
+                .Add("itemId", data.itemId)
+                .Add("couponAmount", data.couponAmount)
+                .Create(DBContext.InsertType.NORMAL, transaction);
+        }
+
+
+        public static async Task CpsLogAsync(UnionCpsDTO response, AlimamaPlus? alimamaPlus = null)
+        {
+            try
+            {
+                var ts = DateTime.Now - response.create_time;
+                response.elapsedTime = (int)ts.TotalMilliseconds;
+                _ = RedisHelper.RPushAsync(queue_cps_key, response);
+
+                if (response.success)
+                {
+                    await RiskControlCore.CallsIncrByAsync(TkChannelEnum.cps, response.accountId);
+                    await saveClientRequestTotalAsync(response.channel, response.ip, response.oaid);
+                }
+
+                if (!TestParseCore.InWhitelist(response.ip, response.oaid))
+                {
+                    saveCpsCache(response.channel, response.accountId,
+                        response.success, response.message, response.reason);
+                }
+            }
+            catch (Exception ex)
+            {
+                _ = new LoggerLibrary("unionCps", "database_error")
+                .Info(response.rawContent)
+                .Info(response.Convert2Json())
+                .Info(ex.Message, ex.StackTrace)
+                .SaveAsync();
+            }
+        }
+
+        private static void saveCpsCache(string channel, int accountId, bool success, string message, string reason)
+        {
+            saveAccountCpsCache("all", success, message, reason);
+            saveAccountCpsCache($"{channel}", success, message, reason);
+            if (accountId != 0)
+            {
+                saveAccountCpsCache($"{channel}_{accountId}", success, message, reason);
+            }
+        }
+
+        private static void saveAccountCpsCache(string flagName, bool success, string message, string reason)
+        {
+            RedisHelper.IncrBy($":cps_total:{flagName}:{DateTime.Now:yyyyMM}");
+            RedisHelper.IncrBy($":cps_total:{flagName}:{DateTime.Now:yyyyMMdd}");
+            RedisHelper.IncrBy($":cps_total:{flagName}:{DateTime.Now:yyyyMMddHH}");
+
+
+            string result = success ? "success" : "fail";
+            RedisHelper.IncrBy($":cps_total:{flagName}:{result}:{DateTime.Now:yyyyMM}");
+            RedisHelper.IncrBy($":cps_total:{flagName}:{result}:{DateTime.Now:yyyyMMdd}");
+            RedisHelper.IncrBy($":cps_total:{flagName}:{result}:{DateTime.Now:yyyyMMddHH}");
+
+
+            if (!string.IsNullOrEmpty(message))
+            {
+                RedisHelper.SAdd($":cps_total:{flagName}:message:{DateTime.Now:yyyyMM}", message);
+                RedisHelper.SAdd($":cps_total:{flagName}:message:{DateTime.Now:yyyyMMdd}", message);
+                RedisHelper.SAdd($":cps_total:{flagName}:message:{DateTime.Now:yyyyMMddHH}", message);
+
+                RedisHelper.IncrBy($":cps_total:{flagName}:{message}:{DateTime.Now:yyyyMM}");
+                RedisHelper.IncrBy($":cps_total:{flagName}:{message}:{DateTime.Now:yyyyMMdd}");
+                RedisHelper.IncrBy($":cps_total:{flagName}:{message}:{DateTime.Now:yyyyMMddHH}");
+                RedisHelper.IncrBy($":cps_total:{flagName}:message:{message}:{DateTime.Now:yyyyMM}");
+                RedisHelper.IncrBy($":cps_total:{flagName}:message:{message}:{DateTime.Now:yyyyMMdd}");
+                RedisHelper.IncrBy($":cps_total:{flagName}:message:{message}:{DateTime.Now:yyyyMMddHH}");
+            }
+
+            if (!string.IsNullOrEmpty(reason))
+            {
+                RedisHelper.SAdd($":cps_total:{flagName}:reason:{DateTime.Now:yyyyMM}", reason);
+                RedisHelper.SAdd($":cps_total:{flagName}:reason:{DateTime.Now:yyyyMMdd}", reason);
+                RedisHelper.SAdd($":cps_total:{flagName}:reason:{DateTime.Now:yyyyMMddHH}", reason);
+
+                RedisHelper.IncrBy($":cps_total:{flagName}:{reason}:{DateTime.Now:yyyyMM}");
+                RedisHelper.IncrBy($":cps_total:{flagName}:{reason}:{DateTime.Now:yyyyMMdd}");
+                RedisHelper.IncrBy($":cps_total:{flagName}:{reason}:{DateTime.Now:yyyyMMddHH}");
+                RedisHelper.IncrBy($":cps_total:{flagName}:reason:{reason}:{DateTime.Now:yyyyMM}");
+                RedisHelper.IncrBy($":cps_total:{flagName}:reason:{reason}:{DateTime.Now:yyyyMMdd}");
+                RedisHelper.IncrBy($":cps_total:{flagName}:reason:{reason}:{DateTime.Now:yyyyMMddHH}");
+            }
+
+        }
+
+
+
+
+    }
+
+}

+ 222 - 222
molilian.core/Core/log/deeplink.cs

@@ -1,222 +1,222 @@
-using dodohold.core;
-using CSRedis;
-using System.Data;
-using static dodohold.core.ZTOExpress.CreateOrderArgs;
-using YunhuiKit;
-
-namespace molilian.core
-{
-    public partial class TkLogCore
-    {
-
-        static string queue_deeplink_parse_key = "queue:parse_logs:deeplink";
-
-        public static async Task<int> InsertDeeplinkLogAsync(int limit, YunhuiKit.RedisClient redis)
-        {
-            int total = 0;
-            using var connection = DBContext.GetOpenConnection();
-            connection.Open();
-            using var transaction = connection.BeginTransaction();
-            try
-            {
-
-                for (int i = 0; i < limit; i++)
-                {
-                    var data = await redis.LPopAsync<DeeplinkParseDataDTO>(queue_deeplink_parse_key);
-                    if (data == null) break;
-
-
-                    if (TestParseCore.InWhitelist(data.ip, data.oaid))
-                    {
-                        save_dp_parse_logs(data, "deeplink_parse_logs_test", connection, transaction);
-                    }
-                    else
-                    {
-                        //save_dp_parse_logs(data, "deeplink_parse_logs", connection, transaction);
-
-                        //if (save_dailys_log)
-                        {
-                            string daily_table = $"deeplink_parse_logs_{data.create_time:yyyyMMdd}";
-                            save_dp_parse_logs(data, daily_table, connection, transaction);
-                        }
-
-                        if (data.success)
-                        {
-                            //save_dp_parse_logs(data, "deeplink_parse_logs_success", connection, transaction);
-                        }
-                        else
-                        {
-                            save_dp_parse_logs(data, "deeplink_parse_logs_fail", connection, transaction);
-                            save_dp_parse_logs(data, $"deeplink_parse_logs_fail_{data.create_time:yyyyMMdd}", connection, transaction);
-                        }
-                    }
-                    total++;
-                }
-                transaction.Commit();
-            }
-            catch (Exception ex)
-            {
-                transaction.Rollback();
-                new LoggerLibrary("TkLogCore_error", "task_insert_parse_deeplink_logs")
-                      .Info(ex.Message, ex.StackTrace)
-                      .SaveAsync();
-                throw;
-            }
-            finally
-            {
-                connection.Close();
-            }
-            return total;
-        }
-
-
-        public static int task_insert_parse_deeplink_logs(int limit, CSRedisClient redis)
-        {
-            int total = 0;
-            using var connection = DBContext.GetOpenConnection();
-            connection.Open();
-            using var transaction = connection.BeginTransaction();
-            try
-            {
-
-                for (int i = 0; i < limit; i++)
-                {
-                    var data = redis.LPop<DeeplinkParseDataDTO>(queue_deeplink_parse_key);
-                    if (data == null) break;
-
-
-                    if (TestParseCore.InWhitelist(data.ip, data.oaid))
-                    {
-                        save_dp_parse_logs(data, "deeplink_parse_logs_test", connection, transaction);
-                    }
-                    else
-                    {
-                        //save_dp_parse_logs(data, "deeplink_parse_logs", connection, transaction);
-
-                        //if (save_dailys_log)
-                        {
-                            string daily_table = $"deeplink_parse_logs_{data.create_time:yyyyMMdd}";
-                            save_dp_parse_logs(data, daily_table, connection, transaction);
-                        }
-
-                        if (data.success)
-                        {
-                            //save_dp_parse_logs(data, "deeplink_parse_logs_success", connection, transaction);
-                        }
-                        else
-                        {
-                            save_dp_parse_logs(data, "deeplink_parse_logs_fail", connection, transaction);
-                        }
-                    }
-                    total++;
-                }
-                transaction.Commit();
-            }
-            catch (Exception ex)
-            {
-                transaction.Rollback();
-                new LoggerLibrary("TkLogCore_error", "task_insert_parse_deeplink_logs")
-                      .Info(ex.Message, ex.StackTrace)
-                      .SaveAsync();
-                throw;
-            }
-            finally
-            {
-                connection.Close();
-            }
-            return total;
-        }
-
-
-        //public static int task_insert_parse_deeplink_logs(IDbConnection connection, IDbTransaction transaction, int limit, CSRedisClient redis)
-        //{
-        //    int total = 0;
-
-        //    for (int i = 0; i < limit; i++)
-        //    {
-        //        var data = redis.LPop<DeeplinkParseDataDTO>(queue_deeplink_parse_key);
-        //        if (data == null) break;
-
-        //        if (_test_oaid.Equals(data.oaid) ||
-        //             data.ip.Contains("127.0.0"))
-        //        {
-        //            var test_data = data.Convert2Json().Convert2Object<TestDeeplinkParseDataDTO>();
-        //            connection.Insert(test_data);
-        //        }
-        //        else
-        //        {
-        //            connection.Insert(data);
-        //            if (!data.success)
-        //            {
-        //                var success_data = data.Convert2Json().Convert2Object<FilDeeplinkParseDataDTO>();
-        //                connection.Insert(success_data);
-        //            }
-        //            else
-        //            {
-        //                var success_data = data.Convert2Json().Convert2Object<SuccessDeeplinkParseDataDTO>();
-        //                connection.Insert(success_data);
-        //            }
-        //        }
-        //        total++;
-        //    }
-        //    return total;
-        //}
-        public static async Task ParseLogAsync(DeeplinkParseDataDTO response)
-        {
-            try
-            {
-                var ts = DateTime.Now - response.create_time;
-                response.elapsedTime = (int)ts.TotalMilliseconds;
-                _ = RedisKit.RPushAsync(queue_deeplink_parse_key, response);
-
-                if (!TestParseCore.InWhitelist(response.ip, response.oaid))
-                {
-                    await SaveParseCacheAsync(response.channel_name, 0, "tool",
-                        response.success, response.message, response.reason,
-                        response.deeplink_url);
-                }
-            }
-            catch (Exception ex) { }
-        }
-
-
-        private static int save_dp_parse_logs(DeeplinkParseDataDTO data, string tablename, IDbConnection connection, IDbTransaction transaction)
-        {
-
-
-            //public int channel_id { get; set; } = 0;
-            //public string channel_name { get; set; } = "unknown";
-            //public bool success { get; set; } = true;
-            //public string message { get; set; } = string.Empty;
-            //public string reason { get; set; } = string.Empty;
-            //public string content { get; set; } = string.Empty;
-            //public string ip { get; set; } = string.Empty;
-            //public string oaid { get; set; } = string.Empty;
-            //public string deeplink_url { get; set; } = string.Empty;
-            //public string itemName { get; set; } = string.Empty;
-            //public int elapsedTime { get; set; } = 0;
-            //public DateTime create_time { get; set; } = DateTime.Now;
-            //public string end_point { get; set; } = string.Empty;
-
-            string content = data.content;
-            if (content.Length > 20000) content = $"{content[..20000]}...";
-
-            return new DBContext.Table(connection, tablename)
-                .Add("end_point", data.end_point)
-                .Add("channel_name", data.channel_name)
-                .Add("success", data.success)
-                .Add("message", data.message)
-                .Add("reason", data.reason)
-                .Add("content", content)
-                .Add("ip", data.ip)
-                .Add("oaid", data.oaid)
-                .Add("deeplink_url", data.deeplink_url)
-                .Add("itemName", data.itemName)
-                .Add("elapsedTime", data.elapsedTime)
-                .Add("create_time", data.create_time)
-                .Create(DBContext.InsertType.NORMAL, transaction);
-        }
-
-    }
-
-}
+using dodohold.core;
+using CSRedis;
+using System.Data;
+using static dodohold.core.ZTOExpress.CreateOrderArgs;
+using YunhuiKit;
+
+namespace molilian.core
+{
+    public partial class TkLogCore
+    {
+
+        static string queue_deeplink_parse_key = "queue:parse_logs:deeplink";
+
+        public static async Task<int> InsertDeeplinkLogAsync(int limit, YunhuiKit.RedisClient redis)
+        {
+            int total = 0;
+            using var connection = DBContext.GetOpenConnection();
+            connection.Open();
+            using var transaction = connection.BeginTransaction();
+            try
+            {
+
+                for (int i = 0; i < limit; i++)
+                {
+                    var data = await redis.LPopAsync<DeeplinkParseDataDTO>(queue_deeplink_parse_key);
+                    if (data == null) break;
+
+
+                    if (TestParseCore.InWhitelist(data.ip, data.oaid))
+                    {
+                        save_dp_parse_logs(data, "deeplink_parse_logs_test", connection, transaction);
+                    }
+                    else
+                    {
+                        //save_dp_parse_logs(data, "deeplink_parse_logs", connection, transaction);
+
+                        //if (save_dailys_log)
+                        {
+                            string daily_table = $"deeplink_parse_logs_{data.create_time:yyyyMMdd}";
+                            save_dp_parse_logs(data, daily_table, connection, transaction);
+                        }
+
+                        if (data.success)
+                        {
+                            //save_dp_parse_logs(data, "deeplink_parse_logs_success", connection, transaction);
+                        }
+                        else
+                        {
+                            save_dp_parse_logs(data, "deeplink_parse_logs_fail", connection, transaction);
+                            save_dp_parse_logs(data, $"deeplink_parse_logs_fail_{data.create_time:yyyyMMdd}", connection, transaction);
+                        }
+                    }
+                    total++;
+                }
+                transaction.Commit();
+            }
+            catch (Exception ex)
+            {
+                transaction.Rollback();
+                new LoggerLibrary("TkLogCore_error", "task_insert_parse_deeplink_logs")
+                      .Info(ex.Message, ex.StackTrace)
+                      .SaveAsync();
+                throw;
+            }
+            finally
+            {
+                connection.Close();
+            }
+            return total;
+        }
+
+
+        public static int task_insert_parse_deeplink_logs(int limit, CSRedisClient redis)
+        {
+            int total = 0;
+            using var connection = DBContext.GetOpenConnection();
+            connection.Open();
+            using var transaction = connection.BeginTransaction();
+            try
+            {
+
+                for (int i = 0; i < limit; i++)
+                {
+                    var data = redis.LPop<DeeplinkParseDataDTO>(queue_deeplink_parse_key);
+                    if (data == null) break;
+
+
+                    if (TestParseCore.InWhitelist(data.ip, data.oaid))
+                    {
+                        save_dp_parse_logs(data, "deeplink_parse_logs_test", connection, transaction);
+                    }
+                    else
+                    {
+                        //save_dp_parse_logs(data, "deeplink_parse_logs", connection, transaction);
+
+                        //if (save_dailys_log)
+                        {
+                            string daily_table = $"deeplink_parse_logs_{data.create_time:yyyyMMdd}";
+                            save_dp_parse_logs(data, daily_table, connection, transaction);
+                        }
+
+                        if (data.success)
+                        {
+                            //save_dp_parse_logs(data, "deeplink_parse_logs_success", connection, transaction);
+                        }
+                        else
+                        {
+                            save_dp_parse_logs(data, "deeplink_parse_logs_fail", connection, transaction);
+                        }
+                    }
+                    total++;
+                }
+                transaction.Commit();
+            }
+            catch (Exception ex)
+            {
+                transaction.Rollback();
+                new LoggerLibrary("TkLogCore_error", "task_insert_parse_deeplink_logs")
+                      .Info(ex.Message, ex.StackTrace)
+                      .SaveAsync();
+                throw;
+            }
+            finally
+            {
+                connection.Close();
+            }
+            return total;
+        }
+
+
+        //public static int task_insert_parse_deeplink_logs(IDbConnection connection, IDbTransaction transaction, int limit, CSRedisClient redis)
+        //{
+        //    int total = 0;
+
+        //    for (int i = 0; i < limit; i++)
+        //    {
+        //        var data = redis.LPop<DeeplinkParseDataDTO>(queue_deeplink_parse_key);
+        //        if (data == null) break;
+
+        //        if (_test_oaid.Equals(data.oaid) ||
+        //             data.ip.Contains("127.0.0"))
+        //        {
+        //            var test_data = data.Convert2Json().Convert2Object<TestDeeplinkParseDataDTO>();
+        //            connection.Insert(test_data);
+        //        }
+        //        else
+        //        {
+        //            connection.Insert(data);
+        //            if (!data.success)
+        //            {
+        //                var success_data = data.Convert2Json().Convert2Object<FilDeeplinkParseDataDTO>();
+        //                connection.Insert(success_data);
+        //            }
+        //            else
+        //            {
+        //                var success_data = data.Convert2Json().Convert2Object<SuccessDeeplinkParseDataDTO>();
+        //                connection.Insert(success_data);
+        //            }
+        //        }
+        //        total++;
+        //    }
+        //    return total;
+        //}
+        public static async Task ParseLogAsync(DeeplinkParseDataDTO response)
+        {
+            try
+            {
+                var ts = DateTime.Now - response.create_time;
+                response.elapsedTime = (int)ts.TotalMilliseconds;
+                _ = RedisKit.RPushAsync(queue_deeplink_parse_key, response);
+
+                if (!TestParseCore.InWhitelist(response.ip, response.oaid))
+                {
+                    await SaveParseCacheAsync(response.channel_name, 0, "tool",
+                        response.success, response.message, response.reason,
+                        response.deeplink_url);
+                }
+            }
+            catch (Exception ex) { }
+        }
+
+
+        private static int save_dp_parse_logs(DeeplinkParseDataDTO data, string tablename, IDbConnection connection, IDbTransaction transaction)
+        {
+
+
+            //public int channel_id { get; set; } = 0;
+            //public string channel_name { get; set; } = "unknown";
+            //public bool success { get; set; } = true;
+            //public string message { get; set; } = string.Empty;
+            //public string reason { get; set; } = string.Empty;
+            //public string content { get; set; } = string.Empty;
+            //public string ip { get; set; } = string.Empty;
+            //public string oaid { get; set; } = string.Empty;
+            //public string deeplink_url { get; set; } = string.Empty;
+            //public string itemName { get; set; } = string.Empty;
+            //public int elapsedTime { get; set; } = 0;
+            //public DateTime create_time { get; set; } = DateTime.Now;
+            //public string end_point { get; set; } = string.Empty;
+
+            string content = data.content;
+            if (content.Length > 20000) content = $"{content[..20000]}...";
+
+            return new DBContext.Table(connection, tablename)
+                .Add("end_point", data.end_point)
+                .Add("channel_name", data.channel_name)
+                .Add("success", data.success)
+                .Add("message", data.message)
+                .Add("reason", data.reason)
+                .Add("content", content)
+                .Add("ip", data.ip)
+                .Add("oaid", data.oaid)
+                .Add("deeplink_url", data.deeplink_url)
+                .Add("itemName", data.itemName)
+                .Add("elapsedTime", data.elapsedTime)
+                .Add("create_time", data.create_time)
+                .Create(DBContext.InsertType.NORMAL, transaction);
+        }
+
+    }
+
+}

+ 155 - 155
molilian.core/Core/log/dy.cs

@@ -1,155 +1,155 @@
-using dodohold.core;
-using CSRedis;
-using System.Data;
-using YunhuiKit;
-
-namespace molilian.core
-{
-    public partial class TkLogCore
-    {
-        static string queue_parse_dy_key = "queue:parse_logs:dy";
-        public static async Task<int> InsertParseDyLogAsync(int limit, YunhuiKit.RedisClient redis)
-        {
-            int total = 0;
-            using var connection = DBContext.GetOpenConnection();
-            connection.Open();
-            using var transaction = connection.BeginTransaction();
-            try
-            {
-
-                for (int i = 0; i < limit; i++)
-                {
-
-                    var data = await redis.LPopAsync<DyDataDTO>(queue_parse_dy_key);
-                    if (data == null) break;
-                    string daily_table = $"dy_parse_logs_{data.create_time:yyyyMMdd}";
-                    new DBContext.Table(connection, daily_table)
-                        .Add("end_point", data.end_point)
-                        .Add("channel", (int)data.channel)
-                        .Add("accountId", data.accountId)
-                        .Add("accountName", data.accountName)
-                        .Add("proxy_node", data.proxy_node)
-                        .Add("rawContent", data.rawContent)
-                        .Add("success", data.success)
-                        .Add("message", data.message)
-                        .Add("reason", data.reason)
-                        .Add("content", data.content)
-                        .Add("itemId", data.itemId)
-                        .Add("itemName", data.itemName)
-                        .Add("pic", data.pic)
-                        .Add("couponAmount", data.couponAmount)
-                        .Add("promotionPrice", data.promotionPrice)
-                        .Add("taoToken", data.taoToken)
-                        .Add("shortLinkurl", data.shortLinkurl)
-                        .Add("deeplink_url", data.deeplink_url)
-                        .Add("elapsedTime", data.elapsedTime)
-                        .Add("elapsedTime2", data.elapsedTime2)
-                        .Add("elapsedTime3", data.elapsedTime3)
-                        .Add("subCode", data.subCode)
-                        .Add("ip", data.ip)
-                        .Add("oaid", data.oaid)
-                        .Add("create_time", data.create_time)
-                        .Create(DBContext.InsertType.NORMAL, transaction);
-                    total++;
-                }
-                transaction.Commit();
-            }
-            catch (Exception ex)
-            {
-                transaction.Rollback();
-                new LoggerLibrary("TkLogCore_error", "task_insert_parse_dy_logs")
-                      .Info(ex.Message, ex.StackTrace)
-                      .SaveAsync();
-                throw;
-            }
-            finally
-            {
-                connection.Close();
-            }
-            return total;
-        }
-
-        public static int task_insert_parse_dy_logs(int limit, CSRedisClient redis)
-        {
-            int total = 0;
-            using var connection = DBContext.GetOpenConnection();
-            connection.Open();
-            using var transaction = connection.BeginTransaction();
-            try
-            {
-
-                for (int i = 0; i < limit; i++)
-                {
-
-                    var data = redis.LPop<DyDataDTO>(queue_parse_dy_key);
-                    if (data == null) break;
-                    string daily_table = $"dy_parse_logs_{data.create_time:yyyyMMdd}";
-                    new DBContext.Table(connection, daily_table)
-                        .Add("end_point", data.end_point)
-                        .Add("channel", (int)data.channel)
-                        .Add("accountId", data.accountId)
-                        .Add("accountName", data.accountName)
-                        .Add("proxy_node", data.proxy_node)
-                        .Add("rawContent", data.rawContent)
-                        .Add("success", data.success)
-                        .Add("message", data.message)
-                        .Add("reason", data.reason)
-                        .Add("content", data.content)
-                        .Add("itemId", data.itemId)
-                        .Add("itemName", data.itemName)
-                        .Add("pic", data.pic)
-                        .Add("couponAmount", data.couponAmount)
-                        .Add("promotionPrice", data.promotionPrice)
-                        .Add("taoToken", data.taoToken)
-                        .Add("shortLinkurl", data.shortLinkurl)
-                        .Add("deeplink_url", data.deeplink_url)
-                        .Add("elapsedTime", data.elapsedTime)
-                        .Add("elapsedTime2", data.elapsedTime2)
-                        .Add("elapsedTime3", data.elapsedTime3)
-                        .Add("subCode", data.subCode)
-                        .Add("ip", data.ip)
-                        .Add("oaid", data.oaid)
-                        .Add("create_time", data.create_time)
-                        .Create(DBContext.InsertType.NORMAL, transaction);
-                    total++;
-                }
-                transaction.Commit();
-            }
-            catch (Exception ex)
-            {
-                transaction.Rollback();
-                new LoggerLibrary("TkLogCore_error", "task_insert_parse_dy_logs")
-                      .Info(ex.Message, ex.StackTrace)
-                      .SaveAsync();
-                throw;
-            }
-            finally
-            {
-                connection.Close();
-            }
-            return total;
-        }
-
-        public static async Task ParseLogAsync(DyDataDTO response)
-        {
-            try
-            {
-                var ts = DateTime.Now - response.create_time;
-                response.elapsedTime = (int)ts.TotalMilliseconds;
-                _ = RedisKit.RPushAsync(queue_parse_dy_key, response);
-
-                if (response.success) await saveClientRequestTotalAsync(response.channel, response.ip, response.oaid);
-
-                if (!TestParseCore.InWhitelist(response.ip, response.oaid))
-                {
-                    await SaveParseCacheAsync(response.channel.ToString(), response.accountId,
-                        response.accountName, response.success, response.message, response.reason,
-                        response.deeplink_url);
-                }
-
-            }
-            catch (Exception ex) { }
-        }
-    }
-
-}
+using dodohold.core;
+using CSRedis;
+using System.Data;
+using YunhuiKit;
+
+namespace molilian.core
+{
+    public partial class TkLogCore
+    {
+        static string queue_parse_dy_key = "queue:parse_logs:dy";
+        public static async Task<int> InsertParseDyLogAsync(int limit, YunhuiKit.RedisClient redis)
+        {
+            int total = 0;
+            using var connection = DBContext.GetOpenConnection();
+            connection.Open();
+            using var transaction = connection.BeginTransaction();
+            try
+            {
+
+                for (int i = 0; i < limit; i++)
+                {
+
+                    var data = await redis.LPopAsync<DyDataDTO>(queue_parse_dy_key);
+                    if (data == null) break;
+                    string daily_table = $"dy_parse_logs_{data.create_time:yyyyMMdd}";
+                    new DBContext.Table(connection, daily_table)
+                        .Add("end_point", data.end_point)
+                        .Add("channel", (int)data.channel)
+                        .Add("accountId", data.accountId)
+                        .Add("accountName", data.accountName)
+                        .Add("proxy_node", data.proxy_node)
+                        .Add("rawContent", data.rawContent)
+                        .Add("success", data.success)
+                        .Add("message", data.message)
+                        .Add("reason", data.reason)
+                        .Add("content", data.content)
+                        .Add("itemId", data.itemId)
+                        .Add("itemName", data.itemName)
+                        .Add("pic", data.pic)
+                        .Add("couponAmount", data.couponAmount)
+                        .Add("promotionPrice", data.promotionPrice)
+                        .Add("taoToken", data.taoToken)
+                        .Add("shortLinkurl", data.shortLinkurl)
+                        .Add("deeplink_url", data.deeplink_url)
+                        .Add("elapsedTime", data.elapsedTime)
+                        .Add("elapsedTime2", data.elapsedTime2)
+                        .Add("elapsedTime3", data.elapsedTime3)
+                        .Add("subCode", data.subCode)
+                        .Add("ip", data.ip)
+                        .Add("oaid", data.oaid)
+                        .Add("create_time", data.create_time)
+                        .Create(DBContext.InsertType.NORMAL, transaction);
+                    total++;
+                }
+                transaction.Commit();
+            }
+            catch (Exception ex)
+            {
+                transaction.Rollback();
+                new LoggerLibrary("TkLogCore_error", "task_insert_parse_dy_logs")
+                      .Info(ex.Message, ex.StackTrace)
+                      .SaveAsync();
+                throw;
+            }
+            finally
+            {
+                connection.Close();
+            }
+            return total;
+        }
+
+        public static int task_insert_parse_dy_logs(int limit, CSRedisClient redis)
+        {
+            int total = 0;
+            using var connection = DBContext.GetOpenConnection();
+            connection.Open();
+            using var transaction = connection.BeginTransaction();
+            try
+            {
+
+                for (int i = 0; i < limit; i++)
+                {
+
+                    var data = redis.LPop<DyDataDTO>(queue_parse_dy_key);
+                    if (data == null) break;
+                    string daily_table = $"dy_parse_logs_{data.create_time:yyyyMMdd}";
+                    new DBContext.Table(connection, daily_table)
+                        .Add("end_point", data.end_point)
+                        .Add("channel", (int)data.channel)
+                        .Add("accountId", data.accountId)
+                        .Add("accountName", data.accountName)
+                        .Add("proxy_node", data.proxy_node)
+                        .Add("rawContent", data.rawContent)
+                        .Add("success", data.success)
+                        .Add("message", data.message)
+                        .Add("reason", data.reason)
+                        .Add("content", data.content)
+                        .Add("itemId", data.itemId)
+                        .Add("itemName", data.itemName)
+                        .Add("pic", data.pic)
+                        .Add("couponAmount", data.couponAmount)
+                        .Add("promotionPrice", data.promotionPrice)
+                        .Add("taoToken", data.taoToken)
+                        .Add("shortLinkurl", data.shortLinkurl)
+                        .Add("deeplink_url", data.deeplink_url)
+                        .Add("elapsedTime", data.elapsedTime)
+                        .Add("elapsedTime2", data.elapsedTime2)
+                        .Add("elapsedTime3", data.elapsedTime3)
+                        .Add("subCode", data.subCode)
+                        .Add("ip", data.ip)
+                        .Add("oaid", data.oaid)
+                        .Add("create_time", data.create_time)
+                        .Create(DBContext.InsertType.NORMAL, transaction);
+                    total++;
+                }
+                transaction.Commit();
+            }
+            catch (Exception ex)
+            {
+                transaction.Rollback();
+                new LoggerLibrary("TkLogCore_error", "task_insert_parse_dy_logs")
+                      .Info(ex.Message, ex.StackTrace)
+                      .SaveAsync();
+                throw;
+            }
+            finally
+            {
+                connection.Close();
+            }
+            return total;
+        }
+
+        public static async Task ParseLogAsync(DyDataDTO response)
+        {
+            try
+            {
+                var ts = DateTime.Now - response.create_time;
+                response.elapsedTime = (int)ts.TotalMilliseconds;
+                _ = RedisKit.RPushAsync(queue_parse_dy_key, response);
+
+                if (response.success) await saveClientRequestTotalAsync(response.channel, response.ip, response.oaid);
+
+                if (!TestParseCore.InWhitelist(response.ip, response.oaid))
+                {
+                    await SaveParseCacheAsync(response.channel.ToString(), response.accountId,
+                        response.accountName, response.success, response.message, response.reason,
+                        response.deeplink_url);
+                }
+
+            }
+            catch (Exception ex) { }
+        }
+    }
+
+}

+ 287 - 287
molilian.core/Core/log/jd.cs

@@ -1,287 +1,287 @@
-using dodohold.core;
-using CSRedis;
-using System.Data;
-using YunhuiKit;
-using static dodohold.core.ZTOExpress.CreateOrderArgs;
-using System.Xml.Linq;
-
-namespace molilian.core
-{
-    public partial class TkLogCore
-    {
-        static string queue_parse_jd_key = "queue:parse_logs:jd";
-
-
-        public static async Task<int> InsertParseJDLogAsync(int limit, YunhuiKit.RedisClient redis)
-        {
-            int total = 0;
-            using var connection = DBContext.GetOpenConnection();
-            connection.Open();
-            using var transaction = connection.BeginTransaction();
-            try
-            {
-                for (int i = 0; i < limit; i++)
-                {
-                    var data = await redis.LPopAsync<JdDataDTO>(queue_parse_jd_key);
-                    if (data == null) break;
-
-                    if (TestParseCore.InWhitelist(data.ip, data.oaid))
-                    {
-                        save_jd_parse_logs(data, "jd_parse_logs_test", connection, transaction);
-                    }
-                    else
-                    {
-                        if ("dp".Equals(data.parse_type))
-                        {
-                            string daily_table = $"jd_dp_logs_{data.create_time:yyyyMMdd}";
-                            save_jd_parse_logs(data, daily_table, connection, transaction);
-
-                            if (data.success)
-                            {
-                                save_jd_parse_logs(data, "jd_dp_logs_success", connection, transaction);
-                            }
-                        }
-
-                        //save_jd_parse_logs(data, "jd_parse_logs", connection, transaction);
-                        //if (save_dailys_log)
-                        {
-                            string daily_table = $"jd_parse_logs_{data.create_time:yyyyMMdd}";
-                            save_jd_parse_logs(data, daily_table, connection, transaction);
-                        }
-
-                        if (data.elapsedTime > 1000)
-                        {
-                            save_jd_parse_logs(data, "jd_parse_logs_test", connection, transaction);
-                        }
-
-                        if (data.success)
-                        {
-                            save_jd_parse_logs(data, "jd_parse_logs_success", connection, transaction);
-                        }
-                    }
-                    total++;
-                }
-                transaction.Commit();
-            }
-            catch (Exception ex)
-            {
-                transaction.Rollback();
-                new LoggerLibrary("TkLogCore_error", "task_insert_parse_jd_logs")
-                      .Info(ex.Message, ex.StackTrace)
-                      .SaveAsync();
-                throw;
-            }
-            finally
-            {
-                connection.Close();
-            }
-            return total;
-        }
-
-        public static int task_insert_parse_jd_logs(int limit, CSRedisClient redis)
-        {
-            int total = 0;
-            using var connection = DBContext.GetOpenConnection();
-            connection.Open();
-            using var transaction = connection.BeginTransaction();
-            try
-            {
-                for (int i = 0; i < limit; i++)
-                {
-                    var data = redis.LPop<JdDataDTO>(queue_parse_jd_key);
-                    if (data == null) break;
-
-                    if (TestParseCore.InWhitelist(data.ip, data.oaid))
-                    {
-                        save_jd_parse_logs(data, "jd_parse_logs_test", connection, transaction);
-                    }
-                    else
-                    {
-                        //save_jd_parse_logs(data, "jd_parse_logs", connection, transaction);
-
-                        //if (save_dailys_log)
-                        {
-                            string daily_table = $"jd_parse_logs_{data.create_time:yyyyMMdd}";
-                            save_jd_parse_logs(data, daily_table, connection, transaction);
-                        }
-
-                        if (data.elapsedTime > 1000)
-                        {
-                            save_jd_parse_logs(data, "jd_parse_logs_test", connection, transaction);
-                        }
-                        if (data.success)
-                        {
-                            save_jd_parse_logs(data, "jd_parse_logs_success", connection, transaction);
-                        }
-                    }
-                    total++;
-                }
-                transaction.Commit();
-            }
-            catch (Exception ex)
-            {
-                transaction.Rollback();
-                new LoggerLibrary("TkLogCore_error", "task_insert_parse_jd_logs")
-                      .Info(ex.Message, ex.StackTrace)
-                      .SaveAsync();
-                throw;
-            }
-            finally
-            {
-                connection.Close();
-            }
-            return total;
-        }
-
-        public static async Task ParseLogAsync(JdDataDTO response, bool is_hide)
-        {
-            try
-            {
-                var ts = DateTime.Now - response.create_time;
-                response.elapsedTime = (int)ts.TotalMilliseconds;
-                _ = RedisKit.RPushAsync(queue_parse_jd_key, response);
-
-                if (!TestParseCore.InWhitelist(response.ip, response.oaid))
-                {
-                    await SaveParseCacheAsync(response.channel.ToString(), response.accountId,
-                        response.accountName, response.success, response.message, response.reason,
-                        response.deeplink_url);
-                }
-
-                if (response.success) await saveClientRequestTotalAsync(response.channel, response.ip, response.oaid);
-                if (response.success || response.message.Equals("转链失败"))
-                {
-                    await RiskControlCore.CallsIncrByAsync(response.channel, response.accountId);
-                    //saveClientRequestTotal(response.channel, response.ip, response.oaid);
-                }
-
-                //if (!response.success && ("nologin".Equals(response.reason) ||
-                //    "方法不存在".Equals(response.reason) ||
-                //    "未登录".Equals(response.reason)))
-                //{
-                //    await JdPoolCore.DisabledAsync(response.accountId, response.accountName, $"{response.rawContent}\n{response.rawContent2}");
-                //}
-
-                if ("没有匹配账号".Equals(response.reason))
-                {
-                    await JdPoolCore.AccountExhaustedAsync();
-                }
-
-                string failure_key = $":total:continuous_failure:{response.accountId}";
-                string failure_lock_key = $":lock:continuous_failure:{response.accountId}";
-
-                if (response.success) await RedisKit.DelAsync(failure_key);
-
-                //连续错误发送报警
-                if ("签名失败".Equals(response.reason) || "网络异常".Equals(response.reason))
-                {
-                    var total = await RedisKit.IncrByAsync(failure_key);
-                    await RedisKit.ExpireAsync(failure_key, 3600);
-
-                    if (total > 30)
-                    {
-                        //没有锁定记录才发送,
-                        string lockfFlag = await RedisKit.GetAsync<string>(failure_lock_key);
-                        if (string.IsNullOrEmpty(lockfFlag))
-                        {
-                            //发送锁,保存半小时(半小时内只发送1次)
-                            await RedisKit.SetAsync(failure_lock_key, 1, 1800);
-
-                            string message = $"【京东{response.accountName}】{EndPointCore.CurrentEndPoint},连续的{response.reason}";
-                            await RedisKit.ExpireAsync(failure_key, 3600);
-                            NotifyCore.Notify(new NifyMessage
-                            {
-                                message = message,
-                                priority = NifyMessagePriority.high,
-                                tags = ["red_circle"]
-                            });
-
-                            if (!is_hide)
-                            {
-                                _ = NotifyCore.QYWeixinPushNotifyAsync(response.reason, message);
-                            }
-                        }
-                    }
-                }
-
-                string nologin_key = $":total:continuous_nologin:{response.accountId}";
-                string nologin_lock_key = $":lock:continuous_nologin:{response.accountId}";
-
-                //连续错误发送报警
-                if (!response.success && ("nologin".Equals(response.reason) ||
-                    "方法不存在".Equals(response.reason) ||
-                    "未登录".Equals(response.reason)))
-                {
-                    var total = await RedisKit.IncrByAsync(nologin_key);
-                    await RedisKit.ExpireAsync(nologin_key, 3600);
-
-                    if (total > 3)
-                    {
-                        //没有锁定记录才发送,
-                        string lockfFlag = await RedisKit.GetAsync<string>(nologin_lock_key);
-                        if (string.IsNullOrEmpty(lockfFlag))
-                        {
-                            await JdPoolCore.DisabledAsync(response.accountId, response.accountName, $"{response.rawContent}\n{response.rawContent2}");
-                            await RedisKit.SetAsync(nologin_lock_key, 1, 60);
-                            await RedisKit.ExpireAsync(nologin_key, 86400);
-                        }
-                    }
-                    else
-                    {
-                        NotifyCore.Notify(new NifyMessage
-                        {
-                            message = $"【京东联盟{response.accountId}:{response.accountName}】cookie 掉线({total})\n\n{response.rawContent}",
-                            priority = NifyMessagePriority.high,
-                            tags = ["red_circle"]
-                        });
-                    }
-                }
-                else
-                {
-                    await RedisKit.DelAsync(nologin_key);
-                }
-
-
-
-
-            }
-            catch (Exception ex) { }
-        }
-
-        private static int save_jd_parse_logs(JdDataDTO data, string tablename, IDbConnection connection, IDbTransaction transaction)
-        {
-            return new DBContext.Table(connection, tablename)
-                .Add("end_point", data.end_point)
-                .Add("channel", (int)data.channel)
-                .Add("accountId", data.accountId)
-                .Add("accountName", data.accountName)
-                .Add("proxy_name", data.proxy_name)
-                .Add("proxy_node", data.proxy_node)
-                .Add("curl_proxy", data.curl_proxy)
-                .Add("rawContent", data.rawContent)
-                .Add("success", data.success)
-                .Add("message", data.message)
-                .Add("reason", data.reason)
-                .Add("content", data.content)
-                .Add("itemId", data.itemId)
-                .Add("itemName", data.itemName)
-                .Add("pic", data.pic)
-                .Add("couponAmount", data.couponAmount)
-                .Add("promotionPrice", data.promotionPrice)
-                .Add("taoToken", data.taoToken)
-                .Add("shortLinkurl", data.shortLinkurl)
-                .Add("deeplink_url", data.deeplink_url)
-                .Add("elapsedTime", data.elapsedTime)
-                .Add("subCode", data.subCode)
-                .Add("ip", data.ip)
-                .Add("oaid", data.oaid)
-                .Add("riskStrategy", data.riskStrategy)
-                .Add("launchScene", data.launchScene)
-                .Add("create_time", data.create_time)
-                .Add("parse_type", data.parse_type)
-                .Create(DBContext.InsertType.NORMAL, transaction);
-        }
-
-    }
-
-}
+using dodohold.core;
+using CSRedis;
+using System.Data;
+using YunhuiKit;
+using static dodohold.core.ZTOExpress.CreateOrderArgs;
+using System.Xml.Linq;
+
+namespace molilian.core
+{
+    public partial class TkLogCore
+    {
+        static string queue_parse_jd_key = "queue:parse_logs:jd";
+
+
+        public static async Task<int> InsertParseJDLogAsync(int limit, YunhuiKit.RedisClient redis)
+        {
+            int total = 0;
+            using var connection = DBContext.GetOpenConnection();
+            connection.Open();
+            using var transaction = connection.BeginTransaction();
+            try
+            {
+                for (int i = 0; i < limit; i++)
+                {
+                    var data = await redis.LPopAsync<JdDataDTO>(queue_parse_jd_key);
+                    if (data == null) break;
+
+                    if (TestParseCore.InWhitelist(data.ip, data.oaid))
+                    {
+                        save_jd_parse_logs(data, "jd_parse_logs_test", connection, transaction);
+                    }
+                    else
+                    {
+                        if ("dp".Equals(data.parse_type))
+                        {
+                            string daily_table = $"jd_dp_logs_{data.create_time:yyyyMMdd}";
+                            save_jd_parse_logs(data, daily_table, connection, transaction);
+
+                            if (data.success)
+                            {
+                                save_jd_parse_logs(data, "jd_dp_logs_success", connection, transaction);
+                            }
+                        }
+
+                        //save_jd_parse_logs(data, "jd_parse_logs", connection, transaction);
+                        //if (save_dailys_log)
+                        {
+                            string daily_table = $"jd_parse_logs_{data.create_time:yyyyMMdd}";
+                            save_jd_parse_logs(data, daily_table, connection, transaction);
+                        }
+
+                        if (data.elapsedTime > 1000)
+                        {
+                            save_jd_parse_logs(data, "jd_parse_logs_test", connection, transaction);
+                        }
+
+                        if (data.success)
+                        {
+                            save_jd_parse_logs(data, "jd_parse_logs_success", connection, transaction);
+                        }
+                    }
+                    total++;
+                }
+                transaction.Commit();
+            }
+            catch (Exception ex)
+            {
+                transaction.Rollback();
+                new LoggerLibrary("TkLogCore_error", "task_insert_parse_jd_logs")
+                      .Info(ex.Message, ex.StackTrace)
+                      .SaveAsync();
+                throw;
+            }
+            finally
+            {
+                connection.Close();
+            }
+            return total;
+        }
+
+        public static int task_insert_parse_jd_logs(int limit, CSRedisClient redis)
+        {
+            int total = 0;
+            using var connection = DBContext.GetOpenConnection();
+            connection.Open();
+            using var transaction = connection.BeginTransaction();
+            try
+            {
+                for (int i = 0; i < limit; i++)
+                {
+                    var data = redis.LPop<JdDataDTO>(queue_parse_jd_key);
+                    if (data == null) break;
+
+                    if (TestParseCore.InWhitelist(data.ip, data.oaid))
+                    {
+                        save_jd_parse_logs(data, "jd_parse_logs_test", connection, transaction);
+                    }
+                    else
+                    {
+                        //save_jd_parse_logs(data, "jd_parse_logs", connection, transaction);
+
+                        //if (save_dailys_log)
+                        {
+                            string daily_table = $"jd_parse_logs_{data.create_time:yyyyMMdd}";
+                            save_jd_parse_logs(data, daily_table, connection, transaction);
+                        }
+
+                        if (data.elapsedTime > 1000)
+                        {
+                            save_jd_parse_logs(data, "jd_parse_logs_test", connection, transaction);
+                        }
+                        if (data.success)
+                        {
+                            save_jd_parse_logs(data, "jd_parse_logs_success", connection, transaction);
+                        }
+                    }
+                    total++;
+                }
+                transaction.Commit();
+            }
+            catch (Exception ex)
+            {
+                transaction.Rollback();
+                new LoggerLibrary("TkLogCore_error", "task_insert_parse_jd_logs")
+                      .Info(ex.Message, ex.StackTrace)
+                      .SaveAsync();
+                throw;
+            }
+            finally
+            {
+                connection.Close();
+            }
+            return total;
+        }
+
+        public static async Task ParseLogAsync(JdDataDTO response, bool is_hide)
+        {
+            try
+            {
+                var ts = DateTime.Now - response.create_time;
+                response.elapsedTime = (int)ts.TotalMilliseconds;
+                _ = RedisKit.RPushAsync(queue_parse_jd_key, response);
+
+                if (!TestParseCore.InWhitelist(response.ip, response.oaid))
+                {
+                    await SaveParseCacheAsync(response.channel.ToString(), response.accountId,
+                        response.accountName, response.success, response.message, response.reason,
+                        response.deeplink_url);
+                }
+
+                if (response.success) await saveClientRequestTotalAsync(response.channel, response.ip, response.oaid);
+                if (response.success || response.message.Equals("转链失败"))
+                {
+                    await RiskControlCore.CallsIncrByAsync(response.channel, response.accountId);
+                    //saveClientRequestTotal(response.channel, response.ip, response.oaid);
+                }
+
+                //if (!response.success && ("nologin".Equals(response.reason) ||
+                //    "方法不存在".Equals(response.reason) ||
+                //    "未登录".Equals(response.reason)))
+                //{
+                //    await JdPoolCore.DisabledAsync(response.accountId, response.accountName, $"{response.rawContent}\n{response.rawContent2}");
+                //}
+
+                if ("没有匹配账号".Equals(response.reason))
+                {
+                    await JdPoolCore.AccountExhaustedAsync();
+                }
+
+                string failure_key = $":total:continuous_failure:{response.accountId}";
+                string failure_lock_key = $":lock:continuous_failure:{response.accountId}";
+
+                if (response.success) await RedisKit.DelAsync(failure_key);
+
+                //连续错误发送报警
+                if ("签名失败".Equals(response.reason) || "网络异常".Equals(response.reason))
+                {
+                    var total = await RedisKit.IncrByAsync(failure_key);
+                    await RedisKit.ExpireAsync(failure_key, 3600);
+
+                    if (total > 30)
+                    {
+                        //没有锁定记录才发送,
+                        string lockfFlag = await RedisKit.GetAsync<string>(failure_lock_key);
+                        if (string.IsNullOrEmpty(lockfFlag))
+                        {
+                            //发送锁,保存半小时(半小时内只发送1次)
+                            await RedisKit.SetAsync(failure_lock_key, 1, 1800);
+
+                            string message = $"【京东{response.accountName}】{EndPointCore.CurrentEndPoint},连续的{response.reason}";
+                            await RedisKit.ExpireAsync(failure_key, 3600);
+                            NotifyCore.Notify(new NifyMessage
+                            {
+                                message = message,
+                                priority = NifyMessagePriority.high,
+                                tags = ["red_circle"]
+                            });
+
+                            if (!is_hide)
+                            {
+                                _ = NotifyCore.QYWeixinPushNotifyAsync(response.reason, message);
+                            }
+                        }
+                    }
+                }
+
+                string nologin_key = $":total:continuous_nologin:{response.accountId}";
+                string nologin_lock_key = $":lock:continuous_nologin:{response.accountId}";
+
+                //连续错误发送报警
+                if (!response.success && ("nologin".Equals(response.reason) ||
+                    "方法不存在".Equals(response.reason) ||
+                    "未登录".Equals(response.reason)))
+                {
+                    var total = await RedisKit.IncrByAsync(nologin_key);
+                    await RedisKit.ExpireAsync(nologin_key, 3600);
+
+                    if (total > 3)
+                    {
+                        //没有锁定记录才发送,
+                        string lockfFlag = await RedisKit.GetAsync<string>(nologin_lock_key);
+                        if (string.IsNullOrEmpty(lockfFlag))
+                        {
+                            await JdPoolCore.DisabledAsync(response.accountId, response.accountName, $"{response.rawContent}\n{response.rawContent2}");
+                            await RedisKit.SetAsync(nologin_lock_key, 1, 60);
+                            await RedisKit.ExpireAsync(nologin_key, 86400);
+                        }
+                    }
+                    else
+                    {
+                        NotifyCore.Notify(new NifyMessage
+                        {
+                            message = $"【京东联盟{response.accountId}:{response.accountName}】cookie 掉线({total})\n\n{response.rawContent}",
+                            priority = NifyMessagePriority.high,
+                            tags = ["red_circle"]
+                        });
+                    }
+                }
+                else
+                {
+                    await RedisKit.DelAsync(nologin_key);
+                }
+
+
+
+
+            }
+            catch (Exception ex) { }
+        }
+
+        private static int save_jd_parse_logs(JdDataDTO data, string tablename, IDbConnection connection, IDbTransaction transaction)
+        {
+            return new DBContext.Table(connection, tablename)
+                .Add("end_point", data.end_point)
+                .Add("channel", (int)data.channel)
+                .Add("accountId", data.accountId)
+                .Add("accountName", data.accountName)
+                .Add("proxy_name", data.proxy_name)
+                .Add("proxy_node", data.proxy_node)
+                .Add("curl_proxy", data.curl_proxy)
+                .Add("rawContent", data.rawContent)
+                .Add("success", data.success)
+                .Add("message", data.message)
+                .Add("reason", data.reason)
+                .Add("content", data.content)
+                .Add("itemId", data.itemId)
+                .Add("itemName", data.itemName)
+                .Add("pic", data.pic)
+                .Add("couponAmount", data.couponAmount)
+                .Add("promotionPrice", data.promotionPrice)
+                .Add("taoToken", data.taoToken)
+                .Add("shortLinkurl", data.shortLinkurl)
+                .Add("deeplink_url", data.deeplink_url)
+                .Add("elapsedTime", data.elapsedTime)
+                .Add("subCode", data.subCode)
+                .Add("ip", data.ip)
+                .Add("oaid", data.oaid)
+                .Add("riskStrategy", data.riskStrategy)
+                .Add("launchScene", data.launchScene)
+                .Add("create_time", data.create_time)
+                .Add("parse_type", data.parse_type)
+                .Create(DBContext.InsertType.NORMAL, transaction);
+        }
+
+    }
+
+}

+ 189 - 189
molilian.core/Core/log/ks.cs

@@ -1,189 +1,189 @@
-using dodohold.core;
-using CSRedis;
-using System.Data;
-using YunhuiKit;
-
-namespace molilian.core
-{
-    public partial class TkLogCore
-    {
-        static string queue_parse_ks_key = "queue:parse_logs:ks";
-
-
-
-
-        public static async Task<int> InsertParseKsLogAsync(int limit, YunhuiKit.RedisClient redis)
-        {
-            int total = 0;
-            using var connection = DBContext.GetOpenConnection();
-            connection.Open();
-            using var transaction = connection.BeginTransaction();
-            try
-            {
-                for (int i = 0; i < limit; i++)
-                {
-                    var data = await redis.LPopAsync<KsDataDTO>(queue_parse_ks_key);
-                    if (data == null) break;
-
-
-                    if (TestParseCore.InWhitelist(data.ip, data.oaid))
-                    {
-                        save_ks_parse_logs(data, "ks_parse_logs_test", connection, transaction);
-                    }
-                    else
-                    {
-                        //save_ks_parse_logs(data, "ks_parse_logs", connection, transaction);
-
-                        //if (save_dailys_log)
-                        {
-                            string daily_table = $"ks_parse_logs_{data.create_time:yyyyMMdd}";
-                            save_ks_parse_logs(data, daily_table, connection, transaction);
-                        }
-
-                        if (data.success)
-                        {
-                            save_ks_parse_logs(data, "ks_parse_logs_success", connection, transaction);
-                        }
-                    }
-                    total++;
-                }
-                transaction.Commit();
-            }
-            catch (Exception ex)
-            {
-                transaction.Rollback();
-                new LoggerLibrary("TkLogCore_error", "task_insert_parse_ks_logs")
-                      .Info(ex.Message, ex.StackTrace)
-                      .SaveAsync();
-                throw;
-            }
-            finally
-            {
-                connection.Close();
-            }
-            return total;
-        }
-
-        public static int task_insert_parse_ks_logs(int limit, CSRedisClient redis)
-        {
-            int total = 0;
-            using var connection = DBContext.GetOpenConnection();
-            connection.Open();
-            using var transaction = connection.BeginTransaction();
-            try
-            {
-                for (int i = 0; i < limit; i++)
-                {
-                    var data = redis.LPop<KsDataDTO>(queue_parse_ks_key);
-                    if (data == null) break;
-
-
-                    if (TestParseCore.InWhitelist(data.ip, data.oaid))
-                    {
-                        save_ks_parse_logs(data, "ks_parse_logs_test", connection, transaction);
-                    }
-                    else
-                    {
-                        //save_ks_parse_logs(data, "ks_parse_logs", connection, transaction);
-
-                        //if (save_dailys_log)
-                        {
-                            string daily_table = $"ks_parse_logs_{data.create_time:yyyyMMdd}";
-                            save_ks_parse_logs(data, daily_table, connection, transaction);
-                        }
-
-                        if (data.success)
-                        {
-                            save_ks_parse_logs(data, "ks_parse_logs_success", connection, transaction);
-                        }
-                    }
-                    total++;
-                }
-                transaction.Commit();
-            }
-            catch (Exception ex)
-            {
-                transaction.Rollback();
-                new LoggerLibrary("TkLogCore_error", "task_insert_parse_ks_logs")
-                      .Info(ex.Message, ex.StackTrace)
-                      .SaveAsync();
-                throw;
-            }
-            finally
-            {
-                connection.Close();
-            }
-            return total;
-        }
-
-        public static async Task ParseLogAsync(KsDataDTO response)
-        {
-            try
-            {
-                var ts = DateTime.Now - response.create_time;
-                response.elapsedTime = (int)ts.TotalMilliseconds;
-                _ = RedisKit.RPushAsync(queue_parse_ks_key, response);
-
-                if (!TestParseCore.InWhitelist(response.ip, response.oaid))
-                {
-                    await SaveParseCacheAsync(response.channel.ToString(), response.accountId,
-                        response.accountName, response.success, response.message, response.reason,
-                        response.deeplink_url);
-                }
-
-                //if (response.success) saveClientRequestTotal(response.channel, response.ip, response.oaid);
-                if (response.success || response.message.Equals("转链失败"))
-                {
-                    await RiskControlCore.CallsIncrByAsync(response.channel, response.accountId);
-                    await saveClientRequestTotalAsync(response.channel, response.ip, response.oaid);
-                }
-                if (!response.success && ("nologin".Equals(response.reason) ||
-                    "方法不存在".Equals(response.reason) ||
-                    "未登录".Equals(response.reason)))
-                {
-                    KsPoolCore.Disabled(response.accountId, response.accountName, $"{response.rawContent}\n{response.rawContent2}");
-                }
-                if ("没有匹配账号".Equals(response.reason))
-                {
-                    KsPoolCore.AccountExhausted();
-                }
-
-                if ("TOKEN过期".Equals(response.reason))
-                {
-                    KsPoolCore.NotifyInterfaceError(response.reason);
-                }
-            }
-            catch (Exception ex) { }
-        }
-        private static int save_ks_parse_logs(KsDataDTO data, string tablename, IDbConnection connection, IDbTransaction transaction)
-        {
-            return new DBContext.Table(connection, tablename)
-                .Add("end_point", data.end_point)
-                .Add("channel", (int)data.channel)
-                .Add("accountId", data.accountId)
-                .Add("accountName", data.accountName)
-                .Add("proxy_node", data.proxy_node)
-                .Add("rawContent", data.rawContent)
-                .Add("success", data.success)
-                .Add("message", data.message)
-                .Add("reason", data.reason)
-                .Add("content", data.content)
-                .Add("itemId", data.itemId)
-                .Add("itemName", data.itemName)
-                .Add("pic", data.pic)
-                .Add("couponAmount", data.couponAmount)
-                .Add("promotionPrice", data.promotionPrice)
-                .Add("taoToken", data.taoToken)
-                .Add("shortLinkurl", data.shortLinkurl)
-                .Add("deeplink_url", data.deeplink_url)
-                .Add("elapsedTime", data.elapsedTime)
-                .Add("subCode", data.subCode)
-                .Add("ip", data.ip)
-                .Add("oaid", data.oaid)
-                .Add("create_time", data.create_time)
-                .Create(DBContext.InsertType.NORMAL, transaction);
-        }
-
-    }
-
-}
+using dodohold.core;
+using CSRedis;
+using System.Data;
+using YunhuiKit;
+
+namespace molilian.core
+{
+    public partial class TkLogCore
+    {
+        static string queue_parse_ks_key = "queue:parse_logs:ks";
+
+
+
+
+        public static async Task<int> InsertParseKsLogAsync(int limit, YunhuiKit.RedisClient redis)
+        {
+            int total = 0;
+            using var connection = DBContext.GetOpenConnection();
+            connection.Open();
+            using var transaction = connection.BeginTransaction();
+            try
+            {
+                for (int i = 0; i < limit; i++)
+                {
+                    var data = await redis.LPopAsync<KsDataDTO>(queue_parse_ks_key);
+                    if (data == null) break;
+
+
+                    if (TestParseCore.InWhitelist(data.ip, data.oaid))
+                    {
+                        save_ks_parse_logs(data, "ks_parse_logs_test", connection, transaction);
+                    }
+                    else
+                    {
+                        //save_ks_parse_logs(data, "ks_parse_logs", connection, transaction);
+
+                        //if (save_dailys_log)
+                        {
+                            string daily_table = $"ks_parse_logs_{data.create_time:yyyyMMdd}";
+                            save_ks_parse_logs(data, daily_table, connection, transaction);
+                        }
+
+                        if (data.success)
+                        {
+                            save_ks_parse_logs(data, "ks_parse_logs_success", connection, transaction);
+                        }
+                    }
+                    total++;
+                }
+                transaction.Commit();
+            }
+            catch (Exception ex)
+            {
+                transaction.Rollback();
+                new LoggerLibrary("TkLogCore_error", "task_insert_parse_ks_logs")
+                      .Info(ex.Message, ex.StackTrace)
+                      .SaveAsync();
+                throw;
+            }
+            finally
+            {
+                connection.Close();
+            }
+            return total;
+        }
+
+        public static int task_insert_parse_ks_logs(int limit, CSRedisClient redis)
+        {
+            int total = 0;
+            using var connection = DBContext.GetOpenConnection();
+            connection.Open();
+            using var transaction = connection.BeginTransaction();
+            try
+            {
+                for (int i = 0; i < limit; i++)
+                {
+                    var data = redis.LPop<KsDataDTO>(queue_parse_ks_key);
+                    if (data == null) break;
+
+
+                    if (TestParseCore.InWhitelist(data.ip, data.oaid))
+                    {
+                        save_ks_parse_logs(data, "ks_parse_logs_test", connection, transaction);
+                    }
+                    else
+                    {
+                        //save_ks_parse_logs(data, "ks_parse_logs", connection, transaction);
+
+                        //if (save_dailys_log)
+                        {
+                            string daily_table = $"ks_parse_logs_{data.create_time:yyyyMMdd}";
+                            save_ks_parse_logs(data, daily_table, connection, transaction);
+                        }
+
+                        if (data.success)
+                        {
+                            save_ks_parse_logs(data, "ks_parse_logs_success", connection, transaction);
+                        }
+                    }
+                    total++;
+                }
+                transaction.Commit();
+            }
+            catch (Exception ex)
+            {
+                transaction.Rollback();
+                new LoggerLibrary("TkLogCore_error", "task_insert_parse_ks_logs")
+                      .Info(ex.Message, ex.StackTrace)
+                      .SaveAsync();
+                throw;
+            }
+            finally
+            {
+                connection.Close();
+            }
+            return total;
+        }
+
+        public static async Task ParseLogAsync(KsDataDTO response)
+        {
+            try
+            {
+                var ts = DateTime.Now - response.create_time;
+                response.elapsedTime = (int)ts.TotalMilliseconds;
+                _ = RedisKit.RPushAsync(queue_parse_ks_key, response);
+
+                if (!TestParseCore.InWhitelist(response.ip, response.oaid))
+                {
+                    await SaveParseCacheAsync(response.channel.ToString(), response.accountId,
+                        response.accountName, response.success, response.message, response.reason,
+                        response.deeplink_url);
+                }
+
+                //if (response.success) saveClientRequestTotal(response.channel, response.ip, response.oaid);
+                if (response.success || response.message.Equals("转链失败"))
+                {
+                    await RiskControlCore.CallsIncrByAsync(response.channel, response.accountId);
+                    await saveClientRequestTotalAsync(response.channel, response.ip, response.oaid);
+                }
+                if (!response.success && ("nologin".Equals(response.reason) ||
+                    "方法不存在".Equals(response.reason) ||
+                    "未登录".Equals(response.reason)))
+                {
+                    KsPoolCore.Disabled(response.accountId, response.accountName, $"{response.rawContent}\n{response.rawContent2}");
+                }
+                if ("没有匹配账号".Equals(response.reason))
+                {
+                    KsPoolCore.AccountExhausted();
+                }
+
+                if ("TOKEN过期".Equals(response.reason))
+                {
+                    KsPoolCore.NotifyInterfaceError(response.reason);
+                }
+            }
+            catch (Exception ex) { }
+        }
+        private static int save_ks_parse_logs(KsDataDTO data, string tablename, IDbConnection connection, IDbTransaction transaction)
+        {
+            return new DBContext.Table(connection, tablename)
+                .Add("end_point", data.end_point)
+                .Add("channel", (int)data.channel)
+                .Add("accountId", data.accountId)
+                .Add("accountName", data.accountName)
+                .Add("proxy_node", data.proxy_node)
+                .Add("rawContent", data.rawContent)
+                .Add("success", data.success)
+                .Add("message", data.message)
+                .Add("reason", data.reason)
+                .Add("content", data.content)
+                .Add("itemId", data.itemId)
+                .Add("itemName", data.itemName)
+                .Add("pic", data.pic)
+                .Add("couponAmount", data.couponAmount)
+                .Add("promotionPrice", data.promotionPrice)
+                .Add("taoToken", data.taoToken)
+                .Add("shortLinkurl", data.shortLinkurl)
+                .Add("deeplink_url", data.deeplink_url)
+                .Add("elapsedTime", data.elapsedTime)
+                .Add("subCode", data.subCode)
+                .Add("ip", data.ip)
+                .Add("oaid", data.oaid)
+                .Add("create_time", data.create_time)
+                .Create(DBContext.InsertType.NORMAL, transaction);
+        }
+
+    }
+
+}

+ 198 - 198
molilian.core/Core/log/pdd.cs

@@ -1,198 +1,198 @@
-using dodohold.core;
-using CSRedis;
-using System.Data;
-using YunhuiKit;
-
-namespace molilian.core
-{
-    public partial class TkLogCore
-    {
-        static string queue_parse_pdd_key = "queue:parse_logs:pdd";
-
-
-        public static async Task<int> InsertParsePddLogAsync(int limit, YunhuiKit.RedisClient redis)
-        {
-            int total = 0;
-            using var connection = DBContext.GetOpenConnection();
-            connection.Open();
-            using var transaction = connection.BeginTransaction();
-            try
-            {
-                for (int i = 0; i < limit; i++)
-                {
-                    var data = await redis.LPopAsync<PddDataDTO>(queue_parse_pdd_key);
-                    if (data == null) break;
-
-                    if (TestParseCore.InWhitelist(data.ip, data.oaid))
-                    {
-                        save_pdd_parse_logs(data, "pdd_parse_logs_test", connection, transaction);
-
-                    }
-                    else
-                    {
-                        string daily_table;
-                        if ("dp".Equals(data.parse_type))
-                        {
-                            daily_table = $"pdd_dp_logs_{data.create_time:yyyyMMdd}";
-                            save_pdd_parse_logs(data, daily_table, connection, transaction);
-                        }
-
-                        daily_table = $"pdd_parse_logs_{data.create_time:yyyyMMdd}";
-                        save_pdd_parse_logs(data, daily_table, connection, transaction);
-
-                        if (!data.success)
-                        {
-                            save_pdd_parse_logs(data, "pdd_parse_logs_fail", connection, transaction);
-                        }
-                    }
-                    total++;
-                }
-                transaction.Commit();
-            }
-            catch (Exception ex)
-            {
-                transaction.Rollback();
-                new LoggerLibrary("TkLogCore_error", "task_insert_parse_pdd_logs")
-                      .Info(ex.Message, ex.StackTrace)
-                      .SaveAsync();
-                throw;
-            }
-            finally
-            {
-                connection.Close();
-            }
-            return total;
-        }
-
-        public static int task_insert_parse_pdd_logs(int limit, CSRedisClient redis)
-        {
-            int total = 0;
-            using var connection = DBContext.GetOpenConnection();
-            connection.Open();
-            using var transaction = connection.BeginTransaction();
-            try
-            {
-                for (int i = 0; i < limit; i++)
-                {
-                    var data = redis.LPop<PddDataDTO>(queue_parse_pdd_key);
-                    if (data == null) break;
-
-
-                    if (TestParseCore.InWhitelist(data.ip, data.oaid))
-                    {
-                        save_pdd_parse_logs(data, "pdd_parse_logs_test", connection, transaction);
-                    }
-                    else
-                    {
-                        //save_pdd_parse_logs(data, "pdd_parse_logs", connection, transaction);
-                        //if (save_dailys_log)
-                        {
-                            string daily_table = $"pdd_parse_logs_{data.create_time:yyyyMMdd}";
-                            save_pdd_parse_logs(data, daily_table, connection, transaction);
-                        }
-                        if (!data.success)
-                        {
-                            save_pdd_parse_logs(data, "pdd_parse_logs_fail", connection, transaction);
-                        }
-                    }
-                    total++;
-                }
-                transaction.Commit();
-            }
-            catch (Exception ex)
-            {
-                transaction.Rollback();
-                new LoggerLibrary("TkLogCore_error", "task_insert_parse_pdd_logs")
-                      .Info(ex.Message, ex.StackTrace)
-                      .SaveAsync();
-                throw;
-            }
-            finally
-            {
-                connection.Close();
-            }
-            return total;
-        }
-
-        public static async Task ParseLogAsync(PddDataDTO response)
-        {
-            try
-            {
-                var ts = DateTime.Now - response.create_time;
-                response.elapsedTime = (int)ts.TotalMilliseconds;
-                _ = RedisKit.RPushAsync(queue_parse_pdd_key, response);
-
-                if (!TestParseCore.InWhitelist(response.ip, response.oaid))
-                {
-                    await SaveParseCacheAsync(response.channel.ToString(), response.accountId,
-                          response.accountName, response.success, response.message, response.reason,
-                          response.deeplink_url);
-                }
-
-                //if (response.success) saveClientRequestTotal(response.channel, response.ip, response.oaid);
-                if (response.success || response.message.Equals("转链失败"))
-                {
-                    await RiskControlCore.CallsIncrByAsync(response.channel, response.accountId);
-                    await saveClientRequestTotalAsync(response.channel, response.ip, response.oaid);
-                }
-                //30007:检测到您推广行为异常,链接生成失败
-                if (response.reason.Equals("您的调用次数过高"))
-                {
-                    PddPoolCore.TempSuspend(response.accountId);
-                }
-
-                if (!response.success && ("nologin".Equals(response.reason) ||
-                    "方法不存在".Equals(response.reason) ||
-                    "43001:会话已过期".Equals(response.reason) ||
-                    "未登录".Equals(response.reason)))
-                {
-                    PddPoolCore.Disabled(response.accountId, response.accountName, $"{response.rawContent}\n{response.rawContent2}");
-                }
-                if (response.reason.Contains("当前账号被禁止使用转链功能"))
-                {
-                    PddPoolCore.Disabled(response.accountId, response.accountName, response.reason);
-                }
-                if (response.reason.Contains("没有匹配账号"))
-                {
-                    PddPoolCore.AccountExhausted();
-                }
-            }
-            catch (Exception ex) { }
-        }
-        private static int save_pdd_parse_logs(PddDataDTO data, string tablename, IDbConnection connection, IDbTransaction transaction)
-        {
-            return new DBContext.Table(connection, tablename)
-                .Add("end_point", data.end_point)
-                .Add("channel", (int)data.channel)
-                .Add("accountId", data.accountId)
-                .Add("accountName", data.accountName)
-                .Add("proxy_node", data.proxy_node)
-                .Add("rawContent", data.rawContent)
-                .Add("rawContent2", data.rawContent2)
-                .Add("success", data.success)
-                .Add("message", data.message)
-                .Add("reason", data.reason)
-                .Add("content", data.content)
-                .Add("itemId", data.itemId)
-                .Add("itemName", data.itemName)
-                .Add("pic", data.pic)
-                .Add("couponAmount", data.couponAmount)
-                .Add("promotionPrice", data.promotionPrice)
-                .Add("taoToken", data.taoToken)
-                .Add("shortLinkurl", data.shortLinkurl)
-                .Add("deeplink_url", data.deeplink_url)
-                .Add("elapsedTime", data.elapsedTime)
-                .Add("elapsedTime2", data.elapsedTime2)
-                .Add("elapsedTime3", data.elapsedTime3)
-                .Add("subCode", data.subCode)
-                .Add("ip", data.ip)
-                .Add("oaid", data.oaid)
-                .Add("create_time", data.create_time)
-                .Add("parse_type", data.parse_type)
-                .Create(DBContext.InsertType.NORMAL, transaction);
-
-        }
-
-    }
-
-}
+using dodohold.core;
+using CSRedis;
+using System.Data;
+using YunhuiKit;
+
+namespace molilian.core
+{
+    public partial class TkLogCore
+    {
+        static string queue_parse_pdd_key = "queue:parse_logs:pdd";
+
+
+        public static async Task<int> InsertParsePddLogAsync(int limit, YunhuiKit.RedisClient redis)
+        {
+            int total = 0;
+            using var connection = DBContext.GetOpenConnection();
+            connection.Open();
+            using var transaction = connection.BeginTransaction();
+            try
+            {
+                for (int i = 0; i < limit; i++)
+                {
+                    var data = await redis.LPopAsync<PddDataDTO>(queue_parse_pdd_key);
+                    if (data == null) break;
+
+                    if (TestParseCore.InWhitelist(data.ip, data.oaid))
+                    {
+                        save_pdd_parse_logs(data, "pdd_parse_logs_test", connection, transaction);
+
+                    }
+                    else
+                    {
+                        string daily_table;
+                        if ("dp".Equals(data.parse_type))
+                        {
+                            daily_table = $"pdd_dp_logs_{data.create_time:yyyyMMdd}";
+                            save_pdd_parse_logs(data, daily_table, connection, transaction);
+                        }
+
+                        daily_table = $"pdd_parse_logs_{data.create_time:yyyyMMdd}";
+                        save_pdd_parse_logs(data, daily_table, connection, transaction);
+
+                        if (!data.success)
+                        {
+                            save_pdd_parse_logs(data, "pdd_parse_logs_fail", connection, transaction);
+                        }
+                    }
+                    total++;
+                }
+                transaction.Commit();
+            }
+            catch (Exception ex)
+            {
+                transaction.Rollback();
+                new LoggerLibrary("TkLogCore_error", "task_insert_parse_pdd_logs")
+                      .Info(ex.Message, ex.StackTrace)
+                      .SaveAsync();
+                throw;
+            }
+            finally
+            {
+                connection.Close();
+            }
+            return total;
+        }
+
+        public static int task_insert_parse_pdd_logs(int limit, CSRedisClient redis)
+        {
+            int total = 0;
+            using var connection = DBContext.GetOpenConnection();
+            connection.Open();
+            using var transaction = connection.BeginTransaction();
+            try
+            {
+                for (int i = 0; i < limit; i++)
+                {
+                    var data = redis.LPop<PddDataDTO>(queue_parse_pdd_key);
+                    if (data == null) break;
+
+
+                    if (TestParseCore.InWhitelist(data.ip, data.oaid))
+                    {
+                        save_pdd_parse_logs(data, "pdd_parse_logs_test", connection, transaction);
+                    }
+                    else
+                    {
+                        //save_pdd_parse_logs(data, "pdd_parse_logs", connection, transaction);
+                        //if (save_dailys_log)
+                        {
+                            string daily_table = $"pdd_parse_logs_{data.create_time:yyyyMMdd}";
+                            save_pdd_parse_logs(data, daily_table, connection, transaction);
+                        }
+                        if (!data.success)
+                        {
+                            save_pdd_parse_logs(data, "pdd_parse_logs_fail", connection, transaction);
+                        }
+                    }
+                    total++;
+                }
+                transaction.Commit();
+            }
+            catch (Exception ex)
+            {
+                transaction.Rollback();
+                new LoggerLibrary("TkLogCore_error", "task_insert_parse_pdd_logs")
+                      .Info(ex.Message, ex.StackTrace)
+                      .SaveAsync();
+                throw;
+            }
+            finally
+            {
+                connection.Close();
+            }
+            return total;
+        }
+
+        public static async Task ParseLogAsync(PddDataDTO response)
+        {
+            try
+            {
+                var ts = DateTime.Now - response.create_time;
+                response.elapsedTime = (int)ts.TotalMilliseconds;
+                _ = RedisKit.RPushAsync(queue_parse_pdd_key, response);
+
+                if (!TestParseCore.InWhitelist(response.ip, response.oaid))
+                {
+                    await SaveParseCacheAsync(response.channel.ToString(), response.accountId,
+                          response.accountName, response.success, response.message, response.reason,
+                          response.deeplink_url);
+                }
+
+                //if (response.success) saveClientRequestTotal(response.channel, response.ip, response.oaid);
+                if (response.success || response.message.Equals("转链失败"))
+                {
+                    await RiskControlCore.CallsIncrByAsync(response.channel, response.accountId);
+                    await saveClientRequestTotalAsync(response.channel, response.ip, response.oaid);
+                }
+                //30007:检测到您推广行为异常,链接生成失败
+                if (response.reason.Equals("您的调用次数过高"))
+                {
+                    PddPoolCore.TempSuspend(response.accountId);
+                }
+
+                if (!response.success && ("nologin".Equals(response.reason) ||
+                    "方法不存在".Equals(response.reason) ||
+                    "43001:会话已过期".Equals(response.reason) ||
+                    "未登录".Equals(response.reason)))
+                {
+                    PddPoolCore.Disabled(response.accountId, response.accountName, $"{response.rawContent}\n{response.rawContent2}");
+                }
+                if (response.reason.Contains("当前账号被禁止使用转链功能"))
+                {
+                    PddPoolCore.Disabled(response.accountId, response.accountName, response.reason);
+                }
+                if (response.reason.Contains("没有匹配账号"))
+                {
+                    PddPoolCore.AccountExhausted();
+                }
+            }
+            catch (Exception ex) { }
+        }
+        private static int save_pdd_parse_logs(PddDataDTO data, string tablename, IDbConnection connection, IDbTransaction transaction)
+        {
+            return new DBContext.Table(connection, tablename)
+                .Add("end_point", data.end_point)
+                .Add("channel", (int)data.channel)
+                .Add("accountId", data.accountId)
+                .Add("accountName", data.accountName)
+                .Add("proxy_node", data.proxy_node)
+                .Add("rawContent", data.rawContent)
+                .Add("rawContent2", data.rawContent2)
+                .Add("success", data.success)
+                .Add("message", data.message)
+                .Add("reason", data.reason)
+                .Add("content", data.content)
+                .Add("itemId", data.itemId)
+                .Add("itemName", data.itemName)
+                .Add("pic", data.pic)
+                .Add("couponAmount", data.couponAmount)
+                .Add("promotionPrice", data.promotionPrice)
+                .Add("taoToken", data.taoToken)
+                .Add("shortLinkurl", data.shortLinkurl)
+                .Add("deeplink_url", data.deeplink_url)
+                .Add("elapsedTime", data.elapsedTime)
+                .Add("elapsedTime2", data.elapsedTime2)
+                .Add("elapsedTime3", data.elapsedTime3)
+                .Add("subCode", data.subCode)
+                .Add("ip", data.ip)
+                .Add("oaid", data.oaid)
+                .Add("create_time", data.create_time)
+                .Add("parse_type", data.parse_type)
+                .Create(DBContext.InsertType.NORMAL, transaction);
+
+        }
+
+    }
+
+}

+ 198 - 198
molilian.core/Core/log/promotion.cs

@@ -1,198 +1,198 @@
-using dodohold.core;
-using CSRedis;
-using System.Data;
-using YunhuiKit;
-
-namespace molilian.core
-{
-    public partial class TkLogCore
-    {
-        static string promotion_img_key = "queue:promotion:img";
-
-        public static async Task<int> InsertPromotionImgAsync(int limit, YunhuiKit.RedisClient redis)
-        {
-            int total = 0;
-
-            using var connection = DBContext.GetOpenConnection();
-            connection.Open();
-            using var transaction = connection.BeginTransaction();
-            try
-            {
-                for (int i = 0; i < limit; i++)
-                {
-                    var data = await redis.LPopAsync<PromotionQueryDTO>(promotion_img_key);
-                    if (data == null) break;
-
-                    if (data.success)
-                    {
-                        data.similarPromotion = string.Empty;
-                        data.promotionImg = string.Empty;
-                    }
-
-                    //按日保存数据
-                    string daily_table = $"tk_promotion_logs_{data.create_time:yyyyMMdd}";
-                    save_tk_promotion_img_logs(data, daily_table, connection, transaction);
-
-                    total++;
-                }
-                transaction.Commit();
-            }
-            catch (Exception ex)
-            {
-                transaction.Rollback();
-                new LoggerLibrary("TkLogCore_error", "task_insert_parse_pdd_logs")
-                      .Info(ex.Message, ex.StackTrace)
-                      .SaveAsync();
-                throw;
-            }
-            finally
-            {
-                connection.Close();
-            }
-            return total;
-        }
-
-        public static int task_insert_promotion_img_logs(int limit, CSRedisClient redis)
-        {
-            int total = 0;
-
-            using var connection = DBContext.GetOpenConnection();
-            connection.Open();
-            using var transaction = connection.BeginTransaction();
-            try
-            {
-                for (int i = 0; i < limit; i++)
-                {
-                    var data = redis.LPop<PromotionQueryDTO>(promotion_img_key);
-                    if (data == null) break;
-
-                    if (data.success)
-                    {
-                        data.similarPromotion = string.Empty;
-                        data.promotionImg = string.Empty;
-                    }
-
-                    //按日保存数据
-                    string daily_table = $"tk_promotion_logs_{data.create_time:yyyyMMdd}";
-                    save_tk_promotion_img_logs(data, daily_table, connection, transaction);
-
-                    total++;
-                }
-                transaction.Commit();
-            }
-            catch (Exception ex)
-            {
-                transaction.Rollback();
-                new LoggerLibrary("TkLogCore_error", "task_insert_parse_pdd_logs")
-                      .Info(ex.Message, ex.StackTrace)
-                      .SaveAsync();
-                throw;
-            }
-            finally
-            {
-                connection.Close();
-            }
-            return total;
-        }
-        private static int save_tk_promotion_img_logs(PromotionQueryDTO data, string tablename, IDbConnection connection, IDbTransaction transaction)
-        {
-            try
-            {
-                return new DBContext.Table(connection, tablename)
-                    .Add("accountId", data.accountId)
-                    .Add("accountName", data.accountName)
-                    .Add("proxy_node", data.proxy_node)
-                    .Add("proxy_nodename", data.proxy_nodename)
-                    //.Add("img", data.img)
-                    .Add("img", string.Empty)
-                    .Add("ip", data.ip)
-                    .Add("oaid", data.oaid)
-                    .Add("scene", data.scene)
-                    .Add("success", data.success)
-                    .Add("message", data.message)
-                    .Add("reason", data.reason)
-                    .Add("url", data.url)
-                    .Add("similarPromotion", data.similarPromotion)
-                    .Add("promotionImg", data.promotionImg)
-
-                    .Add("elapsedTime", data.elapsedTime)
-                    .Add("elapsedTime2", data.elapsedTime2)
-                    .Add("elapsedTime3", data.elapsedTime3)
-                    .Add("elapsedTime4", data.elapsedTime4)
-                    .Add("create_time", data.create_time)
-                    .Add("end_point", data.end_point)
-                    .Create(DBContext.InsertType.NORMAL, transaction);
-            }
-            catch (Exception ex) { return 0; }
-        }
-
-        public static async Task PromotionImgLogAsync(PromotionQueryDTO response)
-        {
-            try
-            {
-                var ts = DateTime.Now - response.create_time;
-                response.elapsedTime = (int)ts.TotalMilliseconds;
-                _ = RedisKit.RPushAsync(promotion_img_key, response);
-
-                _ = savePromotionCacheAsync(response.accountId, response.accountName, response.scene, response.success, response.message, response.reason);
-            }
-            catch (Exception ex) { }
-        }
-
-
-
-
-
-        private static async Task savePromotionCacheAsync(int accountId, string accountName, string scene, bool success, string message, string reason)
-        {
-            await savePromotionAccountCacheAsync("all", success, message, reason);
-            await savePromotionAccountCacheAsync($"{accountId}", success, message, reason);
-            await savePromotionAccountCacheAsync($"all_scene_{scene}", success, message, reason);
-            await savePromotionAccountCacheAsync($"{accountId}_{scene}", success, message, reason);
-        }
-
-        private static async Task savePromotionAccountCacheAsync(string accountName, bool success, string message, string reason)
-        {
-            await RedisKit.IncrByAsync($":promotion_total:{accountName}:{DateTime.Now:yyyyMM}");
-            await RedisKit.IncrByAsync($":promotion_total:{accountName}:{DateTime.Now:yyyyMMdd}");
-            await RedisKit.IncrByAsync($":promotion_total:{accountName}:{DateTime.Now:yyyyMMddHH}");
-
-
-            string result = success ? "success" : "fail";
-            await RedisKit.IncrByAsync($":promotion_total:{accountName}:{result}:{DateTime.Now:yyyyMM}");
-            await RedisKit.IncrByAsync($":promotion_total:{accountName}:{result}:{DateTime.Now:yyyyMMdd}");
-            await RedisKit.IncrByAsync($":promotion_total:{accountName}:{result}:{DateTime.Now:yyyyMMddHH}");
-
-
-            if (!string.IsNullOrEmpty(message))
-            {
-                await RedisKit.SAddAsync($":promotion_total:{accountName}:message:{DateTime.Now:yyyyMM}", message);
-                await RedisKit.SAddAsync($":promotion_total:{accountName}:message:{DateTime.Now:yyyyMMdd}", message);
-                await RedisKit.SAddAsync($":promotion_total:{accountName}:message:{DateTime.Now:yyyyMMddHH}", message);
-
-                await RedisKit.IncrByAsync($":promotion_total:{accountName}:{message}:{DateTime.Now:yyyyMM}");
-                await RedisKit.IncrByAsync($":promotion_total:{accountName}:{message}:{DateTime.Now:yyyyMMdd}");
-                await RedisKit.IncrByAsync($":promotion_total:{accountName}:{message}:{DateTime.Now:yyyyMMddHH}");
-                await RedisKit.IncrByAsync($":promotion_total:{accountName}:message:{message}:{DateTime.Now:yyyyMM}");
-                await RedisKit.IncrByAsync($":promotion_total:{accountName}:message:{message}:{DateTime.Now:yyyyMMdd}");
-                await RedisKit.IncrByAsync($":promotion_total:{accountName}:message:{message}:{DateTime.Now:yyyyMMddHH}");
-            }
-
-            if (!string.IsNullOrEmpty(reason))
-            {
-                await RedisKit.SAddAsync($":promotion_total:{accountName}:reason:{DateTime.Now:yyyyMM}", reason);
-                await RedisKit.SAddAsync($":promotion_total:{accountName}:reason:{DateTime.Now:yyyyMMdd}", reason);
-                await RedisKit.SAddAsync($":promotion_total:{accountName}:reason:{DateTime.Now:yyyyMMddHH}", reason);
-
-                await RedisKit.IncrByAsync($":promotion_total:{accountName}:{reason}:{DateTime.Now:yyyyMM}");
-                await RedisKit.IncrByAsync($":promotion_total:{accountName}:{reason}:{DateTime.Now:yyyyMMdd}");
-                await RedisKit.IncrByAsync($":promotion_total:{accountName}:{reason}:{DateTime.Now:yyyyMMddHH}");
-                await RedisKit.IncrByAsync($":promotion_total:{accountName}:reason:{reason}:{DateTime.Now:yyyyMM}");
-                await RedisKit.IncrByAsync($":promotion_total:{accountName}:reason:{reason}:{DateTime.Now:yyyyMMdd}");
-                await RedisKit.IncrByAsync($":promotion_total:{accountName}:reason:{reason}:{DateTime.Now:yyyyMMddHH}");
-            }
-        }
-
-    }
-
-}
+using dodohold.core;
+using CSRedis;
+using System.Data;
+using YunhuiKit;
+
+namespace molilian.core
+{
+    public partial class TkLogCore
+    {
+        static string promotion_img_key = "queue:promotion:img";
+
+        public static async Task<int> InsertPromotionImgAsync(int limit, YunhuiKit.RedisClient redis)
+        {
+            int total = 0;
+
+            using var connection = DBContext.GetOpenConnection();
+            connection.Open();
+            using var transaction = connection.BeginTransaction();
+            try
+            {
+                for (int i = 0; i < limit; i++)
+                {
+                    var data = await redis.LPopAsync<PromotionQueryDTO>(promotion_img_key);
+                    if (data == null) break;
+
+                    if (data.success)
+                    {
+                        data.similarPromotion = string.Empty;
+                        data.promotionImg = string.Empty;
+                    }
+
+                    //按日保存数据
+                    string daily_table = $"tk_promotion_logs_{data.create_time:yyyyMMdd}";
+                    save_tk_promotion_img_logs(data, daily_table, connection, transaction);
+
+                    total++;
+                }
+                transaction.Commit();
+            }
+            catch (Exception ex)
+            {
+                transaction.Rollback();
+                new LoggerLibrary("TkLogCore_error", "task_insert_parse_pdd_logs")
+                      .Info(ex.Message, ex.StackTrace)
+                      .SaveAsync();
+                throw;
+            }
+            finally
+            {
+                connection.Close();
+            }
+            return total;
+        }
+
+        public static int task_insert_promotion_img_logs(int limit, CSRedisClient redis)
+        {
+            int total = 0;
+
+            using var connection = DBContext.GetOpenConnection();
+            connection.Open();
+            using var transaction = connection.BeginTransaction();
+            try
+            {
+                for (int i = 0; i < limit; i++)
+                {
+                    var data = redis.LPop<PromotionQueryDTO>(promotion_img_key);
+                    if (data == null) break;
+
+                    if (data.success)
+                    {
+                        data.similarPromotion = string.Empty;
+                        data.promotionImg = string.Empty;
+                    }
+
+                    //按日保存数据
+                    string daily_table = $"tk_promotion_logs_{data.create_time:yyyyMMdd}";
+                    save_tk_promotion_img_logs(data, daily_table, connection, transaction);
+
+                    total++;
+                }
+                transaction.Commit();
+            }
+            catch (Exception ex)
+            {
+                transaction.Rollback();
+                new LoggerLibrary("TkLogCore_error", "task_insert_parse_pdd_logs")
+                      .Info(ex.Message, ex.StackTrace)
+                      .SaveAsync();
+                throw;
+            }
+            finally
+            {
+                connection.Close();
+            }
+            return total;
+        }
+        private static int save_tk_promotion_img_logs(PromotionQueryDTO data, string tablename, IDbConnection connection, IDbTransaction transaction)
+        {
+            try
+            {
+                return new DBContext.Table(connection, tablename)
+                    .Add("accountId", data.accountId)
+                    .Add("accountName", data.accountName)
+                    .Add("proxy_node", data.proxy_node)
+                    .Add("proxy_nodename", data.proxy_nodename)
+                    //.Add("img", data.img)
+                    .Add("img", string.Empty)
+                    .Add("ip", data.ip)
+                    .Add("oaid", data.oaid)
+                    .Add("scene", data.scene)
+                    .Add("success", data.success)
+                    .Add("message", data.message)
+                    .Add("reason", data.reason)
+                    .Add("url", data.url)
+                    .Add("similarPromotion", data.similarPromotion)
+                    .Add("promotionImg", data.promotionImg)
+
+                    .Add("elapsedTime", data.elapsedTime)
+                    .Add("elapsedTime2", data.elapsedTime2)
+                    .Add("elapsedTime3", data.elapsedTime3)
+                    .Add("elapsedTime4", data.elapsedTime4)
+                    .Add("create_time", data.create_time)
+                    .Add("end_point", data.end_point)
+                    .Create(DBContext.InsertType.NORMAL, transaction);
+            }
+            catch (Exception ex) { return 0; }
+        }
+
+        public static async Task PromotionImgLogAsync(PromotionQueryDTO response)
+        {
+            try
+            {
+                var ts = DateTime.Now - response.create_time;
+                response.elapsedTime = (int)ts.TotalMilliseconds;
+                _ = RedisKit.RPushAsync(promotion_img_key, response);
+
+                _ = savePromotionCacheAsync(response.accountId, response.accountName, response.scene, response.success, response.message, response.reason);
+            }
+            catch (Exception ex) { }
+        }
+
+
+
+
+
+        private static async Task savePromotionCacheAsync(int accountId, string accountName, string scene, bool success, string message, string reason)
+        {
+            await savePromotionAccountCacheAsync("all", success, message, reason);
+            await savePromotionAccountCacheAsync($"{accountId}", success, message, reason);
+            await savePromotionAccountCacheAsync($"all_scene_{scene}", success, message, reason);
+            await savePromotionAccountCacheAsync($"{accountId}_{scene}", success, message, reason);
+        }
+
+        private static async Task savePromotionAccountCacheAsync(string accountName, bool success, string message, string reason)
+        {
+            await RedisKit.IncrByAsync($":promotion_total:{accountName}:{DateTime.Now:yyyyMM}");
+            await RedisKit.IncrByAsync($":promotion_total:{accountName}:{DateTime.Now:yyyyMMdd}");
+            await RedisKit.IncrByAsync($":promotion_total:{accountName}:{DateTime.Now:yyyyMMddHH}");
+
+
+            string result = success ? "success" : "fail";
+            await RedisKit.IncrByAsync($":promotion_total:{accountName}:{result}:{DateTime.Now:yyyyMM}");
+            await RedisKit.IncrByAsync($":promotion_total:{accountName}:{result}:{DateTime.Now:yyyyMMdd}");
+            await RedisKit.IncrByAsync($":promotion_total:{accountName}:{result}:{DateTime.Now:yyyyMMddHH}");
+
+
+            if (!string.IsNullOrEmpty(message))
+            {
+                await RedisKit.SAddAsync($":promotion_total:{accountName}:message:{DateTime.Now:yyyyMM}", message);
+                await RedisKit.SAddAsync($":promotion_total:{accountName}:message:{DateTime.Now:yyyyMMdd}", message);
+                await RedisKit.SAddAsync($":promotion_total:{accountName}:message:{DateTime.Now:yyyyMMddHH}", message);
+
+                await RedisKit.IncrByAsync($":promotion_total:{accountName}:{message}:{DateTime.Now:yyyyMM}");
+                await RedisKit.IncrByAsync($":promotion_total:{accountName}:{message}:{DateTime.Now:yyyyMMdd}");
+                await RedisKit.IncrByAsync($":promotion_total:{accountName}:{message}:{DateTime.Now:yyyyMMddHH}");
+                await RedisKit.IncrByAsync($":promotion_total:{accountName}:message:{message}:{DateTime.Now:yyyyMM}");
+                await RedisKit.IncrByAsync($":promotion_total:{accountName}:message:{message}:{DateTime.Now:yyyyMMdd}");
+                await RedisKit.IncrByAsync($":promotion_total:{accountName}:message:{message}:{DateTime.Now:yyyyMMddHH}");
+            }
+
+            if (!string.IsNullOrEmpty(reason))
+            {
+                await RedisKit.SAddAsync($":promotion_total:{accountName}:reason:{DateTime.Now:yyyyMM}", reason);
+                await RedisKit.SAddAsync($":promotion_total:{accountName}:reason:{DateTime.Now:yyyyMMdd}", reason);
+                await RedisKit.SAddAsync($":promotion_total:{accountName}:reason:{DateTime.Now:yyyyMMddHH}", reason);
+
+                await RedisKit.IncrByAsync($":promotion_total:{accountName}:{reason}:{DateTime.Now:yyyyMM}");
+                await RedisKit.IncrByAsync($":promotion_total:{accountName}:{reason}:{DateTime.Now:yyyyMMdd}");
+                await RedisKit.IncrByAsync($":promotion_total:{accountName}:{reason}:{DateTime.Now:yyyyMMddHH}");
+                await RedisKit.IncrByAsync($":promotion_total:{accountName}:reason:{reason}:{DateTime.Now:yyyyMM}");
+                await RedisKit.IncrByAsync($":promotion_total:{accountName}:reason:{reason}:{DateTime.Now:yyyyMMdd}");
+                await RedisKit.IncrByAsync($":promotion_total:{accountName}:reason:{reason}:{DateTime.Now:yyyyMMddHH}");
+            }
+        }
+
+    }
+
+}

+ 209 - 209
molilian.core/Core/log/taobao.cs

@@ -1,209 +1,209 @@
-using dodohold.core;
-using CSRedis;
-using System.Data;
-using YunhuiKit;
-
-namespace molilian.core
-{
-    public partial class TkLogCore
-    {
-
-        static string queue_parse_tb_key = "queue:parse_logs:tb";
-
-        public static async Task<int> InsertParseTkLogAsync(int limit, YunhuiKit.RedisClient redis)
-        {
-            int total = 0;
-            using var connection = DBContext.GetOpenConnection();
-            connection.Open();
-            using var transaction = connection.BeginTransaction();
-            try
-            {
-                for (int i = 0; i < limit; i++)
-                {
-                    var data = await redis.LPopAsync<TkDataDTO>(queue_parse_tb_key);
-                    if (data == null) break;
-
-                    if (TestParseCore.InWhitelist(data.ip, data.oaid))
-                    {
-                        save_tk_parse_logs(data, "tk_parse_logs_test", connection, transaction);
-                    }
-                    else
-                    {
-                        if ("dp".Equals(data.parse_type))
-                        {
-                            string daily_table = $"tk_dp_logs_{data.create_time:yyyyMMdd}";
-                            save_tk_parse_logs(data, daily_table, connection, transaction);
-
-                            if (data.success)
-                            {
-                                save_tk_parse_logs(data, "tk_dp_logs_success", connection, transaction);
-                            }
-                        }
-
-                        ////每日分表
-                        //if (save_dailys_log)
-                        {
-                            string daily_table = $"tk_parse_logs_{data.create_time:yyyyMMdd}";
-                            save_tk_parse_logs(data, daily_table, connection, transaction);
-                        }
-
-                        if (data.elapsedTime > 1000)
-                        {
-                            save_tk_parse_logs(data, "tk_parse_logs_test", connection, transaction);
-                        }
-
-                        if (data.reason.Contains("初步筛选2") && !data.rawContent.Contains("I:/kWqN5t623Hx"))
-                        {
-                            save_tk_parse_logs(data, "tk_parse_logs_test2", connection, transaction);
-                        }
-
-                        if (data.reason.Contains("初步筛选1.5"))
-                        {
-                            save_tk_parse_logs(data, "tk_parse_logs_multi_token", connection, transaction);
-                        }
-                        if (data.reason.Contains("霸下验证码"))
-                        {
-                            save_tk_parse_logs(data, "tk_parse_logs_captcha", connection, transaction);
-                        }
-
-                        if (data.success)
-                        {
-                            save_tk_parse_logs(data, "tk_success_parse_logs", connection, transaction);
-                        }
-                        //data.id = save_tk_parse_logs(data, "tk_parse_logs", connection, transaction);
-
-                        if (!string.IsNullOrEmpty(data.itemId)) TkOrderTrackingCore.SaveLinkSummary(data);
-                    }
-
-                    total++;
-                }
-                transaction.Commit();
-            }
-            catch (Exception ex)
-            {
-                transaction.Rollback();
-                new LoggerLibrary("TkLogCore_error", "task_insert_parse_tb_logs")
-                      .Info(ex.Message, ex.StackTrace)
-                      .SaveAsync();
-                throw;
-            }
-            finally
-            {
-                connection.Close();
-            }
-            return total;
-        }
-
-        public static async Task ParseLogAsync(TkDataDTO response, AlimamaPlus? alimamaPlus = null)
-        {
-            try
-            {
-                var ts = DateTime.Now - response.create_time;
-                response.elapsedTime = (int)ts.TotalMilliseconds;
-                _ = RedisKit.RPushAsync(queue_parse_tb_key, response);
-
-                if (response.success || response.message.Equals("转链失败"))
-                {
-                    await RiskControlCore.CallsIncrByAsync(response.channel, response.accountId);
-                    await saveClientRequestTotalAsync(response.channel, response.ip, response.oaid);
-                }
-                if (response.success)
-                {
-                    _ = saveUnionCouponParseCacheAsync(response);
-                }
-                if (!TestParseCore.InWhitelist(response.ip, response.oaid))
-                {
-                    await SaveParseCacheAsync(response.channel.ToString(), response.accountId,
-                        response.accountName, response.success, response.message, response.reason,
-                        response.deeplink_url);
-                }
-                if (!string.IsNullOrEmpty(response.itemId)) TkOrderTrackingCore.SaveLinkSummary(response);
-
-
-                var account = await TkPoolCore.ALLGetOneAsync(response.accountId);
-                bool is_hide = false;
-                if (account != null) is_hide = account.is_hide;
-
-                if (!response.success && "nologin".Equals(response.message))
-                {
-                    switch (response.channel)
-                    {
-                        case TkChannelEnum.tb:
-                            if (alimamaPlus != null)
-                            {
-                                (bool success, string message) = alimamaPlus.RenewCookie();
-                                if (success) return;
-                            }
-                            TkPoolCore.Disabled(response.accountId, response.accountName, $"{response.rawContent}", is_hide);
-                            break;
-                    }
-                }
-                if (response.subCode == TkSubCodeEnum.ParseDeny && "接口风控ALL".Equals(response.reason))
-                {
-                    TkPoolCore.Disabled(response.accountId, response.accountName, "接口风控NONE", is_hide);
-                    //todo  计数器+1
-                }
-
-                if (response.subCode == TkSubCodeEnum.Captcha || "霸下验证码".Equals(response.reason))
-                {
-                    TkPoolCore.Suspend(response.end_point, response.accountId, response.accountName, "霸下验证码");
-                }
-            }
-            catch (Exception ex)
-            {
-                _ = new LoggerLibrary("unionParse", "database_error")
-                .Info(response.rawContent)
-                .Info(response.Convert2Json())
-                .Info(ex.Message, ex.StackTrace)
-                .SaveAsync();
-            }
-        }
-        private static int save_tk_parse_logs(TkDataDTO data, string tablename, IDbConnection connection, IDbTransaction transaction)
-        {
-            return new DBContext.Table(connection, tablename)
-                .Add("end_point", data.end_point)
-                .Add("channel", (int)data.channel)
-                .Add("linkType", (int)data.link_type)
-                .Add("accountId", data.accountId)
-                .Add("accountName", data.accountName)
-                .Add("proxy_node", data.proxy_node)
-                .Add("tkEndpoint", data.tkEndpoint)
-                .Add("rawContent", data.rawContent)
-                .Add("success", data.success)
-                .Add("message", data.message)
-                .Add("reason", data.reason)
-                .Add("content", data.content)
-                .Add("mktId", data.mktId)
-                .Add("itemId", data.itemId)
-                .Add("itemName", data.itemName)
-                .Add("pic", data.pic)
-                .Add("couponAmount", data.couponAmount)
-                .Add("promotionPrice", data.promotionPrice)
-                .Add("taoToken", data.taoToken)
-                .Add("shortLinkurl", data.shortLinkurl)
-                .Add("deeplink_url", data.deeplink_url)
-                .Add("num_iid", data.num_iid)
-                .Add("elapsedTime", data.elapsedTime)
-                .Add("elapsedTime2", data.elapsedTime2)
-                .Add("elapsedTime3", data.elapsedTime3)
-                .Add("retry_count", data.retry_count)
-                .Add("subCode", data.subCode)
-                .Add("ip", data.ip)
-                .Add("oaid", data.oaid)
-                .Add("riskStrategy", data.riskStrategy)
-                .Add("launchScene", data.launchScene)
-                .Add("item_url", data.item_url)
-                .Add("item_deeplink_url", data.item_deeplink_url)
-
-                .Add("create_time", data.create_time)
-                .Add("parse_type", data.parse_type)
-                .Add("remote_response", data.remote_response)
-                .Add("commercial", data.commercial)
-                .Add("with_middle_page", data.with_middle_page)
-                .Create(DBContext.InsertType.NORMAL, transaction);
-        }
-
-
-    }
-
-}
+using dodohold.core;
+using CSRedis;
+using System.Data;
+using YunhuiKit;
+
+namespace molilian.core
+{
+    public partial class TkLogCore
+    {
+
+        static string queue_parse_tb_key = "queue:parse_logs:tb";
+
+        public static async Task<int> InsertParseTkLogAsync(int limit, YunhuiKit.RedisClient redis)
+        {
+            int total = 0;
+            using var connection = DBContext.GetOpenConnection();
+            connection.Open();
+            using var transaction = connection.BeginTransaction();
+            try
+            {
+                for (int i = 0; i < limit; i++)
+                {
+                    var data = await redis.LPopAsync<TkDataDTO>(queue_parse_tb_key);
+                    if (data == null) break;
+
+                    if (TestParseCore.InWhitelist(data.ip, data.oaid))
+                    {
+                        save_tk_parse_logs(data, "tk_parse_logs_test", connection, transaction);
+                    }
+                    else
+                    {
+                        if ("dp".Equals(data.parse_type))
+                        {
+                            string daily_table = $"tk_dp_logs_{data.create_time:yyyyMMdd}";
+                            save_tk_parse_logs(data, daily_table, connection, transaction);
+
+                            if (data.success)
+                            {
+                                save_tk_parse_logs(data, "tk_dp_logs_success", connection, transaction);
+                            }
+                        }
+
+                        ////每日分表
+                        //if (save_dailys_log)
+                        {
+                            string daily_table = $"tk_parse_logs_{data.create_time:yyyyMMdd}";
+                            save_tk_parse_logs(data, daily_table, connection, transaction);
+                        }
+
+                        if (data.elapsedTime > 1000)
+                        {
+                            save_tk_parse_logs(data, "tk_parse_logs_test", connection, transaction);
+                        }
+
+                        if (data.reason.Contains("初步筛选2") && !data.rawContent.Contains("I:/kWqN5t623Hx"))
+                        {
+                            save_tk_parse_logs(data, "tk_parse_logs_test2", connection, transaction);
+                        }
+
+                        if (data.reason.Contains("初步筛选1.5"))
+                        {
+                            save_tk_parse_logs(data, "tk_parse_logs_multi_token", connection, transaction);
+                        }
+                        if (data.reason.Contains("霸下验证码"))
+                        {
+                            save_tk_parse_logs(data, "tk_parse_logs_captcha", connection, transaction);
+                        }
+
+                        if (data.success)
+                        {
+                            save_tk_parse_logs(data, "tk_success_parse_logs", connection, transaction);
+                        }
+                        //data.id = save_tk_parse_logs(data, "tk_parse_logs", connection, transaction);
+
+                        if (!string.IsNullOrEmpty(data.itemId)) TkOrderTrackingCore.SaveLinkSummary(data);
+                    }
+
+                    total++;
+                }
+                transaction.Commit();
+            }
+            catch (Exception ex)
+            {
+                transaction.Rollback();
+                new LoggerLibrary("TkLogCore_error", "task_insert_parse_tb_logs")
+                      .Info(ex.Message, ex.StackTrace)
+                      .SaveAsync();
+                throw;
+            }
+            finally
+            {
+                connection.Close();
+            }
+            return total;
+        }
+
+        public static async Task ParseLogAsync(TkDataDTO response, AlimamaPlus? alimamaPlus = null)
+        {
+            try
+            {
+                var ts = DateTime.Now - response.create_time;
+                response.elapsedTime = (int)ts.TotalMilliseconds;
+                _ = RedisKit.RPushAsync(queue_parse_tb_key, response);
+
+                if (response.success || response.message.Equals("转链失败"))
+                {
+                    await RiskControlCore.CallsIncrByAsync(response.channel, response.accountId);
+                    await saveClientRequestTotalAsync(response.channel, response.ip, response.oaid);
+                }
+                if (response.success)
+                {
+                    _ = saveUnionCouponParseCacheAsync(response);
+                }
+                if (!TestParseCore.InWhitelist(response.ip, response.oaid))
+                {
+                    await SaveParseCacheAsync(response.channel.ToString(), response.accountId,
+                        response.accountName, response.success, response.message, response.reason,
+                        response.deeplink_url);
+                }
+                if (!string.IsNullOrEmpty(response.itemId)) TkOrderTrackingCore.SaveLinkSummary(response);
+
+
+                var account = await TkPoolCore.ALLGetOneAsync(response.accountId);
+                bool is_hide = false;
+                if (account != null) is_hide = account.is_hide;
+
+                if (!response.success && "nologin".Equals(response.message))
+                {
+                    switch (response.channel)
+                    {
+                        case TkChannelEnum.tb:
+                            if (alimamaPlus != null)
+                            {
+                                (bool success, string message) = alimamaPlus.RenewCookie();
+                                if (success) return;
+                            }
+                            TkPoolCore.Disabled(response.accountId, response.accountName, $"{response.rawContent}", is_hide);
+                            break;
+                    }
+                }
+                if (response.subCode == TkSubCodeEnum.ParseDeny && "接口风控ALL".Equals(response.reason))
+                {
+                    TkPoolCore.Disabled(response.accountId, response.accountName, "接口风控NONE", is_hide);
+                    //todo  计数器+1
+                }
+
+                if (response.subCode == TkSubCodeEnum.Captcha || "霸下验证码".Equals(response.reason))
+                {
+                    TkPoolCore.Suspend(response.end_point, response.accountId, response.accountName, "霸下验证码");
+                }
+            }
+            catch (Exception ex)
+            {
+                _ = new LoggerLibrary("unionParse", "database_error")
+                .Info(response.rawContent)
+                .Info(response.Convert2Json())
+                .Info(ex.Message, ex.StackTrace)
+                .SaveAsync();
+            }
+        }
+        private static int save_tk_parse_logs(TkDataDTO data, string tablename, IDbConnection connection, IDbTransaction transaction)
+        {
+            return new DBContext.Table(connection, tablename)
+                .Add("end_point", data.end_point)
+                .Add("channel", (int)data.channel)
+                .Add("linkType", (int)data.link_type)
+                .Add("accountId", data.accountId)
+                .Add("accountName", data.accountName)
+                .Add("proxy_node", data.proxy_node)
+                .Add("tkEndpoint", data.tkEndpoint)
+                .Add("rawContent", data.rawContent)
+                .Add("success", data.success)
+                .Add("message", data.message)
+                .Add("reason", data.reason)
+                .Add("content", data.content)
+                .Add("mktId", data.mktId)
+                .Add("itemId", data.itemId)
+                .Add("itemName", data.itemName)
+                .Add("pic", data.pic)
+                .Add("couponAmount", data.couponAmount)
+                .Add("promotionPrice", data.promotionPrice)
+                .Add("taoToken", data.taoToken)
+                .Add("shortLinkurl", data.shortLinkurl)
+                .Add("deeplink_url", data.deeplink_url)
+                .Add("num_iid", data.num_iid)
+                .Add("elapsedTime", data.elapsedTime)
+                .Add("elapsedTime2", data.elapsedTime2)
+                .Add("elapsedTime3", data.elapsedTime3)
+                .Add("retry_count", data.retry_count)
+                .Add("subCode", data.subCode)
+                .Add("ip", data.ip)
+                .Add("oaid", data.oaid)
+                .Add("riskStrategy", data.riskStrategy)
+                .Add("launchScene", data.launchScene)
+                .Add("item_url", data.item_url)
+                .Add("item_deeplink_url", data.item_deeplink_url)
+
+                .Add("create_time", data.create_time)
+                .Add("parse_type", data.parse_type)
+                .Add("remote_response", data.remote_response)
+                .Add("commercial", data.commercial)
+                .Add("with_middle_page", data.with_middle_page)
+                .Create(DBContext.InsertType.NORMAL, transaction);
+        }
+
+
+    }
+
+}

+ 183 - 183
molilian.core/Core/log/tkActivity.cs

@@ -1,183 +1,183 @@
-using dodohold.core;
-using CSRedis;
-using System.Data;
-
-namespace molilian.core
-{
-    public partial class TkLogCore
-    {
-
-        static string queue_activity_key = "queue:activity_logs";
-
-        public static async Task<int> InsertActivityLogAsync(int limit, YunhuiKit.RedisClient redis)
-        {
-            int total = 0;
-            using var connection = DBContext.GetOpenConnection();
-            connection.Open();
-            using var transaction = connection.BeginTransaction();
-            try
-            {
-                for (int i = 0; i < limit; i++)
-                {
-                    var data = await redis.LPopAsync<TkActivityDTO>(queue_activity_key);
-                    if (data == null) break;
-
-                    if (TestParseCore.InWhitelist(data.ip, data.oaid))
-                    {
-                        // For test environment, could create a test version if needed
-                        string daily_table = $"tk_activity_logs_test";
-                        save_activity_logs(data, daily_table, connection, transaction);
-                    }
-                    else
-                    {
-                        string daily_table = $"tk_activity_logs_{data.create_time:yyyyMMdd}";
-                        save_activity_logs(data, daily_table, connection, transaction);
-                    }
-                    total++;
-                }
-                transaction.Commit();
-            }
-            catch (Exception ex)
-            {
-                transaction.Rollback();
-                new LoggerLibrary("TkLogCore_error", "task_insert_activity_logs")
-                      .Info(ex.Message, ex.StackTrace)
-                      .SaveAsync();
-                throw;
-            }
-            finally
-            {
-                connection.Close();
-            }
-            return total;
-        }
-
-        public static async Task TkActivityLogAsync(TkActivityDTO response)
-        {
-            try
-            {
-                if (response.create_time == null)
-                {
-                    response.create_time = DateTime.Now;
-                }
-
-                var ts = DateTime.Now - response.create_time.Value;
-                response.elapsedTime = (int)ts.TotalMilliseconds;
-
-                _ = RedisHelper.RPushAsync(queue_activity_key, response);
-
-                if (!TestParseCore.InWhitelist(response.ip, response.oaid))
-                {
-                    saveActivityCache(response.page_id, response.accountId, response.accountName, response.success, response.message, response.reason);
-                }
-
-                if (response.success) await saveClientRequestTotalAsync(response.channel, response.ip, response.oaid);
-
-                var account = await TkPoolCore.ALLGetOneAsync(response.accountId);
-                bool is_hide = account == null ? false : account.is_hide;
-
-                if (!response.success && "nologin".Equals(response.message))
-                {
-                    switch (response.channel)
-                    {
-                        case TkChannelEnum.tb:
-                            TkPoolCore.Disabled(response.accountId, response.accountName, $"{response.reason}", is_hide);
-                            break;
-                    }
-                }
-            }
-            catch (Exception ex)
-            {
-                _ = new LoggerLibrary("tkActivity", "database_error")
-                .Info(response.Convert2Json())
-                .Info(ex.Message, ex.StackTrace)
-                .SaveAsync();
-            }
-        }
-
-        private static void saveActivityCache(string channel, int accountId, string accountName, bool success, string message, string reason)
-        {
-            saveAccountActivityCache("all", success, message, reason);
-            saveAccountActivityCache($"{channel}", success, message, reason);
-            if (accountId != 0)
-            {
-                saveAccountActivityCache($"{channel}_{accountId}", success, message, reason);
-            }
-        }
-
-        private static int save_activity_logs(TkActivityDTO data, string tablename, IDbConnection connection, IDbTransaction transaction)
-        {
-            return new DBContext.Table(connection, tablename)
-                .Add("end_point", data.end_point)
-                .Add("channel", data.channel)
-                .Add("accountId", data.accountId)
-                .Add("accountName", data.accountName)
-                .Add("proxy_node", data.proxy_node)
-                .Add("page_id", data.page_id)
-                .Add("item_id", data.item_id)
-                .Add("success", data.success)
-                .Add("reason", data.reason)
-                .Add("message", data.message)
-                .Add("deeplink_url", data.deeplink_url)
-                .Add("create_time", data.create_time)
-                .Add("elapsedTime", data.elapsedTime)
-                .Add("elapsedTime2", data.elapsedTime2)
-                .Add("ip", data.ip)
-                .Add("oaid", data.oaid)
-                .Add("estimatedCommission", data.estimatedCommission)
-                .Add("maxCommission", data.maxCommission)
-                .Add("zkFinalPrice", data.zkFinalPrice)
-                .Add("reservePrice", data.reservePrice)
-                .Add("title", data.title)
-                .Add("picUrl", data.picUrl)
-                .Add("shortTitle", data.shortTitle)
-                .Add("priceAfterCoupon", data.priceAfterCoupon)
-                .Add("commissionRate", data.commissionRate)
-                .Add("couponAmount", data.couponAmount)
-                .Create(DBContext.InsertType.NORMAL, transaction);
-        }
-
-        private static void saveAccountActivityCache(string accountName, bool success, string message, string reason)
-        {
-            RedisHelper.IncrBy($":activity_total:{accountName}:{DateTime.Now:yyyyMM}");
-            RedisHelper.IncrBy($":activity_total:{accountName}:{DateTime.Now:yyyyMMdd}");
-            RedisHelper.IncrBy($":activity_total:{accountName}:{DateTime.Now:yyyyMMddHH}");
-
-            string result = success ? "success" : "fail";
-            RedisHelper.IncrBy($":activity_total:{accountName}:{result}:{DateTime.Now:yyyyMM}");
-            RedisHelper.IncrBy($":activity_total:{accountName}:{result}:{DateTime.Now:yyyyMMdd}");
-            RedisHelper.IncrBy($":activity_total:{accountName}:{result}:{DateTime.Now:yyyyMMddHH}");
-
-            if (!string.IsNullOrEmpty(message))
-            {
-                RedisHelper.SAdd($":activity_total:{accountName}:message:{DateTime.Now:yyyyMM}", message);
-                RedisHelper.SAdd($":activity_total:{accountName}:message:{DateTime.Now:yyyyMMdd}", message);
-                RedisHelper.SAdd($":activity_total:{accountName}:message:{DateTime.Now:yyyyMMddHH}", message);
-
-                RedisHelper.IncrBy($":activity_total:{accountName}:{message}:{DateTime.Now:yyyyMM}");
-                RedisHelper.IncrBy($":activity_total:{accountName}:{message}:{DateTime.Now:yyyyMMdd}");
-                RedisHelper.IncrBy($":activity_total:{accountName}:{message}:{DateTime.Now:yyyyMMddHH}");
-                RedisHelper.IncrBy($":activity_total:{accountName}:message:{message}:{DateTime.Now:yyyyMM}");
-                RedisHelper.IncrBy($":activity_total:{accountName}:message:{message}:{DateTime.Now:yyyyMMdd}");
-                RedisHelper.IncrBy($":activity_total:{accountName}:message:{message}:{DateTime.Now:yyyyMMddHH}");
-            }
-
-            if (!string.IsNullOrEmpty(reason))
-            {
-                RedisHelper.SAdd($":activity_total:{accountName}:reason:{DateTime.Now:yyyyMM}", reason);
-                RedisHelper.SAdd($":activity_total:{accountName}:reason:{DateTime.Now:yyyyMMdd}", reason);
-                RedisHelper.SAdd($":activity_total:{accountName}:reason:{DateTime.Now:yyyyMMddHH}", reason);
-
-                RedisHelper.IncrBy($":activity_total:{accountName}:{reason}:{DateTime.Now:yyyyMM}");
-                RedisHelper.IncrBy($":activity_total:{accountName}:{reason}:{DateTime.Now:yyyyMMdd}");
-                RedisHelper.IncrBy($":activity_total:{accountName}:{reason}:{DateTime.Now:yyyyMMddHH}");
-                RedisHelper.IncrBy($":activity_total:{accountName}:reason:{reason}:{DateTime.Now:yyyyMM}");
-                RedisHelper.IncrBy($":activity_total:{accountName}:reason:{reason}:{DateTime.Now:yyyyMMdd}");
-                RedisHelper.IncrBy($":activity_total:{accountName}:reason:{reason}:{DateTime.Now:yyyyMMddHH}");
-            }
-        }
-
-
-    }
-
-}
+using dodohold.core;
+using CSRedis;
+using System.Data;
+
+namespace molilian.core
+{
+    public partial class TkLogCore
+    {
+
+        static string queue_activity_key = "queue:activity_logs";
+
+        public static async Task<int> InsertActivityLogAsync(int limit, YunhuiKit.RedisClient redis)
+        {
+            int total = 0;
+            using var connection = DBContext.GetOpenConnection();
+            connection.Open();
+            using var transaction = connection.BeginTransaction();
+            try
+            {
+                for (int i = 0; i < limit; i++)
+                {
+                    var data = await redis.LPopAsync<TkActivityDTO>(queue_activity_key);
+                    if (data == null) break;
+
+                    if (TestParseCore.InWhitelist(data.ip, data.oaid))
+                    {
+                        // For test environment, could create a test version if needed
+                        string daily_table = $"tk_activity_logs_test";
+                        save_activity_logs(data, daily_table, connection, transaction);
+                    }
+                    else
+                    {
+                        string daily_table = $"tk_activity_logs_{data.create_time:yyyyMMdd}";
+                        save_activity_logs(data, daily_table, connection, transaction);
+                    }
+                    total++;
+                }
+                transaction.Commit();
+            }
+            catch (Exception ex)
+            {
+                transaction.Rollback();
+                new LoggerLibrary("TkLogCore_error", "task_insert_activity_logs")
+                      .Info(ex.Message, ex.StackTrace)
+                      .SaveAsync();
+                throw;
+            }
+            finally
+            {
+                connection.Close();
+            }
+            return total;
+        }
+
+        public static async Task TkActivityLogAsync(TkActivityDTO response)
+        {
+            try
+            {
+                if (response.create_time == null)
+                {
+                    response.create_time = DateTime.Now;
+                }
+
+                var ts = DateTime.Now - response.create_time.Value;
+                response.elapsedTime = (int)ts.TotalMilliseconds;
+
+                _ = RedisHelper.RPushAsync(queue_activity_key, response);
+
+                if (!TestParseCore.InWhitelist(response.ip, response.oaid))
+                {
+                    saveActivityCache(response.page_id, response.accountId, response.accountName, response.success, response.message, response.reason);
+                }
+
+                if (response.success) await saveClientRequestTotalAsync(response.channel, response.ip, response.oaid);
+
+                var account = await TkPoolCore.ALLGetOneAsync(response.accountId);
+                bool is_hide = account == null ? false : account.is_hide;
+
+                if (!response.success && "nologin".Equals(response.message))
+                {
+                    switch (response.channel)
+                    {
+                        case TkChannelEnum.tb:
+                            TkPoolCore.Disabled(response.accountId, response.accountName, $"{response.reason}", is_hide);
+                            break;
+                    }
+                }
+            }
+            catch (Exception ex)
+            {
+                _ = new LoggerLibrary("tkActivity", "database_error")
+                .Info(response.Convert2Json())
+                .Info(ex.Message, ex.StackTrace)
+                .SaveAsync();
+            }
+        }
+
+        private static void saveActivityCache(string channel, int accountId, string accountName, bool success, string message, string reason)
+        {
+            saveAccountActivityCache("all", success, message, reason);
+            saveAccountActivityCache($"{channel}", success, message, reason);
+            if (accountId != 0)
+            {
+                saveAccountActivityCache($"{channel}_{accountId}", success, message, reason);
+            }
+        }
+
+        private static int save_activity_logs(TkActivityDTO data, string tablename, IDbConnection connection, IDbTransaction transaction)
+        {
+            return new DBContext.Table(connection, tablename)
+                .Add("end_point", data.end_point)
+                .Add("channel", data.channel)
+                .Add("accountId", data.accountId)
+                .Add("accountName", data.accountName)
+                .Add("proxy_node", data.proxy_node)
+                .Add("page_id", data.page_id)
+                .Add("item_id", data.item_id)
+                .Add("success", data.success)
+                .Add("reason", data.reason)
+                .Add("message", data.message)
+                .Add("deeplink_url", data.deeplink_url)
+                .Add("create_time", data.create_time)
+                .Add("elapsedTime", data.elapsedTime)
+                .Add("elapsedTime2", data.elapsedTime2)
+                .Add("ip", data.ip)
+                .Add("oaid", data.oaid)
+                .Add("estimatedCommission", data.estimatedCommission)
+                .Add("maxCommission", data.maxCommission)
+                .Add("zkFinalPrice", data.zkFinalPrice)
+                .Add("reservePrice", data.reservePrice)
+                .Add("title", data.title)
+                .Add("picUrl", data.picUrl)
+                .Add("shortTitle", data.shortTitle)
+                .Add("priceAfterCoupon", data.priceAfterCoupon)
+                .Add("commissionRate", data.commissionRate)
+                .Add("couponAmount", data.couponAmount)
+                .Create(DBContext.InsertType.NORMAL, transaction);
+        }
+
+        private static void saveAccountActivityCache(string accountName, bool success, string message, string reason)
+        {
+            RedisHelper.IncrBy($":activity_total:{accountName}:{DateTime.Now:yyyyMM}");
+            RedisHelper.IncrBy($":activity_total:{accountName}:{DateTime.Now:yyyyMMdd}");
+            RedisHelper.IncrBy($":activity_total:{accountName}:{DateTime.Now:yyyyMMddHH}");
+
+            string result = success ? "success" : "fail";
+            RedisHelper.IncrBy($":activity_total:{accountName}:{result}:{DateTime.Now:yyyyMM}");
+            RedisHelper.IncrBy($":activity_total:{accountName}:{result}:{DateTime.Now:yyyyMMdd}");
+            RedisHelper.IncrBy($":activity_total:{accountName}:{result}:{DateTime.Now:yyyyMMddHH}");
+
+            if (!string.IsNullOrEmpty(message))
+            {
+                RedisHelper.SAdd($":activity_total:{accountName}:message:{DateTime.Now:yyyyMM}", message);
+                RedisHelper.SAdd($":activity_total:{accountName}:message:{DateTime.Now:yyyyMMdd}", message);
+                RedisHelper.SAdd($":activity_total:{accountName}:message:{DateTime.Now:yyyyMMddHH}", message);
+
+                RedisHelper.IncrBy($":activity_total:{accountName}:{message}:{DateTime.Now:yyyyMM}");
+                RedisHelper.IncrBy($":activity_total:{accountName}:{message}:{DateTime.Now:yyyyMMdd}");
+                RedisHelper.IncrBy($":activity_total:{accountName}:{message}:{DateTime.Now:yyyyMMddHH}");
+                RedisHelper.IncrBy($":activity_total:{accountName}:message:{message}:{DateTime.Now:yyyyMM}");
+                RedisHelper.IncrBy($":activity_total:{accountName}:message:{message}:{DateTime.Now:yyyyMMdd}");
+                RedisHelper.IncrBy($":activity_total:{accountName}:message:{message}:{DateTime.Now:yyyyMMddHH}");
+            }
+
+            if (!string.IsNullOrEmpty(reason))
+            {
+                RedisHelper.SAdd($":activity_total:{accountName}:reason:{DateTime.Now:yyyyMM}", reason);
+                RedisHelper.SAdd($":activity_total:{accountName}:reason:{DateTime.Now:yyyyMMdd}", reason);
+                RedisHelper.SAdd($":activity_total:{accountName}:reason:{DateTime.Now:yyyyMMddHH}", reason);
+
+                RedisHelper.IncrBy($":activity_total:{accountName}:{reason}:{DateTime.Now:yyyyMM}");
+                RedisHelper.IncrBy($":activity_total:{accountName}:{reason}:{DateTime.Now:yyyyMMdd}");
+                RedisHelper.IncrBy($":activity_total:{accountName}:{reason}:{DateTime.Now:yyyyMMddHH}");
+                RedisHelper.IncrBy($":activity_total:{accountName}:reason:{reason}:{DateTime.Now:yyyyMM}");
+                RedisHelper.IncrBy($":activity_total:{accountName}:reason:{reason}:{DateTime.Now:yyyyMMdd}");
+                RedisHelper.IncrBy($":activity_total:{accountName}:reason:{reason}:{DateTime.Now:yyyyMMddHH}");
+            }
+        }
+
+
+    }
+
+}

+ 105 - 105
molilian.core/Core/log/tool.cs

@@ -1,105 +1,105 @@
-using dodohold.core;
-using CSRedis;
-using System.Data;
-using YunhuiKit;
-
-namespace molilian.core
-{
-    public partial class TkLogCore
-    {
-        static string queue_parse_tool_key = "queue:parse_logs:tool";
-
-        public static async Task<int> InsertToolLogAsync(int limit, YunhuiKit.RedisClient redis)
-        {
-            int total = 0;
-
-            using var connection = DBContext.GetOpenConnection();
-            connection.Open();
-            using var transaction = connection.BeginTransaction();
-            try
-            {
-                for (int i = 0; i < limit; i++)
-                {
-                    var data = await redis.LPopAsync<ToolParseDataDTO>(queue_parse_tool_key);
-                    if (data == null) break;
-
-
-                    if (TestParseCore.InWhitelist(data.ip, data.oaid))
-                    {
-                        new DBContext.Table(connection, "tool_parse_logs_test")
-                            .Add("end_point", data.end_point)
-                            .Add("channel", (int)data.channel)
-                            .Add("rawContent", data.rawContent)
-                            .Add("success", data.success)
-                            .Add("message", data.message)
-                            .Add("reason", data.reason)
-                            .Add("content", data.content)
-                            .Add("taoToken", data.taoToken)
-                            .Add("shortLinkurl", data.shortLinkurl)
-                            .Add("deeplink_url", data.deeplink_url)
-                            .Add("elapsedTime", data.elapsedTime)
-                            .Add("ip", data.ip)
-                            .Add("oaid", data.oaid)
-                            .Add("create_time", data.create_time)
-                            .Create(DBContext.InsertType.NORMAL, transaction);
-                    }
-                    else
-                    {
-                        string daily_table = $"tool_parse_logs_{data.create_time:yyyyMMdd}";
-                        new DBContext.Table(connection, daily_table)
-                            .Add("end_point", data.end_point)
-                            .Add("channel", (int)data.channel)
-                            .Add("rawContent", data.rawContent)
-                            .Add("success", data.success)
-                            .Add("message", data.message)
-                            .Add("reason", data.reason)
-                            .Add("content", data.content)
-                            .Add("taoToken", data.taoToken)
-                            .Add("shortLinkurl", data.shortLinkurl)
-                            .Add("deeplink_url", data.deeplink_url)
-                            .Add("elapsedTime", data.elapsedTime)
-                            .Add("ip", data.ip)
-                            .Add("oaid", data.oaid)
-                            .Add("create_time", data.create_time)
-                            .Create(DBContext.InsertType.NORMAL, transaction);
-                    }
-                    total++;
-                }
-                transaction.Commit();
-            }
-            catch (Exception ex)
-            {
-                transaction.Rollback();
-                new LoggerLibrary("TkLogCore_error", "task_insert_parse_tool_logs")
-                      .Info(ex.Message, ex.StackTrace)
-                      .SaveAsync();
-                throw;
-            }
-            finally
-            {
-                connection.Close();
-            }
-            return total;
-        }
-
-        public static async Task ParseLogAsync(ToolParseDataDTO response)
-        {
-            try
-            {
-                var ts = DateTime.Now - response.create_time;
-                response.elapsedTime = (int)ts.TotalMilliseconds;
-
-                _ = RedisKit.RPushAsync(queue_parse_tool_key, response);
-
-                if (!TestParseCore.InWhitelist(response.ip, response.oaid))
-                {
-                    await SaveParseCacheAsync(response.channel.ToString(), 0, "tool",
-                        response.success, response.message, response.reason,
-                        response.deeplink_url);
-                }
-            }
-            catch (Exception ex) { }
-        }
-    }
-
-}
+using dodohold.core;
+using CSRedis;
+using System.Data;
+using YunhuiKit;
+
+namespace molilian.core
+{
+    public partial class TkLogCore
+    {
+        static string queue_parse_tool_key = "queue:parse_logs:tool";
+
+        public static async Task<int> InsertToolLogAsync(int limit, YunhuiKit.RedisClient redis)
+        {
+            int total = 0;
+
+            using var connection = DBContext.GetOpenConnection();
+            connection.Open();
+            using var transaction = connection.BeginTransaction();
+            try
+            {
+                for (int i = 0; i < limit; i++)
+                {
+                    var data = await redis.LPopAsync<ToolParseDataDTO>(queue_parse_tool_key);
+                    if (data == null) break;
+
+
+                    if (TestParseCore.InWhitelist(data.ip, data.oaid))
+                    {
+                        new DBContext.Table(connection, "tool_parse_logs_test")
+                            .Add("end_point", data.end_point)
+                            .Add("channel", (int)data.channel)
+                            .Add("rawContent", data.rawContent)
+                            .Add("success", data.success)
+                            .Add("message", data.message)
+                            .Add("reason", data.reason)
+                            .Add("content", data.content)
+                            .Add("taoToken", data.taoToken)
+                            .Add("shortLinkurl", data.shortLinkurl)
+                            .Add("deeplink_url", data.deeplink_url)
+                            .Add("elapsedTime", data.elapsedTime)
+                            .Add("ip", data.ip)
+                            .Add("oaid", data.oaid)
+                            .Add("create_time", data.create_time)
+                            .Create(DBContext.InsertType.NORMAL, transaction);
+                    }
+                    else
+                    {
+                        string daily_table = $"tool_parse_logs_{data.create_time:yyyyMMdd}";
+                        new DBContext.Table(connection, daily_table)
+                            .Add("end_point", data.end_point)
+                            .Add("channel", (int)data.channel)
+                            .Add("rawContent", data.rawContent)
+                            .Add("success", data.success)
+                            .Add("message", data.message)
+                            .Add("reason", data.reason)
+                            .Add("content", data.content)
+                            .Add("taoToken", data.taoToken)
+                            .Add("shortLinkurl", data.shortLinkurl)
+                            .Add("deeplink_url", data.deeplink_url)
+                            .Add("elapsedTime", data.elapsedTime)
+                            .Add("ip", data.ip)
+                            .Add("oaid", data.oaid)
+                            .Add("create_time", data.create_time)
+                            .Create(DBContext.InsertType.NORMAL, transaction);
+                    }
+                    total++;
+                }
+                transaction.Commit();
+            }
+            catch (Exception ex)
+            {
+                transaction.Rollback();
+                new LoggerLibrary("TkLogCore_error", "task_insert_parse_tool_logs")
+                      .Info(ex.Message, ex.StackTrace)
+                      .SaveAsync();
+                throw;
+            }
+            finally
+            {
+                connection.Close();
+            }
+            return total;
+        }
+
+        public static async Task ParseLogAsync(ToolParseDataDTO response)
+        {
+            try
+            {
+                var ts = DateTime.Now - response.create_time;
+                response.elapsedTime = (int)ts.TotalMilliseconds;
+
+                _ = RedisKit.RPushAsync(queue_parse_tool_key, response);
+
+                if (!TestParseCore.InWhitelist(response.ip, response.oaid))
+                {
+                    await SaveParseCacheAsync(response.channel.ToString(), 0, "tool",
+                        response.success, response.message, response.reason,
+                        response.deeplink_url);
+                }
+            }
+            catch (Exception ex) { }
+        }
+    }
+
+}

+ 188 - 188
molilian.core/Core/log/第三方旧接口.cs

@@ -1,188 +1,188 @@
-
-using CSRedis;
-using dodohold.core;
-using System.Data;
-
-namespace molilian.core
-{
-    public partial class TkLogCore
-    {
-        static string queue_tb_key = "queue:logs:tb";
-        static string queue_jd_key = "queue:logs:jd";
-
-        public static int task_insert_tk_logs(int limit, CSRedisClient redis)
-        {
-            int total = 0;
-            using var connection = DBContext.GetOpenConnection();
-            connection.Open();
-            using var transaction = connection.BeginTransaction();
-            try
-            {
-                for (int i = 0; i < limit; i++)
-                {
-                    var data = redis.LPop<TkDataDTO>(queue_tb_key);
-                    if (data == null) break;
-
-                    if (TestParseCore.InWhitelist(data.ip, data.oaid))
-                    {
-                        save_tk_log(data, "tk_logs_test", connection, transaction);
-                    }
-                    else
-                    {
-                        data.id = save_tk_log(data, "tk_logs", connection, transaction);
-                        if (data.elapsedTime > 1000)
-                        {
-                            save_tk_log(data, "tk_logs_test", connection, transaction);
-                        }
-                        if (data.success)
-                        {
-                            save_tk_log(data, "tk_success_logs", connection, transaction);
-                        }
-                        if (!string.IsNullOrEmpty(data.itemId)) TkOrderTrackingCore.SaveLinkSummary(data);
-                    }
-                    total++;
-                }
-
-                for (int i = 0; i < limit; i++)
-                {
-                    var data = redis.LPop<JdDataDTO>(queue_jd_key);
-                    if (data == null) break;
-                    new DBContext.Table(connection, "tk_logs")
-                        .Add("end_point", data.end_point)
-                        .Add("channel", (int)data.channel)
-                        .Add("accountId", data.accountId)
-                        .Add("accountName", data.accountName)
-                        .Add("rawContent", data.rawContent)
-                        .Add("rawContent2", data.rawContent2)
-                        .Add("success", data.success)
-                        .Add("message", data.message)
-                        .Add("reason", data.reason)
-                        .Add("shortLinkurl", data.shortLinkurl)
-                        .Add("deeplink_url", data.deeplink_url)
-                        .Add("elapsedTime", data.elapsedTime)
-                        .Add("ip", data.ip)
-                        .Add("oaid", data.oaid)
-                        .Add("create_time", data.create_time)
-                        .Create(DBContext.InsertType.NORMAL, transaction);
-                    total++;
-                }
-                transaction.Commit();
-            }
-            catch (Exception ex)
-            {
-                transaction.Rollback();
-                new LoggerLibrary("TkLogCore_error", "task_insert_tk_logs")
-                      .Info(ex.Message, ex.StackTrace)
-                      .SaveAsync();
-                throw;
-            }
-            finally
-            {
-                connection.Close();
-            }
-            return total;
-        }
-        public static async Task LogAsync(JdDataDTO response)
-        {
-            try
-            {
-                var ts = DateTime.Now - response.create_time;
-                response.elapsedTime = (int)ts.TotalMilliseconds;
-                _ = RedisHelper.RPushAsync(queue_jd_key, response);
-
-                if (!response.ip.StartsWith("127.0.0"))
-
-                {
-                    saveCache(response.channel.ToString(), response.accountId, response.accountName, response.success, response.message, response.reason);
-                }
-            }
-            catch (Exception ex) { }
-        }
-        public static async Task LogAsync(TkDataDTO response, AlimamaPlus? alimamaPlus = null)
-        {
-            try
-            {
-                var ts = DateTime.Now - response.create_time;
-                response.elapsedTime = (int)ts.TotalMilliseconds;
-                _ = RedisHelper.RPushAsync(queue_tb_key, response);
-
-                if (!response.ip.StartsWith("127.0.0"))
-                {
-                    saveCache(response.channel.ToString(), response.accountId, response.accountName, response.success, response.message, response.reason);
-                }
-
-
-                var account = await TkPoolCore.ALLGetOneAsync(response.accountId);
-                bool is_hide = account.is_hide;
-                if (!response.success && "nologin".Equals(response.message))
-                {
-                    switch (response.channel)
-                    {
-                        case TkChannelEnum.tb:
-
-                            await Task.Run(async () =>
-                            {
-                                if (alimamaPlus != null)
-                                {
-                                    (bool success, string message) = alimamaPlus.RenewCookie();
-                                    if (success) return;
-                                }
-                                TkPoolCore.Disabled(response.accountId, response.accountName, $"{response.rawContent}\n{response.rawContent2}", is_hide);
-                            });
-                            break;
-                    }
-                }
-
-                if ("接口风控ALL".Equals(response.reason))
-                {
-                    TkPoolCore.Disabled(response.accountId, response.accountName, $"{response.rawContent}\n{response.rawContent2}", is_hide);
-                }
-
-                if ("没有匹配账号".Equals(response.reason))
-                {
-                    TkPoolCore.AccountExhausted();
-                }
-            }
-            catch (Exception ex)
-            {
-                _ = new LoggerLibrary("unionParse", "database_error")
-                .Info(response.rawContent, response.rawContent2)
-                .Info(response.Convert2Json())
-                .Info(ex.Message, ex.StackTrace)
-                .SaveAsync();
-            }
-        }
-
-        private static int save_tk_log(TkDataDTO data, string tablename, IDbConnection connection, IDbTransaction transaction)
-        {
-            return new DBContext.Table(connection, tablename)
-                  .Add("end_point", data.end_point)
-                  .Add("channel", (int)data.channel)
-                  .Add("accountId", data.accountId)
-                  .Add("accountName", data.accountName)
-                  .Add("rawContent", data.rawContent)
-                  .Add("rawContent2", data.rawContent2)
-                  .Add("success", data.success)
-                  .Add("message", data.message)
-                  .Add("reason", data.reason)
-                  .Add("content", data.content)
-                  .Add("couponAmount", data.couponAmount)
-                  .Add("itemId", data.itemId)
-                  .Add("itemName", data.itemName)
-                  .Add("pic", data.pic)
-                  .Add("promotionPrice", data.promotionPrice)
-                  .Add("taoToken", data.taoToken)
-                  .Add("shortLinkurl", data.shortLinkurl)
-                  .Add("deeplink_url", data.deeplink_url)
-                  .Add("num_iid", data.num_iid)
-                  .Add("elapsedTime", data.elapsedTime)
-                  .Add("subCode", data.subCode)
-                  .Add("ip", data.ip)
-                  .Add("oaid", data.oaid)
-                  .Add("create_time", data.create_time)
-                  .Create(DBContext.InsertType.NORMAL, transaction);
-        }
-
-    }
-
-}
+
+using CSRedis;
+using dodohold.core;
+using System.Data;
+
+namespace molilian.core
+{
+    public partial class TkLogCore
+    {
+        static string queue_tb_key = "queue:logs:tb";
+        static string queue_jd_key = "queue:logs:jd";
+
+        public static int task_insert_tk_logs(int limit, CSRedisClient redis)
+        {
+            int total = 0;
+            using var connection = DBContext.GetOpenConnection();
+            connection.Open();
+            using var transaction = connection.BeginTransaction();
+            try
+            {
+                for (int i = 0; i < limit; i++)
+                {
+                    var data = redis.LPop<TkDataDTO>(queue_tb_key);
+                    if (data == null) break;
+
+                    if (TestParseCore.InWhitelist(data.ip, data.oaid))
+                    {
+                        save_tk_log(data, "tk_logs_test", connection, transaction);
+                    }
+                    else
+                    {
+                        data.id = save_tk_log(data, "tk_logs", connection, transaction);
+                        if (data.elapsedTime > 1000)
+                        {
+                            save_tk_log(data, "tk_logs_test", connection, transaction);
+                        }
+                        if (data.success)
+                        {
+                            save_tk_log(data, "tk_success_logs", connection, transaction);
+                        }
+                        if (!string.IsNullOrEmpty(data.itemId)) TkOrderTrackingCore.SaveLinkSummary(data);
+                    }
+                    total++;
+                }
+
+                for (int i = 0; i < limit; i++)
+                {
+                    var data = redis.LPop<JdDataDTO>(queue_jd_key);
+                    if (data == null) break;
+                    new DBContext.Table(connection, "tk_logs")
+                        .Add("end_point", data.end_point)
+                        .Add("channel", (int)data.channel)
+                        .Add("accountId", data.accountId)
+                        .Add("accountName", data.accountName)
+                        .Add("rawContent", data.rawContent)
+                        .Add("rawContent2", data.rawContent2)
+                        .Add("success", data.success)
+                        .Add("message", data.message)
+                        .Add("reason", data.reason)
+                        .Add("shortLinkurl", data.shortLinkurl)
+                        .Add("deeplink_url", data.deeplink_url)
+                        .Add("elapsedTime", data.elapsedTime)
+                        .Add("ip", data.ip)
+                        .Add("oaid", data.oaid)
+                        .Add("create_time", data.create_time)
+                        .Create(DBContext.InsertType.NORMAL, transaction);
+                    total++;
+                }
+                transaction.Commit();
+            }
+            catch (Exception ex)
+            {
+                transaction.Rollback();
+                new LoggerLibrary("TkLogCore_error", "task_insert_tk_logs")
+                      .Info(ex.Message, ex.StackTrace)
+                      .SaveAsync();
+                throw;
+            }
+            finally
+            {
+                connection.Close();
+            }
+            return total;
+        }
+        public static async Task LogAsync(JdDataDTO response)
+        {
+            try
+            {
+                var ts = DateTime.Now - response.create_time;
+                response.elapsedTime = (int)ts.TotalMilliseconds;
+                _ = RedisHelper.RPushAsync(queue_jd_key, response);
+
+                if (!response.ip.StartsWith("127.0.0"))
+
+                {
+                    saveCache(response.channel.ToString(), response.accountId, response.accountName, response.success, response.message, response.reason);
+                }
+            }
+            catch (Exception ex) { }
+        }
+        public static async Task LogAsync(TkDataDTO response, AlimamaPlus? alimamaPlus = null)
+        {
+            try
+            {
+                var ts = DateTime.Now - response.create_time;
+                response.elapsedTime = (int)ts.TotalMilliseconds;
+                _ = RedisHelper.RPushAsync(queue_tb_key, response);
+
+                if (!response.ip.StartsWith("127.0.0"))
+                {
+                    saveCache(response.channel.ToString(), response.accountId, response.accountName, response.success, response.message, response.reason);
+                }
+
+
+                var account = await TkPoolCore.ALLGetOneAsync(response.accountId);
+                bool is_hide = account.is_hide;
+                if (!response.success && "nologin".Equals(response.message))
+                {
+                    switch (response.channel)
+                    {
+                        case TkChannelEnum.tb:
+
+                            await Task.Run(async () =>
+                            {
+                                if (alimamaPlus != null)
+                                {
+                                    (bool success, string message) = alimamaPlus.RenewCookie();
+                                    if (success) return;
+                                }
+                                TkPoolCore.Disabled(response.accountId, response.accountName, $"{response.rawContent}\n{response.rawContent2}", is_hide);
+                            });
+                            break;
+                    }
+                }
+
+                if ("接口风控ALL".Equals(response.reason))
+                {
+                    TkPoolCore.Disabled(response.accountId, response.accountName, $"{response.rawContent}\n{response.rawContent2}", is_hide);
+                }
+
+                if ("没有匹配账号".Equals(response.reason))
+                {
+                    TkPoolCore.AccountExhausted();
+                }
+            }
+            catch (Exception ex)
+            {
+                _ = new LoggerLibrary("unionParse", "database_error")
+                .Info(response.rawContent, response.rawContent2)
+                .Info(response.Convert2Json())
+                .Info(ex.Message, ex.StackTrace)
+                .SaveAsync();
+            }
+        }
+
+        private static int save_tk_log(TkDataDTO data, string tablename, IDbConnection connection, IDbTransaction transaction)
+        {
+            return new DBContext.Table(connection, tablename)
+                  .Add("end_point", data.end_point)
+                  .Add("channel", (int)data.channel)
+                  .Add("accountId", data.accountId)
+                  .Add("accountName", data.accountName)
+                  .Add("rawContent", data.rawContent)
+                  .Add("rawContent2", data.rawContent2)
+                  .Add("success", data.success)
+                  .Add("message", data.message)
+                  .Add("reason", data.reason)
+                  .Add("content", data.content)
+                  .Add("couponAmount", data.couponAmount)
+                  .Add("itemId", data.itemId)
+                  .Add("itemName", data.itemName)
+                  .Add("pic", data.pic)
+                  .Add("promotionPrice", data.promotionPrice)
+                  .Add("taoToken", data.taoToken)
+                  .Add("shortLinkurl", data.shortLinkurl)
+                  .Add("deeplink_url", data.deeplink_url)
+                  .Add("num_iid", data.num_iid)
+                  .Add("elapsedTime", data.elapsedTime)
+                  .Add("subCode", data.subCode)
+                  .Add("ip", data.ip)
+                  .Add("oaid", data.oaid)
+                  .Add("create_time", data.create_time)
+                  .Create(DBContext.InsertType.NORMAL, transaction);
+        }
+
+    }
+
+}

+ 933 - 933
molilian.core/Core/pdd/PddPoolCore.cs

@@ -1,933 +1,933 @@
-using Microsoft.AspNetCore.Http;
-using Microsoft.AspNetCore.Mvc.Controllers;
-using Microsoft.AspNetCore.Mvc.Filters;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using dodohold.core;
-using Dataoke;
-using Google.Protobuf.WellKnownTypes;
-using System.Diagnostics;
-using System.Text.Json;
-using TencentCloud.Tcm.V20210413.Models;
-using System.Security.Cryptography;
-using System.Collections.Concurrent;
-using YunhuiKit;
-using System.Xml.Linq;
-
-
-namespace molilian.core
-{
-    public partial class PddPoolCore
-    {
-        private static readonly object _lockObj = new();
-        private static IEnumerable<PddPoolDTO> _cached;
-        private static IEnumerable<PddPoolDTO> _all_cached;
-
-        private static string _end_point;
-        private static Dictionary<string, decimal> _incomeAmt = new();
-        private static ConcurrentDictionary<int, DateTime> _suspend = new();
-
-        // 移除本地内存统计,改用 Redis 存储
-
-        private static SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
-
-        static PddPoolCore()
-        {
-            _end_point = Environment.GetEnvironmentVariable("EndPoint");
-        }
-
-        /// <summary>
-        /// 获取一个账号用于处理请求
-        /// 使用平滑加权轮询算法(Smooth Weighted Round-Robin)实现流量均衡分配
-        /// </summary>
-        public static PddPoolDTO? GetOne(PddUnionWorkMode mode, int accountid = 0, string parse_type = "")
-        {
-            var list = List();
-            if (!list.Any()) return null;
-
-            // 按 parse_type 过滤
-            if ("dp".Equals(parse_type))
-            {
-                list = list.Where(item => "dp".Equals(item.parse_type)).ToList();
-                if (!list.Any()) return null;
-            }
-            else
-            {
-                list = list.Where(item => string.IsNullOrEmpty(item.parse_type)).ToList();
-                if (!list.Any()) return null;
-            }
-
-            // 过滤出符合条件的候选账号
-            var candidates = list.Where(e => IsMatch(e, mode, accountid)).ToList();
-            if (!candidates.Any()) return null;
-
-            // 使用平滑加权轮询算法选择账号
-            return SmoothWeightedRoundRobin(candidates, parse_type);
-        }
-
-        /// <summary>
-        /// 基于最后使用时间的调度策略(并发安全版本)
-        /// 选择"距离上次使用时间最长"且"超过cis_limit间隔"的账号
-        ///
-        /// 优势:
-        /// 1. 不区分新老账号,一视同仁(无历史记录的账号返回DateTime.MinValue,自然会被优先选中)
-        /// 2. 自然满足cis_limit要求(距离时间 < limit 会被跳过)
-        /// 3. 优先选择"休息最久"的账号,最大化每个账号的休息时间
-        /// 4. 简单直观,易于调试和理解
-        /// 5. 并发安全:使用Redis原子操作预留账号,避免多个请求竞争同一账号
-        /// </summary>
-        private static PddPoolDTO? SmoothWeightedRoundRobin(List<PddPoolDTO> candidates, string parse_type)
-        {
-            if (!candidates.Any()) return null;
-            if (candidates.Count == 1)
-            {
-                var single = candidates[0];
-                // 尝试预留账号(并发安全)
-                if (TryReserveAccount(single.id, parse_type, single.cis_limit))
-                {
-                    return single;
-                }
-                return null; // 预留失败,说明账号已被其他请求占用
-            }
-
-            try
-            {
-                string groupKey = string.IsNullOrEmpty(parse_type) ? "default" : parse_type;
-                var now = DateTime.Now;
-
-                // 候选账号列表:记录每个账号的空闲时长
-                var accountsWithIdleTime = new List<(PddPoolDTO account, long idleMs)>();
-
-                foreach (var account in candidates)
-                {
-                    // 获取账号最后使用时间
-                    var lastUsedTime = GetAccountLastUsedTime(account.id, groupKey);
-
-                    // 计算距离现在的时间差(毫秒)
-                    long idleMs = (long)(now - lastUsedTime).TotalMilliseconds;
-
-                    // 如果设置了 cis_limit,必须满足间隔要求
-                    if (account.cis_limit > 0 && idleMs < account.cis_limit)
-                    {
-                        continue; // 跳过未满足间隔要求的账号
-                    }
-
-                    accountsWithIdleTime.Add((account, idleMs));
-                }
-
-                // 如果没有满足条件的账号,返回null
-                if (!accountsWithIdleTime.Any()) return null;
-
-                // 按照空闲时间降序排序,优先尝试"休息最久"的账号
-                var sortedAccounts = accountsWithIdleTime
-                    .OrderByDescending(x => x.idleMs)
-                    .ToList();
-
-                // 依次尝试预留账号(从空闲时间最长的开始)
-                foreach (var (account, idleMs) in sortedAccounts)
-                {
-                    // 尝试原子预留此账号
-                    if (TryReserveAccount(account.id, groupKey, account.cis_limit))
-                    {
-                        // 预留成功,更新最后使用时间
-                        SetAccountLastUsedTime(account.id, groupKey, now);
-                        Console.WriteLine($"[PDD] Selected account {account.id} ({account.name}), idle time: {Math.Round(idleMs / 1000.0, 2)}s");
-                        return account;
-                    }
-                    else
-                    {
-                        // 预留失败,说明此账号已被其他并发请求占用,尝试下一个
-                        Console.WriteLine($"[PDD] Account {account.id} ({account.name}) is reserved by another request, trying next...");
-                    }
-                }
-
-                // 所有满足条件的账号都已被占用
-                Console.WriteLine($"[PDD] All available accounts are reserved, no account available");
-                return null;
-            }
-            catch (Exception ex)
-            {
-                Console.WriteLine($"Idle time selection failed: {ex.Message}, fallback to simple strategy");
-
-                // 降级策略:使用原有的最少使用次数算法
-                return candidates
-                    .OrderBy(account => GetCurrentDayUsageFromRedis(account.id))
-                    .ThenBy(account => Guid.NewGuid())
-                    .FirstOrDefault();
-            }
-        }
-
-        /// <summary>
-        /// 尝试原子预留账号(并发安全)
-        /// 使用 Redis SET NX EX 实现原子操作,避免多个请求竞争同一账号
-        /// </summary>
-        /// <param name="accountId">账号ID</param>
-        /// <param name="groupKey">分组键</param>
-        /// <param name="cisLimit">请求间隔限制(毫秒),0表示无限制</param>
-        /// <returns>true=预留成功,false=账号已被占用</returns>
-        private static bool TryReserveAccount(int accountId, string groupKey, int cisLimit)
-        {
-            try
-            {
-                string reserveKey = $"pdd_account_reserved:{groupKey}:{accountId}";
-
-                // 如果没有设置 cis_limit,使用默认100ms的预留时间(防止极端并发)
-                int expireSeconds = cisLimit > 0 ? (int)Math.Ceiling(cisLimit / 1000.0) : 1;
-
-                // 使用 Redis SET NX EX 原子操作(一次性完成设置和过期时间)
-                // 等价于 Redis 命令: SET key value NX EX seconds
-                // 如果 key 不存在则设置成功(返回true),如果已存在则失败(返回false)
-                // 注意:必须使用原子操作,SetNx + Expire 两步操作会有竞态条件!
-                bool reserved = RedisHelper.Set(reserveKey, 1, expireSeconds, CSRedis.RedisExistence.Nx);
-                return reserved;
-            }
-            catch (Exception ex)
-            {
-                Console.WriteLine($"Failed to reserve account {accountId}: {ex.Message}");
-                // 预留失败时保守处理:假设账号不可用
-                return false;
-            }
-        }
-
-        /// <summary>
-        /// 获取账号最后使用时间
-        /// </summary>
-        private static DateTime GetAccountLastUsedTime(int accountId, string groupKey)
-        {
-            try
-            {
-                string key = $"pdd_last_used_time:{groupKey}:{accountId}";
-                var timestamp = RedisHelper.Get<long>(key);
-
-                if (timestamp > 0)
-                {
-                    return DateTimeOffset.FromUnixTimeMilliseconds(timestamp).LocalDateTime;
-                }
-
-                // 如果没有记录,返回很久以前的时间(确保新账号和长期未使用的账号能被优先选中)
-                return DateTime.MinValue;
-            }
-            catch
-            {
-                return DateTime.MinValue;
-            }
-        }
-
-        /// <summary>
-        /// 设置账号最后使用时间
-        /// </summary>
-        private static void SetAccountLastUsedTime(int accountId, string groupKey, DateTime time)
-        {
-            try
-            {
-                string key = $"pdd_last_used_time:{groupKey}:{accountId}";
-                long timestamp = new DateTimeOffset(time).ToUnixTimeMilliseconds();
-
-                // 设置过期时间为24小时,避免数据积累
-                RedisHelper.Set(key, timestamp, 86400);
-            }
-            catch (Exception ex)
-            {
-                Console.WriteLine($"Failed to set last used time for {accountId}: {ex.Message}");
-            }
-        }
-
-        /// <summary>
-        /// 获取账号的静态权重
-        /// 可以根据账号的 daily_calls_limit 或其他因素动态计算
-        /// </summary>
-        private static int GetAccountStaticWeight(int accountId)
-        {
-            try
-            {
-                string key = $"pdd_account_weight:{accountId}";
-                int weight = RedisHelper.Get<int>(key);
-
-                // 如果没有设置,返回默认权重100
-                if (weight <= 0)
-                {
-                    // 可以基于账号的限额动态计算默认权重
-                    var account = List()?.FirstOrDefault(a => a.id == accountId);
-                    if (account != null && account.daily_calls_limit > 0)
-                    {
-                        // 将日限额映射到权重:每1000次调用对应权重10
-                        weight = Math.Max(10, Math.Min(1000, account.daily_calls_limit / 100));
-                    }
-                    else
-                    {
-                        weight = 100; // 默认权重
-                    }
-                }
-
-                return weight;
-            }
-            catch
-            {
-                return 100; // 异常时返回默认权重
-            }
-        }
-
-        /// <summary>
-        /// 设置账号的静态权重
-        /// </summary>
-        public static void SetAccountStaticWeight(int accountId, int weight)
-        {
-            try
-            {
-                if (weight < 1) weight = 1;
-                if (weight > 1000) weight = 1000;
-
-                string key = $"pdd_account_weight:{accountId}";
-                RedisHelper.Set(key, weight, 30 * 86400); // 30天过期
-            }
-            catch (Exception ex)
-            {
-                Console.WriteLine($"Failed to set account weight for {accountId}: {ex.Message}");
-            }
-        }
-
-        /// <summary>
-        /// 获取账号的当前动态权重
-        /// </summary>
-        private static int GetAccountCurrentWeight(int accountId, string groupKey)
-        {
-            try
-            {
-                string key = $"pdd_current_weight:{groupKey}:{accountId}";
-                return RedisHelper.Get<int>(key);
-            }
-            catch
-            {
-                return 0;
-            }
-        }
-
-        /// <summary>
-        /// 设置账号的当前动态权重
-        /// </summary>
-        private static void SetAccountCurrentWeight(int accountId, string groupKey, int weight)
-        {
-            try
-            {
-                string key = $"pdd_current_weight:{groupKey}:{accountId}";
-                RedisHelper.Set(key, weight, 3600); // 1小时过期,自动重置
-            }
-            catch (Exception ex)
-            {
-                Console.WriteLine($"Failed to set current weight for {accountId}: {ex.Message}");
-            }
-        }
-
-        /// <summary>
-        /// 重置所有账号的动态权重(用于调试或重新初始化)
-        /// </summary>
-        public static void ResetAllWeights(string groupKey = "default")
-        {
-            try
-            {
-                var accounts = List()?.Where(a => a.status && a.enable_parse).ToList();
-                if (accounts == null || !accounts.Any()) return;
-
-                foreach (var account in accounts)
-                {
-                    string key = $"pdd_current_weight:{groupKey}:{account.id}";
-                    RedisHelper.Del(key);
-                }
-
-                Console.WriteLine($"Reset weights for {accounts.Count} accounts in group '{groupKey}'");
-            }
-            catch (Exception ex)
-            {
-                Console.WriteLine($"Failed to reset weights: {ex.Message}");
-            }
-        }
-
-        private static int GetCurrentDayUsageFromRedis(int accountId)
-        {
-            try
-            {
-                var today = DateTime.Now.ToString("yyyyMMdd");
-                string key = $"pdd_daily_usage:{accountId}:{today}";
-                return RedisHelper.Get<int>(key);
-            }
-            catch (Exception)
-            {
-                return 0;
-            }
-        }
-
-        internal static void UpdateAccountUsage(int accountId)
-        {
-            try
-            {
-                var today = DateTime.Now.ToString("yyyyMMdd");
-                string key = $"pdd_daily_usage:{accountId}:{today}";
-
-                // 递增计数并设置24小时过期时间
-                RedisHelper.IncrBy(key);
-                RedisHelper.Expire(key, 86400); // 24小时后自动过期
-            }
-            catch (Exception ex)
-            {
-                // 记录错误但不影响主流程
-                Console.WriteLine($"Failed to update account usage for {accountId}: {ex.Message}");
-            }
-        }
-
-        /// <summary>
-        /// 获取当前所有账号的平均使用次数
-        /// </summary>
-        private static int GetAverageUsageCount()
-        {
-            try
-            {
-                var activeAccounts = List()?.Where(a => a.enable_parse).ToList();
-                if (activeAccounts == null || !activeAccounts.Any()) return 0;
-
-                var today = DateTime.Now.ToString("yyyyMMdd");
-                var totalUsage = 0;
-                var validCount = 0;
-
-                foreach (var account in activeAccounts)
-                {
-                    var usage = GetCurrentDayUsageFromRedis(account.id);
-                    totalUsage += usage;
-                    validCount++;
-                }
-
-                return validCount > 0 ? totalUsage / validCount : 0;
-            }
-            catch (Exception ex)
-            {
-                Console.WriteLine($"Failed to get average usage count: {ex.Message}");
-                return 0;
-            }
-        }
-
-        /// <summary>
-        /// 重置所有在线账号的当日调用次数Redis计数器
-        /// 随机排序账号后,每个账号间隔1递增value
-        /// </summary>
-        public static int RechargeAllOnlineAccountUsage()
-        {
-            try
-            {
-                var onlineAccounts = List()?.Where(a => a.status && a.enable_parse).ToList();
-                if (onlineAccounts == null || !onlineAccounts.Any())
-                {
-                    return 0;
-                }
-
-                var today = DateTime.Now.ToString("yyyyMMdd");
-
-                // 随机排序账号
-                var random = new Random();
-                var shuffledAccounts = onlineAccounts.OrderBy(x => random.Next()).ToList();
-
-                int incrementValue = 1;
-                int rechargedCount = 0;
-
-                foreach (var account in shuffledAccounts)
-                {
-                    string key = $"pdd_daily_usage:{account.id}:{today}";
-
-                    // 设置递增的value并设置24小时过期时间
-                    RedisHelper.Set(key, incrementValue, 86400);
-                    RedisHelper.Set(key, 0, 86400);
-                    incrementValue++;
-                    rechargedCount++;
-                }
-                return rechargedCount;
-            }
-            catch (Exception ex)
-            {
-            }
-            return 0;
-        }
-
-        /// <summary>
-        /// 为新上线的账号设置平均使用次数(仅用于降级策略的统计)
-        /// 注意:基于时间间隔的调度策略不需要初始化权重,因为无历史记录的账号会自动返回DateTime.MinValue
-        /// </summary>
-        public static void InitializeNewAccountUsage(int accountId, string groupKey = "default")
-        {
-            try
-            {
-                var today = DateTime.Now.ToString("yyyyMMdd");
-                string usageKey = $"pdd_daily_usage:{accountId}:{today}";
-
-                // 检查该账号今天是否已有使用记录
-                var currentUsage = RedisHelper.Get<int>(usageKey);
-                if (currentUsage > 0)
-                {
-                    Console.WriteLine($"Account {accountId} already has usage record: {currentUsage}, skip initialization");
-                    return; // 已有记录,不需要初始化
-                }
-
-                // 获取所有在线账号的平均值(仅用于降级策略)
-                var activeAccounts = List()?.Where(a => a.status && a.enable_parse && a.id != accountId).ToList();
-                if (activeAccounts == null || !activeAccounts.Any())
-                {
-                    Console.WriteLine($"No other active accounts found, account {accountId} will use default value");
-                    return; // 没有其他账号,使用默认值0即可
-                }
-
-                // 初始化 daily_usage(用于降级策略的统计)
-                var totalUsage = 0;
-                foreach (var account in activeAccounts)
-                {
-                    totalUsage += GetCurrentDayUsageFromRedis(account.id);
-                }
-                var averageUsage = totalUsage / activeAccounts.Count;
-                if (averageUsage > 0)
-                {
-                    RedisHelper.Set(usageKey, averageUsage, 86400);
-                    Console.WriteLine($"Initialized account {accountId} daily_usage with average: {averageUsage}");
-                }
-
-                Console.WriteLine($"Account {accountId} initialization complete. Time-based scheduling will use DateTime.MinValue for idle time calculation.");
-            }
-            catch (Exception ex)
-            {
-                Console.WriteLine($"Failed to initialize new account for {accountId}: {ex.Message}");
-            }
-        }
-        /// <summary>
-        /// 获取当前统计信息(用于监控和调试)- 改为从Redis获取
-        /// </summary>
-        internal static Dictionary<string, int> GetCurrentUsageSnapshot()
-        {
-            try
-            {
-                var result = new Dictionary<string, int>();
-                var accounts = List()?.Where(a => a.status && a.enable_parse).ToList();
-                if (accounts == null || !accounts.Any()) return result;
-
-                var today = DateTime.Now.ToString("yyyyMMdd");
-                foreach (var account in accounts)
-                {
-                    string key = $"pdd_daily_usage:{account.id}:{today}";
-                    int usage = RedisHelper.Get<int>(key);
-                    result[$"Account_{account.id}_{account.name}"] = usage;
-                }
-
-                return result;
-            }
-            catch (Exception)
-            {
-                return new Dictionary<string, int>();
-            }
-        }
-
-        /// <summary>
-        /// 获取账号调度时间间隔信息(用于监控和调试)
-        /// </summary>
-        public static Dictionary<string, object> GetScheduleWeightSnapshot(string groupKey = "default")
-        {
-            try
-            {
-                var result = new Dictionary<string, object>();
-                var accounts = List()?.Where(a => a.status && a.enable_parse).ToList();
-                if (accounts == null || !accounts.Any()) return result;
-
-                var now = DateTime.Now;
-                var accountInfos = new List<Dictionary<string, object>>();
-
-                foreach (var account in accounts)
-                {
-                    var lastUsedTime = GetAccountLastUsedTime(account.id, groupKey);
-                    long idleMs = (long)(now - lastUsedTime).TotalMilliseconds;
-                    bool canUse = account.cis_limit <= 0 || idleMs >= account.cis_limit;
-
-                    var info = new Dictionary<string, object>
-                    {
-                        ["account_id"] = account.id,
-                        ["account_name"] = account.name,
-                        ["cis_limit_ms"] = account.cis_limit,
-                        ["last_used_time"] = lastUsedTime == DateTime.MinValue ? "从未使用" : lastUsedTime.ToString("yyyy-MM-dd HH:mm:ss.fff"),
-                        ["idle_time_ms"] = idleMs,
-                        ["idle_time_seconds"] = Math.Round(idleMs / 1000.0, 2),
-                        ["can_use"] = canUse,
-                        ["daily_usage"] = GetCurrentDayUsageFromRedis(account.id),
-                        ["daily_limit"] = account.daily_calls_limit
-                    };
-                    accountInfos.Add(info);
-                }
-
-                // 按照空闲时间降序排序(与选择逻辑一致)
-                accountInfos = accountInfos.OrderByDescending(x => (long)x["idle_time_ms"]).ToList();
-
-                result["accounts"] = accountInfos;
-                result["group_key"] = groupKey;
-                result["timestamp"] = now.ToString("yyyy-MM-dd HH:mm:ss.fff");
-                result["total_accounts"] = accountInfos.Count;
-                result["available_accounts"] = accountInfos.Count(x => (bool)x["can_use"]);
-
-                return result;
-            }
-            catch (Exception ex)
-            {
-                return new Dictionary<string, object> { ["error"] = ex.Message };
-            }
-        }
-
-        /// <summary>
-        /// 强制清理统计数据(仅用于测试或紧急情况)- Redis版本
-        /// </summary>
-        internal static void ForceClearStats()
-        {
-            try
-            {
-                var today = DateTime.Now.ToString("yyyyMMdd");
-                var pattern = $"pdd_daily_usage:*:{today}";
-
-                // TODO: 实现Redis批量删除相关keys
-                // 生产环境中需要谨慎使用
-                Console.WriteLine("Force clear stats - Redis keys will auto-expire in 24h");
-            }
-            catch (Exception ex)
-            {
-                Console.WriteLine($"Failed to force clear stats: {ex.Message}");
-            }
-        }
-
-        internal static void TempSuspend(int accountId)
-        {
-            _suspend.AddOrUpdate(accountId, DateTime.Now, (key, oldValue) => DateTime.Now);
-        }
-
-        private static bool IsMatch(PddPoolDTO item, PddUnionWorkMode mode, int accountid)
-        {
-            if (accountid != 0 && accountid != item.id) return false;
-            if (!item.enable_parse) return false;
-            if (mode != PddUnionWorkMode.All && mode != item.work_mode) return false;
-            if (item.work_mode == PddUnionWorkMode.Crawler && !item.cookie_status) return false;
-
-            if (!string.IsNullOrEmpty(_end_point) && !string.IsNullOrEmpty(item.end_point))
-            {
-                if (item.end_point != _end_point) return false;
-            }
-
-            // 使用初始化参数创建工作时间表
-            if (!new WorkSchedule(item.time_range).IsWorkHour()) return false;
-
-            if (_suspend.TryGetValue(accountid, out DateTime suspendTime))
-            {
-                var ts = DateTime.Now - suspendTime;
-                if (ts.TotalSeconds < 70) return false;
-            }
-
-            // cis_limit 检查已移至 SmoothWeightedRoundRobin 方法中基于时间间隔判断
-            // 这里不再需要检查 Redis 锁
-
-            if (item.rpm_limit > 0)
-            {
-                int rpm_num = RiskControlCore.GetCalls(TkChannelEnum.pdd, item.id, DateTime.Now.ToString("yyyyMMddHHmm"));
-                if (rpm_num >= item.rpm_limit) return false;
-            }
-            if (item.daily_calls_limit > 0)
-            {
-                int daily_num = RiskControlCore.GetCalls(TkChannelEnum.pdd, item.id, DateTime.Now.ToString("yyyyMMdd"));
-                if (daily_num >= item.daily_calls_limit) return false;
-            }
-            if (item.hourly_calls_limit > 0)
-            {
-                int hourly_num = RiskControlCore.GetCalls(TkChannelEnum.pdd, item.id, DateTime.Now.ToString("yyyyMMddHH"));
-                if (hourly_num >= item.hourly_calls_limit) return false;
-            }
-            return true;
-        }
-
-
-
-        public static async Task<IEnumerable<PddPoolDTO>> AllListAsync(bool force = false)
-        {
-            if (!force && _all_cached != default) return _all_cached;
-
-            try
-            {
-                await _semaphore.WaitAsync();
-
-                string cache_key = $"cache:all_pdd_pool";
-                var list = await RedisKit.GetAsync<IEnumerable<PddPoolDTO>>(cache_key);
-
-                if (force || list == default)
-                {
-                    list = new DBContext.Table("pdd_pool").Select<PddPoolDTO>();
-                    if (list == null) return default;
-
-                    // 等待 Redis 写入完成
-                    await RedisKit.SetAsync(cache_key, list, 30 * 86400);
-                }
-
-                _all_cached = list;
-                return list;
-            }
-            catch (Exception)
-            {
-                // 发生异常时返回上一次的缓存,如果没有则返回默认值
-                return _all_cached ?? default;
-            }
-            finally
-            {
-                _semaphore.Release();
-            }
-        }
-        public static IEnumerable<PddPoolDTO> List(bool force = false)
-        {
-            if (!force && _cached != null) return _cached;
-
-            string cache_key = $"cache:pdd_pool";
-            var list = RedisHelper.Get<IEnumerable<PddPoolDTO>>(cache_key);
-            if (force || list == null)
-            {
-                lock (_lockObj)
-                {
-                    list = new DBContext.Table("pdd_pool")
-                        .Where("status=@status", new { status = 1 })
-                        .Select<PddPoolDTO>();
-                    if (list == null) return default;
-                    foreach (var item in list)
-                    {
-                        RiskControlCore.SetCallsAsync(TkChannelEnum.pdd, item.id, DateTime.Now.ToString("yyyyMMddHH"), item.current_hourly_calls);
-                        RiskControlCore.SetCallsAsync(TkChannelEnum.pdd, item.id, DateTime.Now.ToString("yyyyMMdd"), item.current_daily_calls);
-                    }
-                    RedisHelper.Set(cache_key, list, 30 * 86400);
-                }
-            }
-            _cached = list;
-            return list;
-        }
-        public static void Refresh()
-        {
-            _cached = null;
-            _all_cached = null;
-            _ = List(true);
-            _ = AllListAsync(true);
-        }
-
-        public static void Disabled(string name)
-        {
-            string cache_key = $"cache:pdd_pool:{name}:disabled";
-            long count = RedisHelper.IncrBy(cache_key);
-            RedisHelper.Expire(cache_key, 10);
-            if (count > 1) return;
-
-            new DBContext.Table("pdd_pool")
-                .Add("status", 0)
-                .Where("name=@name", new { name })
-                .Update();
-            _ = List(true);
-
-            NotifyCore.Notify(new NifyMessage
-            {
-                message = $"【多多:{name}】调用异常",
-                priority = NifyMessagePriority.high,
-                tags = ["red_circle"]
-            });
-        }
-
-
-        public static void Disabled(int accountId, string name, string content)
-        {
-            string cache_key = $"cache:pdd_pool:{name}:disabled";
-            long count = RedisHelper.IncrBy(cache_key);
-            RedisHelper.Expire(cache_key, 10);
-            if (count > 1) return;
-
-            var update = new DBContext.Table("pdd_pool").Add("status", 0);
-            if (accountId > 0)
-            {
-                update.Where("id=@accountId", new { accountId }).Update();
-            }
-            else
-            {
-                update.Where("name=@name", new { name }).Update();
-            }
-
-            _ = List(true);
-
-            NotifyCore.Notify(new NifyMessage
-            {
-                message = $"【多多{accountId}:{name}】cookie 掉线\n\n{content}",
-                priority = NifyMessagePriority.high,
-                tags = ["red_circle"]
-            });
-
-            //NotifyCore.QYWeixinPushNotifyAsync("掉线", $"【多多{accountId}:{name}】cookie 掉线");
-            EndPointCore.NotifyReload(true);
-        }
-
-        internal static void AccountExhausted()
-        {
-            string cache_key = $"cache:pdd_pool:account:exhausted";
-            long count = RedisHelper.IncrBy(cache_key);
-            if (count > 1) return;
-            RedisHelper.Expire(cache_key, 3600);
-            NotifyCore.Notify(new NifyMessage
-            {
-                message = $"【多多】没有匹配账号",
-                priority = NifyMessagePriority.high,
-                tags = ["red_circle"]
-            });
-            //_ = NotifyCore.QYWeixinPushNotifyAsync("没账号", $"【多多】没有匹配账号");
-        }
-
-        internal static async Task<JsonElement> GetUserInfo(string cookies)
-        {
-            JsonElement result = default;
-            try
-            {
-                string url = "https://jinbao.pinduoduo.com/network/api/account/userInfo";
-                WebClientUtility client = new WebClientUtility();
-                client.Post("{}");
-                client.SetCookies(cookies);
-                client.SetContentType("application/json");
-                var response = await client.RequestAsync(url, "POST");
-                var body = response.Body();
-                result = body.Convert2JsonElement();
-            }
-            catch (Exception ex)
-            {
-            }
-            return result;
-        }
-
-        public static async Task<int> UpdateCookies(string cookies, string user_agent, int id = 0)
-        {
-            if (string.IsNullOrEmpty(cookies)) return 0;
-            var userinfo = await GetUserInfo(cookies);
-            if (userinfo.ValueKind != JsonValueKind.Object) return 0;
-
-            int duoId = userinfo.PathRead<int>("result.duoId", 0);
-            string company = userinfo.PathRead<string>("result.mobile", string.Empty);
-            string lastPid = userinfo.PathRead<string>("result.lastPid", string.Empty);
-
-            int accountId = 0;
-            if (duoId == 0 && id == 0) return 0;
-
-
-            string filter = id != 0 ? "id=@id" : "duoId=@duoId";
-            var exist = new DBContext.Table("pdd_pool").Get<PddPoolDTO>(filter, new { id, duoId });
-            if (exist != null)
-            {
-                var status = exist.status;
-                var work_mode = exist.work_mode;
-                accountId = exist.id;
-                if (work_mode == PddUnionWorkMode.Crawler) status = true;
-                new DBContext.Table("pdd_pool")
-                    .Add("cookies", cookies)
-                    .Add("user_agent", user_agent)
-                    .Add("status", status)
-                    .Add("cookie_status", 1)
-                    .Add("last_time", DateTime.Now)
-                    .Add("login_time", DateTime.Now)
-                    .Where("id=@id", new { exist.id })
-                    .Update();
-                if (status)
-                {
-                    _ = List(true);
-                    // 为重新上线的账号初始化使用次数和权重(针对所有分组)
-                    InitializeNewAccountUsage(exist.id, "default");
-                    InitializeNewAccountUsage(exist.id, "dp");
-                }
-            }
-            else
-            {
-                accountId = new DBContext.Table("pdd_pool")
-                    .Add("duoId", duoId)
-                    .Add("name", company)
-                    .Add("company", company)
-                    .Add("description", "由cookies上报创建此记录")
-                    .Add("cookies", cookies)
-                    .Add("user_agent", user_agent)
-                    .Add("pid", lastPid)
-                    .Add("cookie_status", 1)
-                    .Add("create_time", DateTime.Now)
-                    .Add("last_time", DateTime.Now)
-                    .Add("login_time", DateTime.Now)
-                    .Add("status", 0)
-                    .Create();
-
-                // 为新创建的账号初始化使用次数(如果将来会启用的话)
-                // InitializeNewAccountUsage(accountId); // 暂不调用,因为新创建的账号status=0
-            }
-
-            NotifyCore.Notify(new NifyMessage
-            {
-                message = $"【拼多多{accountId}:{company}】cookie 上线",
-                tags = ["green_circle"]
-            });
-            EndPointCore.NotifyReload(true);
-            //NotifyCore.QYWeixinPushNotifyAsync("上线", $"【淘宝联盟:{dnk}】cookie 上报更新");
-            return accountId;
-        }
-
-
-        public static int CookieDisabled(int id)
-        {
-            return new DBContext.Table("pdd_pool")
-                .Add("cookie_status", 0)
-                .Add("last_time", DateTime.Now)
-                .Where("id=@id", new { id })
-                .Update();
-        }
-
-
-        public static int Update(PddPoolDTO account)
-        {
-            return new DBContext.Table("pdd_pool")
-                .Add("current_hourly_calls", account.current_hourly_calls)
-                .Add("current_daily_calls", account.current_daily_calls)
-
-                //.Add("today_clickNum", account.today_clickNum)
-                //.Add("today_cosFee", account.today_cosFee)
-                //.Add("today_cosPrice", account.today_cosPrice)
-                //.Add("today_finishCosFee", account.today_finishCosFee)
-                //.Add("today_finishCosPrice", account.today_finishCosPrice)
-                //.Add("today_finishOrderNum", account.today_finishOrderNum)
-                //.Add("today_orderNum", account.today_orderNum)
-                .Add("last_time", DateTime.Now)
-                .Where("id=@id", new { account.id })
-                .Update();
-        }
-
-        public static void AccountRisk(PddPoolDTO account)
-        {
-            string cache_key = $"cache:pdd_pool:{account.name}:disabled";
-            long count = RedisHelper.IncrBy(cache_key);
-            RedisHelper.Expire(cache_key, 10);
-            if (count > 1) return;
-
-
-            // 发送通知
-            string message = $"【PDD风控】【{account.id}:{account.name}】出现30007";
-            NotifyCore.Notify(new NifyMessage
-            {
-                message = message,
-                priority = NifyMessagePriority.high,
-                tags = ["red_circle"]
-            });
-
-            if (!account.is_hide)
-            {
-                _ = NotifyCore.QYWeixinPushNotifyAsync("PDD风控", message);
-            }
-
-            // 设置风控标记
-            RiskControlCore.SetRiskCookie($"pdd:{account.id}", "30007");
-
-            // 更新数据库状态
-            new DBContext.Table("pdd_pool")
-            .Add("enable_parse", 0)
-                .Where("id=@id", new { account.id })
-                .Update();
-            _ = List(true);
-
-            _ = EndPointCore.NotifyReload(true);
-
-        }
-    }
-}
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc.Controllers;
+using Microsoft.AspNetCore.Mvc.Filters;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using dodohold.core;
+using Dataoke;
+using Google.Protobuf.WellKnownTypes;
+using System.Diagnostics;
+using System.Text.Json;
+using TencentCloud.Tcm.V20210413.Models;
+using System.Security.Cryptography;
+using System.Collections.Concurrent;
+using YunhuiKit;
+using System.Xml.Linq;
+
+
+namespace molilian.core
+{
+    public partial class PddPoolCore
+    {
+        private static readonly object _lockObj = new();
+        private static IEnumerable<PddPoolDTO> _cached;
+        private static IEnumerable<PddPoolDTO> _all_cached;
+
+        private static string _end_point;
+        private static Dictionary<string, decimal> _incomeAmt = new();
+        private static ConcurrentDictionary<int, DateTime> _suspend = new();
+
+        // 移除本地内存统计,改用 Redis 存储
+
+        private static SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
+
+        static PddPoolCore()
+        {
+            _end_point = Environment.GetEnvironmentVariable("EndPoint");
+        }
+
+        /// <summary>
+        /// 获取一个账号用于处理请求
+        /// 使用平滑加权轮询算法(Smooth Weighted Round-Robin)实现流量均衡分配
+        /// </summary>
+        public static PddPoolDTO? GetOne(PddUnionWorkMode mode, int accountid = 0, string parse_type = "")
+        {
+            var list = List();
+            if (!list.Any()) return null;
+
+            // 按 parse_type 过滤
+            if ("dp".Equals(parse_type))
+            {
+                list = list.Where(item => "dp".Equals(item.parse_type)).ToList();
+                if (!list.Any()) return null;
+            }
+            else
+            {
+                list = list.Where(item => string.IsNullOrEmpty(item.parse_type)).ToList();
+                if (!list.Any()) return null;
+            }
+
+            // 过滤出符合条件的候选账号
+            var candidates = list.Where(e => IsMatch(e, mode, accountid)).ToList();
+            if (!candidates.Any()) return null;
+
+            // 使用平滑加权轮询算法选择账号
+            return SmoothWeightedRoundRobin(candidates, parse_type);
+        }
+
+        /// <summary>
+        /// 基于最后使用时间的调度策略(并发安全版本)
+        /// 选择"距离上次使用时间最长"且"超过cis_limit间隔"的账号
+        ///
+        /// 优势:
+        /// 1. 不区分新老账号,一视同仁(无历史记录的账号返回DateTime.MinValue,自然会被优先选中)
+        /// 2. 自然满足cis_limit要求(距离时间 < limit 会被跳过)
+        /// 3. 优先选择"休息最久"的账号,最大化每个账号的休息时间
+        /// 4. 简单直观,易于调试和理解
+        /// 5. 并发安全:使用Redis原子操作预留账号,避免多个请求竞争同一账号
+        /// </summary>
+        private static PddPoolDTO? SmoothWeightedRoundRobin(List<PddPoolDTO> candidates, string parse_type)
+        {
+            if (!candidates.Any()) return null;
+            if (candidates.Count == 1)
+            {
+                var single = candidates[0];
+                // 尝试预留账号(并发安全)
+                if (TryReserveAccount(single.id, parse_type, single.cis_limit))
+                {
+                    return single;
+                }
+                return null; // 预留失败,说明账号已被其他请求占用
+            }
+
+            try
+            {
+                string groupKey = string.IsNullOrEmpty(parse_type) ? "default" : parse_type;
+                var now = DateTime.Now;
+
+                // 候选账号列表:记录每个账号的空闲时长
+                var accountsWithIdleTime = new List<(PddPoolDTO account, long idleMs)>();
+
+                foreach (var account in candidates)
+                {
+                    // 获取账号最后使用时间
+                    var lastUsedTime = GetAccountLastUsedTime(account.id, groupKey);
+
+                    // 计算距离现在的时间差(毫秒)
+                    long idleMs = (long)(now - lastUsedTime).TotalMilliseconds;
+
+                    // 如果设置了 cis_limit,必须满足间隔要求
+                    if (account.cis_limit > 0 && idleMs < account.cis_limit)
+                    {
+                        continue; // 跳过未满足间隔要求的账号
+                    }
+
+                    accountsWithIdleTime.Add((account, idleMs));
+                }
+
+                // 如果没有满足条件的账号,返回null
+                if (!accountsWithIdleTime.Any()) return null;
+
+                // 按照空闲时间降序排序,优先尝试"休息最久"的账号
+                var sortedAccounts = accountsWithIdleTime
+                    .OrderByDescending(x => x.idleMs)
+                    .ToList();
+
+                // 依次尝试预留账号(从空闲时间最长的开始)
+                foreach (var (account, idleMs) in sortedAccounts)
+                {
+                    // 尝试原子预留此账号
+                    if (TryReserveAccount(account.id, groupKey, account.cis_limit))
+                    {
+                        // 预留成功,更新最后使用时间
+                        SetAccountLastUsedTime(account.id, groupKey, now);
+                        Console.WriteLine($"[PDD] Selected account {account.id} ({account.name}), idle time: {Math.Round(idleMs / 1000.0, 2)}s");
+                        return account;
+                    }
+                    else
+                    {
+                        // 预留失败,说明此账号已被其他并发请求占用,尝试下一个
+                        Console.WriteLine($"[PDD] Account {account.id} ({account.name}) is reserved by another request, trying next...");
+                    }
+                }
+
+                // 所有满足条件的账号都已被占用
+                Console.WriteLine($"[PDD] All available accounts are reserved, no account available");
+                return null;
+            }
+            catch (Exception ex)
+            {
+                Console.WriteLine($"Idle time selection failed: {ex.Message}, fallback to simple strategy");
+
+                // 降级策略:使用原有的最少使用次数算法
+                return candidates
+                    .OrderBy(account => GetCurrentDayUsageFromRedis(account.id))
+                    .ThenBy(account => Guid.NewGuid())
+                    .FirstOrDefault();
+            }
+        }
+
+        /// <summary>
+        /// 尝试原子预留账号(并发安全)
+        /// 使用 Redis SET NX EX 实现原子操作,避免多个请求竞争同一账号
+        /// </summary>
+        /// <param name="accountId">账号ID</param>
+        /// <param name="groupKey">分组键</param>
+        /// <param name="cisLimit">请求间隔限制(毫秒),0表示无限制</param>
+        /// <returns>true=预留成功,false=账号已被占用</returns>
+        private static bool TryReserveAccount(int accountId, string groupKey, int cisLimit)
+        {
+            try
+            {
+                string reserveKey = $"pdd_account_reserved:{groupKey}:{accountId}";
+
+                // 如果没有设置 cis_limit,使用默认100ms的预留时间(防止极端并发)
+                int expireSeconds = cisLimit > 0 ? (int)Math.Ceiling(cisLimit / 1000.0) : 1;
+
+                // 使用 Redis SET NX EX 原子操作(一次性完成设置和过期时间)
+                // 等价于 Redis 命令: SET key value NX EX seconds
+                // 如果 key 不存在则设置成功(返回true),如果已存在则失败(返回false)
+                // 注意:必须使用原子操作,SetNx + Expire 两步操作会有竞态条件!
+                bool reserved = RedisHelper.Set(reserveKey, 1, expireSeconds, CSRedis.RedisExistence.Nx);
+                return reserved;
+            }
+            catch (Exception ex)
+            {
+                Console.WriteLine($"Failed to reserve account {accountId}: {ex.Message}");
+                // 预留失败时保守处理:假设账号不可用
+                return false;
+            }
+        }
+
+        /// <summary>
+        /// 获取账号最后使用时间
+        /// </summary>
+        private static DateTime GetAccountLastUsedTime(int accountId, string groupKey)
+        {
+            try
+            {
+                string key = $"pdd_last_used_time:{groupKey}:{accountId}";
+                var timestamp = RedisHelper.Get<long>(key);
+
+                if (timestamp > 0)
+                {
+                    return DateTimeOffset.FromUnixTimeMilliseconds(timestamp).LocalDateTime;
+                }
+
+                // 如果没有记录,返回很久以前的时间(确保新账号和长期未使用的账号能被优先选中)
+                return DateTime.MinValue;
+            }
+            catch
+            {
+                return DateTime.MinValue;
+            }
+        }
+
+        /// <summary>
+        /// 设置账号最后使用时间
+        /// </summary>
+        private static void SetAccountLastUsedTime(int accountId, string groupKey, DateTime time)
+        {
+            try
+            {
+                string key = $"pdd_last_used_time:{groupKey}:{accountId}";
+                long timestamp = new DateTimeOffset(time).ToUnixTimeMilliseconds();
+
+                // 设置过期时间为24小时,避免数据积累
+                RedisHelper.Set(key, timestamp, 86400);
+            }
+            catch (Exception ex)
+            {
+                Console.WriteLine($"Failed to set last used time for {accountId}: {ex.Message}");
+            }
+        }
+
+        /// <summary>
+        /// 获取账号的静态权重
+        /// 可以根据账号的 daily_calls_limit 或其他因素动态计算
+        /// </summary>
+        private static int GetAccountStaticWeight(int accountId)
+        {
+            try
+            {
+                string key = $"pdd_account_weight:{accountId}";
+                int weight = RedisHelper.Get<int>(key);
+
+                // 如果没有设置,返回默认权重100
+                if (weight <= 0)
+                {
+                    // 可以基于账号的限额动态计算默认权重
+                    var account = List()?.FirstOrDefault(a => a.id == accountId);
+                    if (account != null && account.daily_calls_limit > 0)
+                    {
+                        // 将日限额映射到权重:每1000次调用对应权重10
+                        weight = Math.Max(10, Math.Min(1000, account.daily_calls_limit / 100));
+                    }
+                    else
+                    {
+                        weight = 100; // 默认权重
+                    }
+                }
+
+                return weight;
+            }
+            catch
+            {
+                return 100; // 异常时返回默认权重
+            }
+        }
+
+        /// <summary>
+        /// 设置账号的静态权重
+        /// </summary>
+        public static void SetAccountStaticWeight(int accountId, int weight)
+        {
+            try
+            {
+                if (weight < 1) weight = 1;
+                if (weight > 1000) weight = 1000;
+
+                string key = $"pdd_account_weight:{accountId}";
+                RedisHelper.Set(key, weight, 30 * 86400); // 30天过期
+            }
+            catch (Exception ex)
+            {
+                Console.WriteLine($"Failed to set account weight for {accountId}: {ex.Message}");
+            }
+        }
+
+        /// <summary>
+        /// 获取账号的当前动态权重
+        /// </summary>
+        private static int GetAccountCurrentWeight(int accountId, string groupKey)
+        {
+            try
+            {
+                string key = $"pdd_current_weight:{groupKey}:{accountId}";
+                return RedisHelper.Get<int>(key);
+            }
+            catch
+            {
+                return 0;
+            }
+        }
+
+        /// <summary>
+        /// 设置账号的当前动态权重
+        /// </summary>
+        private static void SetAccountCurrentWeight(int accountId, string groupKey, int weight)
+        {
+            try
+            {
+                string key = $"pdd_current_weight:{groupKey}:{accountId}";
+                RedisHelper.Set(key, weight, 3600); // 1小时过期,自动重置
+            }
+            catch (Exception ex)
+            {
+                Console.WriteLine($"Failed to set current weight for {accountId}: {ex.Message}");
+            }
+        }
+
+        /// <summary>
+        /// 重置所有账号的动态权重(用于调试或重新初始化)
+        /// </summary>
+        public static void ResetAllWeights(string groupKey = "default")
+        {
+            try
+            {
+                var accounts = List()?.Where(a => a.status && a.enable_parse).ToList();
+                if (accounts == null || !accounts.Any()) return;
+
+                foreach (var account in accounts)
+                {
+                    string key = $"pdd_current_weight:{groupKey}:{account.id}";
+                    RedisHelper.Del(key);
+                }
+
+                Console.WriteLine($"Reset weights for {accounts.Count} accounts in group '{groupKey}'");
+            }
+            catch (Exception ex)
+            {
+                Console.WriteLine($"Failed to reset weights: {ex.Message}");
+            }
+        }
+
+        private static int GetCurrentDayUsageFromRedis(int accountId)
+        {
+            try
+            {
+                var today = DateTime.Now.ToString("yyyyMMdd");
+                string key = $"pdd_daily_usage:{accountId}:{today}";
+                return RedisHelper.Get<int>(key);
+            }
+            catch (Exception)
+            {
+                return 0;
+            }
+        }
+
+        internal static void UpdateAccountUsage(int accountId)
+        {
+            try
+            {
+                var today = DateTime.Now.ToString("yyyyMMdd");
+                string key = $"pdd_daily_usage:{accountId}:{today}";
+
+                // 递增计数并设置24小时过期时间
+                RedisHelper.IncrBy(key);
+                RedisHelper.Expire(key, 86400); // 24小时后自动过期
+            }
+            catch (Exception ex)
+            {
+                // 记录错误但不影响主流程
+                Console.WriteLine($"Failed to update account usage for {accountId}: {ex.Message}");
+            }
+        }
+
+        /// <summary>
+        /// 获取当前所有账号的平均使用次数
+        /// </summary>
+        private static int GetAverageUsageCount()
+        {
+            try
+            {
+                var activeAccounts = List()?.Where(a => a.enable_parse).ToList();
+                if (activeAccounts == null || !activeAccounts.Any()) return 0;
+
+                var today = DateTime.Now.ToString("yyyyMMdd");
+                var totalUsage = 0;
+                var validCount = 0;
+
+                foreach (var account in activeAccounts)
+                {
+                    var usage = GetCurrentDayUsageFromRedis(account.id);
+                    totalUsage += usage;
+                    validCount++;
+                }
+
+                return validCount > 0 ? totalUsage / validCount : 0;
+            }
+            catch (Exception ex)
+            {
+                Console.WriteLine($"Failed to get average usage count: {ex.Message}");
+                return 0;
+            }
+        }
+
+        /// <summary>
+        /// 重置所有在线账号的当日调用次数Redis计数器
+        /// 随机排序账号后,每个账号间隔1递增value
+        /// </summary>
+        public static int RechargeAllOnlineAccountUsage()
+        {
+            try
+            {
+                var onlineAccounts = List()?.Where(a => a.status && a.enable_parse).ToList();
+                if (onlineAccounts == null || !onlineAccounts.Any())
+                {
+                    return 0;
+                }
+
+                var today = DateTime.Now.ToString("yyyyMMdd");
+
+                // 随机排序账号
+                var random = new Random();
+                var shuffledAccounts = onlineAccounts.OrderBy(x => random.Next()).ToList();
+
+                int incrementValue = 1;
+                int rechargedCount = 0;
+
+                foreach (var account in shuffledAccounts)
+                {
+                    string key = $"pdd_daily_usage:{account.id}:{today}";
+
+                    // 设置递增的value并设置24小时过期时间
+                    RedisHelper.Set(key, incrementValue, 86400);
+                    RedisHelper.Set(key, 0, 86400);
+                    incrementValue++;
+                    rechargedCount++;
+                }
+                return rechargedCount;
+            }
+            catch (Exception ex)
+            {
+            }
+            return 0;
+        }
+
+        /// <summary>
+        /// 为新上线的账号设置平均使用次数(仅用于降级策略的统计)
+        /// 注意:基于时间间隔的调度策略不需要初始化权重,因为无历史记录的账号会自动返回DateTime.MinValue
+        /// </summary>
+        public static void InitializeNewAccountUsage(int accountId, string groupKey = "default")
+        {
+            try
+            {
+                var today = DateTime.Now.ToString("yyyyMMdd");
+                string usageKey = $"pdd_daily_usage:{accountId}:{today}";
+
+                // 检查该账号今天是否已有使用记录
+                var currentUsage = RedisHelper.Get<int>(usageKey);
+                if (currentUsage > 0)
+                {
+                    Console.WriteLine($"Account {accountId} already has usage record: {currentUsage}, skip initialization");
+                    return; // 已有记录,不需要初始化
+                }
+
+                // 获取所有在线账号的平均值(仅用于降级策略)
+                var activeAccounts = List()?.Where(a => a.status && a.enable_parse && a.id != accountId).ToList();
+                if (activeAccounts == null || !activeAccounts.Any())
+                {
+                    Console.WriteLine($"No other active accounts found, account {accountId} will use default value");
+                    return; // 没有其他账号,使用默认值0即可
+                }
+
+                // 初始化 daily_usage(用于降级策略的统计)
+                var totalUsage = 0;
+                foreach (var account in activeAccounts)
+                {
+                    totalUsage += GetCurrentDayUsageFromRedis(account.id);
+                }
+                var averageUsage = totalUsage / activeAccounts.Count;
+                if (averageUsage > 0)
+                {
+                    RedisHelper.Set(usageKey, averageUsage, 86400);
+                    Console.WriteLine($"Initialized account {accountId} daily_usage with average: {averageUsage}");
+                }
+
+                Console.WriteLine($"Account {accountId} initialization complete. Time-based scheduling will use DateTime.MinValue for idle time calculation.");
+            }
+            catch (Exception ex)
+            {
+                Console.WriteLine($"Failed to initialize new account for {accountId}: {ex.Message}");
+            }
+        }
+        /// <summary>
+        /// 获取当前统计信息(用于监控和调试)- 改为从Redis获取
+        /// </summary>
+        internal static Dictionary<string, int> GetCurrentUsageSnapshot()
+        {
+            try
+            {
+                var result = new Dictionary<string, int>();
+                var accounts = List()?.Where(a => a.status && a.enable_parse).ToList();
+                if (accounts == null || !accounts.Any()) return result;
+
+                var today = DateTime.Now.ToString("yyyyMMdd");
+                foreach (var account in accounts)
+                {
+                    string key = $"pdd_daily_usage:{account.id}:{today}";
+                    int usage = RedisHelper.Get<int>(key);
+                    result[$"Account_{account.id}_{account.name}"] = usage;
+                }
+
+                return result;
+            }
+            catch (Exception)
+            {
+                return new Dictionary<string, int>();
+            }
+        }
+
+        /// <summary>
+        /// 获取账号调度时间间隔信息(用于监控和调试)
+        /// </summary>
+        public static Dictionary<string, object> GetScheduleWeightSnapshot(string groupKey = "default")
+        {
+            try
+            {
+                var result = new Dictionary<string, object>();
+                var accounts = List()?.Where(a => a.status && a.enable_parse).ToList();
+                if (accounts == null || !accounts.Any()) return result;
+
+                var now = DateTime.Now;
+                var accountInfos = new List<Dictionary<string, object>>();
+
+                foreach (var account in accounts)
+                {
+                    var lastUsedTime = GetAccountLastUsedTime(account.id, groupKey);
+                    long idleMs = (long)(now - lastUsedTime).TotalMilliseconds;
+                    bool canUse = account.cis_limit <= 0 || idleMs >= account.cis_limit;
+
+                    var info = new Dictionary<string, object>
+                    {
+                        ["account_id"] = account.id,
+                        ["account_name"] = account.name,
+                        ["cis_limit_ms"] = account.cis_limit,
+                        ["last_used_time"] = lastUsedTime == DateTime.MinValue ? "从未使用" : lastUsedTime.ToString("yyyy-MM-dd HH:mm:ss.fff"),
+                        ["idle_time_ms"] = idleMs,
+                        ["idle_time_seconds"] = Math.Round(idleMs / 1000.0, 2),
+                        ["can_use"] = canUse,
+                        ["daily_usage"] = GetCurrentDayUsageFromRedis(account.id),
+                        ["daily_limit"] = account.daily_calls_limit
+                    };
+                    accountInfos.Add(info);
+                }
+
+                // 按照空闲时间降序排序(与选择逻辑一致)
+                accountInfos = accountInfos.OrderByDescending(x => (long)x["idle_time_ms"]).ToList();
+
+                result["accounts"] = accountInfos;
+                result["group_key"] = groupKey;
+                result["timestamp"] = now.ToString("yyyy-MM-dd HH:mm:ss.fff");
+                result["total_accounts"] = accountInfos.Count;
+                result["available_accounts"] = accountInfos.Count(x => (bool)x["can_use"]);
+
+                return result;
+            }
+            catch (Exception ex)
+            {
+                return new Dictionary<string, object> { ["error"] = ex.Message };
+            }
+        }
+
+        /// <summary>
+        /// 强制清理统计数据(仅用于测试或紧急情况)- Redis版本
+        /// </summary>
+        internal static void ForceClearStats()
+        {
+            try
+            {
+                var today = DateTime.Now.ToString("yyyyMMdd");
+                var pattern = $"pdd_daily_usage:*:{today}";
+
+                // TODO: 实现Redis批量删除相关keys
+                // 生产环境中需要谨慎使用
+                Console.WriteLine("Force clear stats - Redis keys will auto-expire in 24h");
+            }
+            catch (Exception ex)
+            {
+                Console.WriteLine($"Failed to force clear stats: {ex.Message}");
+            }
+        }
+
+        internal static void TempSuspend(int accountId)
+        {
+            _suspend.AddOrUpdate(accountId, DateTime.Now, (key, oldValue) => DateTime.Now);
+        }
+
+        private static bool IsMatch(PddPoolDTO item, PddUnionWorkMode mode, int accountid)
+        {
+            if (accountid != 0 && accountid != item.id) return false;
+            if (!item.enable_parse) return false;
+            if (mode != PddUnionWorkMode.All && mode != item.work_mode) return false;
+            if (item.work_mode == PddUnionWorkMode.Crawler && !item.cookie_status) return false;
+
+            if (!string.IsNullOrEmpty(_end_point) && !string.IsNullOrEmpty(item.end_point))
+            {
+                if (item.end_point != _end_point) return false;
+            }
+
+            // 使用初始化参数创建工作时间表
+            if (!new WorkSchedule(item.time_range).IsWorkHour()) return false;
+
+            if (_suspend.TryGetValue(accountid, out DateTime suspendTime))
+            {
+                var ts = DateTime.Now - suspendTime;
+                if (ts.TotalSeconds < 70) return false;
+            }
+
+            // cis_limit 检查已移至 SmoothWeightedRoundRobin 方法中基于时间间隔判断
+            // 这里不再需要检查 Redis 锁
+
+            if (item.rpm_limit > 0)
+            {
+                int rpm_num = RiskControlCore.GetCalls(TkChannelEnum.pdd, item.id, DateTime.Now.ToString("yyyyMMddHHmm"));
+                if (rpm_num >= item.rpm_limit) return false;
+            }
+            if (item.daily_calls_limit > 0)
+            {
+                int daily_num = RiskControlCore.GetCalls(TkChannelEnum.pdd, item.id, DateTime.Now.ToString("yyyyMMdd"));
+                if (daily_num >= item.daily_calls_limit) return false;
+            }
+            if (item.hourly_calls_limit > 0)
+            {
+                int hourly_num = RiskControlCore.GetCalls(TkChannelEnum.pdd, item.id, DateTime.Now.ToString("yyyyMMddHH"));
+                if (hourly_num >= item.hourly_calls_limit) return false;
+            }
+            return true;
+        }
+
+
+
+        public static async Task<IEnumerable<PddPoolDTO>> AllListAsync(bool force = false)
+        {
+            if (!force && _all_cached != default) return _all_cached;
+
+            try
+            {
+                await _semaphore.WaitAsync();
+
+                string cache_key = $"cache:all_pdd_pool";
+                var list = await RedisKit.GetAsync<IEnumerable<PddPoolDTO>>(cache_key);
+
+                if (force || list == default)
+                {
+                    list = new DBContext.Table("pdd_pool").Select<PddPoolDTO>();
+                    if (list == null) return default;
+
+                    // 等待 Redis 写入完成
+                    await RedisKit.SetAsync(cache_key, list, 30 * 86400);
+                }
+
+                _all_cached = list;
+                return list;
+            }
+            catch (Exception)
+            {
+                // 发生异常时返回上一次的缓存,如果没有则返回默认值
+                return _all_cached ?? default;
+            }
+            finally
+            {
+                _semaphore.Release();
+            }
+        }
+        public static IEnumerable<PddPoolDTO> List(bool force = false)
+        {
+            if (!force && _cached != null) return _cached;
+
+            string cache_key = $"cache:pdd_pool";
+            var list = RedisHelper.Get<IEnumerable<PddPoolDTO>>(cache_key);
+            if (force || list == null)
+            {
+                lock (_lockObj)
+                {
+                    list = new DBContext.Table("pdd_pool")
+                        .Where("status=@status", new { status = 1 })
+                        .Select<PddPoolDTO>();
+                    if (list == null) return default;
+                    foreach (var item in list)
+                    {
+                        RiskControlCore.SetCallsAsync(TkChannelEnum.pdd, item.id, DateTime.Now.ToString("yyyyMMddHH"), item.current_hourly_calls);
+                        RiskControlCore.SetCallsAsync(TkChannelEnum.pdd, item.id, DateTime.Now.ToString("yyyyMMdd"), item.current_daily_calls);
+                    }
+                    RedisHelper.Set(cache_key, list, 30 * 86400);
+                }
+            }
+            _cached = list;
+            return list;
+        }
+        public static void Refresh()
+        {
+            _cached = null;
+            _all_cached = null;
+            _ = List(true);
+            _ = AllListAsync(true);
+        }
+
+        public static void Disabled(string name)
+        {
+            string cache_key = $"cache:pdd_pool:{name}:disabled";
+            long count = RedisHelper.IncrBy(cache_key);
+            RedisHelper.Expire(cache_key, 10);
+            if (count > 1) return;
+
+            new DBContext.Table("pdd_pool")
+                .Add("status", 0)
+                .Where("name=@name", new { name })
+                .Update();
+            _ = List(true);
+
+            NotifyCore.Notify(new NifyMessage
+            {
+                message = $"【多多:{name}】调用异常",
+                priority = NifyMessagePriority.high,
+                tags = ["red_circle"]
+            });
+        }
+
+
+        public static void Disabled(int accountId, string name, string content)
+        {
+            string cache_key = $"cache:pdd_pool:{name}:disabled";
+            long count = RedisHelper.IncrBy(cache_key);
+            RedisHelper.Expire(cache_key, 10);
+            if (count > 1) return;
+
+            var update = new DBContext.Table("pdd_pool").Add("status", 0);
+            if (accountId > 0)
+            {
+                update.Where("id=@accountId", new { accountId }).Update();
+            }
+            else
+            {
+                update.Where("name=@name", new { name }).Update();
+            }
+
+            _ = List(true);
+
+            NotifyCore.Notify(new NifyMessage
+            {
+                message = $"【多多{accountId}:{name}】cookie 掉线\n\n{content}",
+                priority = NifyMessagePriority.high,
+                tags = ["red_circle"]
+            });
+
+            //NotifyCore.QYWeixinPushNotifyAsync("掉线", $"【多多{accountId}:{name}】cookie 掉线");
+            EndPointCore.NotifyReload(true);
+        }
+
+        internal static void AccountExhausted()
+        {
+            string cache_key = $"cache:pdd_pool:account:exhausted";
+            long count = RedisHelper.IncrBy(cache_key);
+            if (count > 1) return;
+            RedisHelper.Expire(cache_key, 3600);
+            NotifyCore.Notify(new NifyMessage
+            {
+                message = $"【多多】没有匹配账号",
+                priority = NifyMessagePriority.high,
+                tags = ["red_circle"]
+            });
+            //_ = NotifyCore.QYWeixinPushNotifyAsync("没账号", $"【多多】没有匹配账号");
+        }
+
+        internal static async Task<JsonElement> GetUserInfo(string cookies)
+        {
+            JsonElement result = default;
+            try
+            {
+                string url = "https://jinbao.pinduoduo.com/network/api/account/userInfo";
+                WebClientUtility client = new WebClientUtility();
+                client.Post("{}");
+                client.SetCookies(cookies);
+                client.SetContentType("application/json");
+                var response = await client.RequestAsync(url, "POST");
+                var body = response.Body();
+                result = body.Convert2JsonElement();
+            }
+            catch (Exception ex)
+            {
+            }
+            return result;
+        }
+
+        public static async Task<int> UpdateCookies(string cookies, string user_agent, int id = 0)
+        {
+            if (string.IsNullOrEmpty(cookies)) return 0;
+            var userinfo = await GetUserInfo(cookies);
+            if (userinfo.ValueKind != JsonValueKind.Object) return 0;
+
+            int duoId = userinfo.PathRead<int>("result.duoId", 0);
+            string company = userinfo.PathRead<string>("result.mobile", string.Empty);
+            string lastPid = userinfo.PathRead<string>("result.lastPid", string.Empty);
+
+            int accountId = 0;
+            if (duoId == 0 && id == 0) return 0;
+
+
+            string filter = id != 0 ? "id=@id" : "duoId=@duoId";
+            var exist = new DBContext.Table("pdd_pool").Get<PddPoolDTO>(filter, new { id, duoId });
+            if (exist != null)
+            {
+                var status = exist.status;
+                var work_mode = exist.work_mode;
+                accountId = exist.id;
+                if (work_mode == PddUnionWorkMode.Crawler) status = true;
+                new DBContext.Table("pdd_pool")
+                    .Add("cookies", cookies)
+                    .Add("user_agent", user_agent)
+                    .Add("status", status)
+                    .Add("cookie_status", 1)
+                    .Add("last_time", DateTime.Now)
+                    .Add("login_time", DateTime.Now)
+                    .Where("id=@id", new { exist.id })
+                    .Update();
+                if (status)
+                {
+                    _ = List(true);
+                    // 为重新上线的账号初始化使用次数和权重(针对所有分组)
+                    InitializeNewAccountUsage(exist.id, "default");
+                    InitializeNewAccountUsage(exist.id, "dp");
+                }
+            }
+            else
+            {
+                accountId = new DBContext.Table("pdd_pool")
+                    .Add("duoId", duoId)
+                    .Add("name", company)
+                    .Add("company", company)
+                    .Add("description", "由cookies上报创建此记录")
+                    .Add("cookies", cookies)
+                    .Add("user_agent", user_agent)
+                    .Add("pid", lastPid)
+                    .Add("cookie_status", 1)
+                    .Add("create_time", DateTime.Now)
+                    .Add("last_time", DateTime.Now)
+                    .Add("login_time", DateTime.Now)
+                    .Add("status", 0)
+                    .Create();
+
+                // 为新创建的账号初始化使用次数(如果将来会启用的话)
+                // InitializeNewAccountUsage(accountId); // 暂不调用,因为新创建的账号status=0
+            }
+
+            NotifyCore.Notify(new NifyMessage
+            {
+                message = $"【拼多多{accountId}:{company}】cookie 上线",
+                tags = ["green_circle"]
+            });
+            EndPointCore.NotifyReload(true);
+            //NotifyCore.QYWeixinPushNotifyAsync("上线", $"【淘宝联盟:{dnk}】cookie 上报更新");
+            return accountId;
+        }
+
+
+        public static int CookieDisabled(int id)
+        {
+            return new DBContext.Table("pdd_pool")
+                .Add("cookie_status", 0)
+                .Add("last_time", DateTime.Now)
+                .Where("id=@id", new { id })
+                .Update();
+        }
+
+
+        public static int Update(PddPoolDTO account)
+        {
+            return new DBContext.Table("pdd_pool")
+                .Add("current_hourly_calls", account.current_hourly_calls)
+                .Add("current_daily_calls", account.current_daily_calls)
+
+                //.Add("today_clickNum", account.today_clickNum)
+                //.Add("today_cosFee", account.today_cosFee)
+                //.Add("today_cosPrice", account.today_cosPrice)
+                //.Add("today_finishCosFee", account.today_finishCosFee)
+                //.Add("today_finishCosPrice", account.today_finishCosPrice)
+                //.Add("today_finishOrderNum", account.today_finishOrderNum)
+                //.Add("today_orderNum", account.today_orderNum)
+                .Add("last_time", DateTime.Now)
+                .Where("id=@id", new { account.id })
+                .Update();
+        }
+
+        public static void AccountRisk(PddPoolDTO account)
+        {
+            string cache_key = $"cache:pdd_pool:{account.name}:disabled";
+            long count = RedisHelper.IncrBy(cache_key);
+            RedisHelper.Expire(cache_key, 10);
+            if (count > 1) return;
+
+
+            // 发送通知
+            string message = $"【PDD风控】【{account.id}:{account.name}】出现30007";
+            NotifyCore.Notify(new NifyMessage
+            {
+                message = message,
+                priority = NifyMessagePriority.high,
+                tags = ["red_circle"]
+            });
+
+            if (!account.is_hide)
+            {
+                _ = NotifyCore.QYWeixinPushNotifyAsync("PDD风控", message);
+            }
+
+            // 设置风控标记
+            RiskControlCore.SetRiskCookie($"pdd:{account.id}", "30007");
+
+            // 更新数据库状态
+            new DBContext.Table("pdd_pool")
+            .Add("enable_parse", 0)
+                .Where("id=@id", new { account.id })
+                .Update();
+            _ = List(true);
+
+            _ = EndPointCore.NotifyReload(true);
+
+        }
+    }
+}

+ 279 - 279
molilian.core/Core/taoke/OrderTrackingCore.cs

@@ -1,279 +1,279 @@
-using Microsoft.AspNetCore.Http;
-using Microsoft.AspNetCore.Mvc.Controllers;
-using Microsoft.AspNetCore.Mvc.Filters;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using dodohold.core;
-using Dataoke;
-using Google.Protobuf.WellKnownTypes;
-using System.Diagnostics;
-using System.Text.Json;
-using TencentCloud.Tcm.V20210413.Models;
-using System.Security.Cryptography;
-using System.Data;
-using System.Threading.Channels;
-using YunhuiKit;
-using System.IO;
-
-
-namespace molilian.core
-{
-    public partial class TkOrderTrackingCore
-    {
-        public static void SaveLinkSummary(TkDataDTO item)
-        {
-            //清洗数据
-            item.content = null;
-            item.rawContent = null;
-            item.rawContent2 = null;
-
-            TkDataDTO newData = new()
-            {
-                channel = item.channel,
-                ip = item.ip,
-                oaid = item.oaid,
-                mktId = item.mktId,
-                itemId = item.itemId,
-                itemName = item.itemName,
-                taoToken = item.taoToken,
-                shortLinkurl = item.shortLinkurl,
-                deeplink_url = item.deeplink_url,
-                create_time = item.create_time,
-            };
-
-            var config = TkConfigCore.Get();
-            var ttl = config.fake_click_ttl * 3600;
-            string cacheKey = $":cache:order_summary:{item.accountId}:{item.itemId}";
-            RedisHelper.Set(cacheKey, newData, ttl);
-
-            string mktId = item.mktId;
-            if (!string.IsNullOrEmpty(mktId) && mktId.Contains('-'))
-            {
-                mktId = mktId.Split('-')[^1];
-                cacheKey = $":cache:order_summary:{item.accountId}:mktId:{mktId}";
-                RedisHelper.Set(cacheKey, newData, ttl);
-            }
-
-            cacheKey = $":cache:order_summary:{item.accountId}:{item.itemName}";
-            RedisHelper.Set(cacheKey, newData, ttl);
-        }
-
-
-        public static async Task<(int, TkDataDTO)> GetLinkSummaryAsync(TkPoolDTO account, string itemId, string mktId, string itemTitle)
-        {
-            try
-            {
-                int accountId = account.id;
-                mktId = !string.IsNullOrEmpty(mktId) && mktId.Contains('-')
-                    ? mktId.Split('-')[^1]
-                    : mktId;
-
-
-                // 获取所有关联账户ID(包括当前账户)
-                var relatedAccountIds = new List<int> { accountId };
-                if (!string.IsNullOrEmpty(account.related_account_ids))
-                {
-                    relatedAccountIds.AddRange(account.related_account_ids
-                        .Split(',')
-                        .Where(id => !string.IsNullOrWhiteSpace(id))
-                        .Select(int.Parse));
-                }
-
-                var tasks = EndPointCore.List()
-                    .Where(node => node.is_public_api && !string.IsNullOrEmpty(node.redis_server))
-                    .Select(async node =>
-                    {
-                        try
-                        {
-                            var redisServer = EndPointCore.GetRedisServer(node);
-                            if (string.IsNullOrEmpty(redisServer)) return (0, null);
-
-
-                            using var scope = await RedisClientFactory.CreateScopeAsync(redisServer);
-                            var redis = scope.Client;
-
-                            // 按优先级依次查询不同的缓存key
-                            TkDataDTO result = null;
-
-
-                            // 遍历所有关联账户ID
-                            foreach (var relatedId in relatedAccountIds)
-                            {
-                                if (!string.IsNullOrEmpty(mktId))
-                                {
-                                    result = await redis.GetAsync<TkDataDTO>($":cache:order_summary:{relatedId}:mktId:{mktId}");
-                                    if (result != null) return (1, result);
-                                }
-
-                                if (!string.IsNullOrEmpty(itemId))
-                                {
-                                    result = await redis.GetAsync<TkDataDTO>($":cache:order_summary:{relatedId}:{itemId}");
-                                    if (result != null) return (2, result);
-
-                                    if (!string.IsNullOrEmpty(itemTitle))
-                                    {
-                                        result = await redis.GetAsync<TkDataDTO>($":cache:order_summary:{relatedId}:{itemTitle}");
-                                        if (result != null) return (3, result);
-                                    }
-                                }
-                            }
-                        }
-                        catch (Exception ex)
-                        {
-                        }
-                        return (0, null);
-                    });
-
-                var results = await Task.WhenAll(tasks);
-                return results.FirstOrDefault(r => r != (0, null));
-            }
-            catch (Exception)
-            {
-                // TODO: 添加日志记录
-                return (0, null);
-            }
-        }
-
-        public static async Task RemoveLinkSummaryAsync(int accountId, string itemId, string mktId, string itemTitle)
-        {
-            try
-            {
-                var tasks = EndPointCore.List()
-                    .Where(node => node.is_public_api && !string.IsNullOrEmpty(node.redis_server))
-                    .Select(async node =>
-                    {
-                        try
-                        {
-
-                            var redisServer = EndPointCore.GetRedisServer(node);
-                            if (string.IsNullOrEmpty(redisServer)) return;
-
-                            using var scope = await RedisClientFactory.CreateScopeAsync(redisServer);
-                            var redis = scope.Client;
-
-                            var keys = new[]
-                            {
-                                $":cache:order_summary:{accountId}:{itemId}",
-                                $":cache:order_summary:{accountId}:mktId:{mktId}",
-                                $":cache:order_summary:{accountId}:{itemTitle}"
-                            }.Where(k => !string.IsNullOrEmpty(k));
-
-                            // 批量删除所有相关缓存
-                            await redis.DelAsync(keys.ToArray());
-                        }
-                        catch (Exception ex) { }
-                    });
-                await Task.WhenAll(tasks);
-            }
-            catch (Exception)
-            {
-                // TODO: 添加日志记录
-            }
-        }
-
-
-
-        //public static async void RemoveLinkSummary(int accountId, string itemId, string mktId, string itemTitle)
-        //{
-        //    await EndPointCore.ProcessEndPointNodesAsync(node =>
-        //    {
-        //        if (!node.is_public_api) return Task.CompletedTask;
-        //        if (string.IsNullOrEmpty(node.redis_server)) return Task.CompletedTask;
-
-
-        //        var redis = RedisClientManager.GetRedisClient(node.redis_server);
-
-        //        string cacheKey = $":cache:order_summary:{accountId}:{itemId}";
-        //        redis.Del(cacheKey);
-
-        //        cacheKey = $":cache:order_summary:{accountId}:mktId:{mktId}";
-        //        redis.Del(cacheKey);
-
-        //        cacheKey = $":cache:order_summary:{accountId}:{itemTitle}";
-        //        redis.Del(cacheKey);
-
-        //        return Task.CompletedTask;
-        //    });
-        //}
-
-        public static async Task<bool> MatchOrder(TkPoolDTO account, TkOrderDetailDTO item)
-        {
-            DateTime now = DateTime.Now;
-            if (item.tbPaidTime < now.AddDays(-24)) return false;
-
-            (int match_type, var summary) = await GetLinkSummaryAsync(account, item.itemId, item.mktId, item.itemTitle);
-            if (summary == null) return false;
-
-
-            var config = TkConfigCore.Get();
-            Random rand = new();
-            int click_num = rand.Next(config.fake_click_min, config.fake_click_max + 1);
-
-            if (account.fake_click_min != 0 && account.fake_click_max != 0)
-                click_num = rand.Next(account.fake_click_min, account.fake_click_max + 1);
-
-            DateTime paidTime = item.tbPaidTime;
-            if (paidTime < summary.create_time) return false;
-            if (paidTime < now.AddDays(-24)) return false;
-
-            int minutes = now.Minute / 10 * 10;
-            DateTime rounded = new(now.Year, now.Month, now.Day, now.Hour, minutes, 0);
-            string batchId = rounded.ToString("yyyyMMddHHmm");
-
-            string accountName = item.accountName;
-
-            string shortLinkUrl = !string.IsNullOrEmpty(summary.item_url) ? summary.item_url : summary.shortLinkurl;
-            string deeplinkUrl = !string.IsNullOrEmpty(summary.item_deeplink_url) ? summary.item_deeplink_url : summary.deeplink_url;
-            switch (account.fake_click_link_type)
-            {
-                case FakeClickLinkType.Deeplink:
-                    shortLinkUrl = string.Empty;
-                    break;
-                case FakeClickLinkType.H5:
-                    deeplinkUrl = string.Empty;
-                    break;
-            }
-
-            var exist = new DBContext.Table("tk_order_tracking").Fields("id").Get<dynamic>("tradeId=@tradeId", new { item.tradeId });
-
-            DateTime expTime = now.Hour >= 21 ? now.AddHours(3) : now.Date.AddDays(1);
-            var data = new TkOrderTrackingDTO()
-            {
-                match_type = match_type,
-
-                channel = summary.channel,
-                accountId = item.accountId,
-                accountName = accountName,
-                batchId = batchId,
-                ip = summary.ip,
-                oaid = summary.oaid,
-                mktId = summary.mktId,
-                itemId = summary.itemId,
-                itemName = summary.itemName,
-                taoToken = summary.taoToken,
-                shortLinkUrl = shortLinkUrl,
-                deeplinkUrl = deeplinkUrl,
-
-                tradeId = item.tradeId,
-                tradeParentId = item.tradeParentId,
-
-                create_time = summary.create_time,
-                click_time = item.clickTime,
-                paid_time = item.tbPaidTime,
-
-                exp_time = expTime,
-                click_num = click_num,
-            };
-            if (exist == null)
-            {
-                using var conn = DBContext.GetOpenConnection();
-                conn.Insert(data);
-            }
-            //RemoveLinkSummary(item.accountId, item.itemId, item.mktId, item.itemTitle);
-            await RemoveLinkSummaryAsync(item.accountId, item.itemId, item.mktId, item.itemTitle);
-            return true;
-        }
-    }
-}
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc.Controllers;
+using Microsoft.AspNetCore.Mvc.Filters;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using dodohold.core;
+using Dataoke;
+using Google.Protobuf.WellKnownTypes;
+using System.Diagnostics;
+using System.Text.Json;
+using TencentCloud.Tcm.V20210413.Models;
+using System.Security.Cryptography;
+using System.Data;
+using System.Threading.Channels;
+using YunhuiKit;
+using System.IO;
+
+
+namespace molilian.core
+{
+    public partial class TkOrderTrackingCore
+    {
+        public static void SaveLinkSummary(TkDataDTO item)
+        {
+            //清洗数据
+            item.content = null;
+            item.rawContent = null;
+            item.rawContent2 = null;
+
+            TkDataDTO newData = new()
+            {
+                channel = item.channel,
+                ip = item.ip,
+                oaid = item.oaid,
+                mktId = item.mktId,
+                itemId = item.itemId,
+                itemName = item.itemName,
+                taoToken = item.taoToken,
+                shortLinkurl = item.shortLinkurl,
+                deeplink_url = item.deeplink_url,
+                create_time = item.create_time,
+            };
+
+            var config = TkConfigCore.Get();
+            var ttl = config.fake_click_ttl * 3600;
+            string cacheKey = $":cache:order_summary:{item.accountId}:{item.itemId}";
+            RedisHelper.Set(cacheKey, newData, ttl);
+
+            string mktId = item.mktId;
+            if (!string.IsNullOrEmpty(mktId) && mktId.Contains('-'))
+            {
+                mktId = mktId.Split('-')[^1];
+                cacheKey = $":cache:order_summary:{item.accountId}:mktId:{mktId}";
+                RedisHelper.Set(cacheKey, newData, ttl);
+            }
+
+            cacheKey = $":cache:order_summary:{item.accountId}:{item.itemName}";
+            RedisHelper.Set(cacheKey, newData, ttl);
+        }
+
+
+        public static async Task<(int, TkDataDTO)> GetLinkSummaryAsync(TkPoolDTO account, string itemId, string mktId, string itemTitle)
+        {
+            try
+            {
+                int accountId = account.id;
+                mktId = !string.IsNullOrEmpty(mktId) && mktId.Contains('-')
+                    ? mktId.Split('-')[^1]
+                    : mktId;
+
+
+                // 获取所有关联账户ID(包括当前账户)
+                var relatedAccountIds = new List<int> { accountId };
+                if (!string.IsNullOrEmpty(account.related_account_ids))
+                {
+                    relatedAccountIds.AddRange(account.related_account_ids
+                        .Split(',')
+                        .Where(id => !string.IsNullOrWhiteSpace(id))
+                        .Select(int.Parse));
+                }
+
+                var tasks = EndPointCore.List()
+                    .Where(node => node.is_public_api && !string.IsNullOrEmpty(node.redis_server))
+                    .Select(async node =>
+                    {
+                        try
+                        {
+                            var redisServer = EndPointCore.GetRedisServer(node);
+                            if (string.IsNullOrEmpty(redisServer)) return (0, null);
+
+
+                            using var scope = await RedisClientFactory.CreateScopeAsync(redisServer);
+                            var redis = scope.Client;
+
+                            // 按优先级依次查询不同的缓存key
+                            TkDataDTO result = null;
+
+
+                            // 遍历所有关联账户ID
+                            foreach (var relatedId in relatedAccountIds)
+                            {
+                                if (!string.IsNullOrEmpty(mktId))
+                                {
+                                    result = await redis.GetAsync<TkDataDTO>($":cache:order_summary:{relatedId}:mktId:{mktId}");
+                                    if (result != null) return (1, result);
+                                }
+
+                                if (!string.IsNullOrEmpty(itemId))
+                                {
+                                    result = await redis.GetAsync<TkDataDTO>($":cache:order_summary:{relatedId}:{itemId}");
+                                    if (result != null) return (2, result);
+
+                                    if (!string.IsNullOrEmpty(itemTitle))
+                                    {
+                                        result = await redis.GetAsync<TkDataDTO>($":cache:order_summary:{relatedId}:{itemTitle}");
+                                        if (result != null) return (3, result);
+                                    }
+                                }
+                            }
+                        }
+                        catch (Exception ex)
+                        {
+                        }
+                        return (0, null);
+                    });
+
+                var results = await Task.WhenAll(tasks);
+                return results.FirstOrDefault(r => r != (0, null));
+            }
+            catch (Exception)
+            {
+                // TODO: 添加日志记录
+                return (0, null);
+            }
+        }
+
+        public static async Task RemoveLinkSummaryAsync(int accountId, string itemId, string mktId, string itemTitle)
+        {
+            try
+            {
+                var tasks = EndPointCore.List()
+                    .Where(node => node.is_public_api && !string.IsNullOrEmpty(node.redis_server))
+                    .Select(async node =>
+                    {
+                        try
+                        {
+
+                            var redisServer = EndPointCore.GetRedisServer(node);
+                            if (string.IsNullOrEmpty(redisServer)) return;
+
+                            using var scope = await RedisClientFactory.CreateScopeAsync(redisServer);
+                            var redis = scope.Client;
+
+                            var keys = new[]
+                            {
+                                $":cache:order_summary:{accountId}:{itemId}",
+                                $":cache:order_summary:{accountId}:mktId:{mktId}",
+                                $":cache:order_summary:{accountId}:{itemTitle}"
+                            }.Where(k => !string.IsNullOrEmpty(k));
+
+                            // 批量删除所有相关缓存
+                            await redis.DelAsync(keys.ToArray());
+                        }
+                        catch (Exception ex) { }
+                    });
+                await Task.WhenAll(tasks);
+            }
+            catch (Exception)
+            {
+                // TODO: 添加日志记录
+            }
+        }
+
+
+
+        //public static async void RemoveLinkSummary(int accountId, string itemId, string mktId, string itemTitle)
+        //{
+        //    await EndPointCore.ProcessEndPointNodesAsync(node =>
+        //    {
+        //        if (!node.is_public_api) return Task.CompletedTask;
+        //        if (string.IsNullOrEmpty(node.redis_server)) return Task.CompletedTask;
+
+
+        //        var redis = RedisClientManager.GetRedisClient(node.redis_server);
+
+        //        string cacheKey = $":cache:order_summary:{accountId}:{itemId}";
+        //        redis.Del(cacheKey);
+
+        //        cacheKey = $":cache:order_summary:{accountId}:mktId:{mktId}";
+        //        redis.Del(cacheKey);
+
+        //        cacheKey = $":cache:order_summary:{accountId}:{itemTitle}";
+        //        redis.Del(cacheKey);
+
+        //        return Task.CompletedTask;
+        //    });
+        //}
+
+        public static async Task<bool> MatchOrder(TkPoolDTO account, TkOrderDetailDTO item)
+        {
+            DateTime now = DateTime.Now;
+            if (item.tbPaidTime < now.AddDays(-24)) return false;
+
+            (int match_type, var summary) = await GetLinkSummaryAsync(account, item.itemId, item.mktId, item.itemTitle);
+            if (summary == null) return false;
+
+
+            var config = TkConfigCore.Get();
+            Random rand = new();
+            int click_num = rand.Next(config.fake_click_min, config.fake_click_max + 1);
+
+            if (account.fake_click_min != 0 && account.fake_click_max != 0)
+                click_num = rand.Next(account.fake_click_min, account.fake_click_max + 1);
+
+            DateTime paidTime = item.tbPaidTime;
+            if (paidTime < summary.create_time) return false;
+            if (paidTime < now.AddDays(-24)) return false;
+
+            int minutes = now.Minute / 10 * 10;
+            DateTime rounded = new(now.Year, now.Month, now.Day, now.Hour, minutes, 0);
+            string batchId = rounded.ToString("yyyyMMddHHmm");
+
+            string accountName = item.accountName;
+
+            string shortLinkUrl = !string.IsNullOrEmpty(summary.item_url) ? summary.item_url : summary.shortLinkurl;
+            string deeplinkUrl = !string.IsNullOrEmpty(summary.item_deeplink_url) ? summary.item_deeplink_url : summary.deeplink_url;
+            switch (account.fake_click_link_type)
+            {
+                case FakeClickLinkType.Deeplink:
+                    shortLinkUrl = string.Empty;
+                    break;
+                case FakeClickLinkType.H5:
+                    deeplinkUrl = string.Empty;
+                    break;
+            }
+
+            var exist = new DBContext.Table("tk_order_tracking").Fields("id").Get<dynamic>("tradeId=@tradeId", new { item.tradeId });
+
+            DateTime expTime = now.Hour >= 21 ? now.AddHours(3) : now.Date.AddDays(1);
+            var data = new TkOrderTrackingDTO()
+            {
+                match_type = match_type,
+
+                channel = summary.channel,
+                accountId = item.accountId,
+                accountName = accountName,
+                batchId = batchId,
+                ip = summary.ip,
+                oaid = summary.oaid,
+                mktId = summary.mktId,
+                itemId = summary.itemId,
+                itemName = summary.itemName,
+                taoToken = summary.taoToken,
+                shortLinkUrl = shortLinkUrl,
+                deeplinkUrl = deeplinkUrl,
+
+                tradeId = item.tradeId,
+                tradeParentId = item.tradeParentId,
+
+                create_time = summary.create_time,
+                click_time = item.clickTime,
+                paid_time = item.tbPaidTime,
+
+                exp_time = expTime,
+                click_num = click_num,
+            };
+            if (exist == null)
+            {
+                using var conn = DBContext.GetOpenConnection();
+                conn.Insert(data);
+            }
+            //RemoveLinkSummary(item.accountId, item.itemId, item.mktId, item.itemTitle);
+            await RemoveLinkSummaryAsync(item.accountId, item.itemId, item.mktId, item.itemTitle);
+            return true;
+        }
+    }
+}

+ 236 - 236
molilian.core/Core/taoke/RiskControlCore.cs

@@ -1,236 +1,236 @@
-using Microsoft.AspNetCore.Http;
-using Microsoft.AspNetCore.Mvc.Controllers;
-using Microsoft.AspNetCore.Mvc.Filters;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using dodohold.core;
-using System.Threading.Channels;
-using YunhuiKit;
-
-
-namespace molilian.core
-{
-    public partial class RiskControlCore
-    {
-        private static Dictionary<string, int> _calls = new();
-        private static Dictionary<string, decimal> _incomeAmt = new();
-
-        public static void Refresh()
-        {
-            _incomeAmt = [];
-        }
-        public static void ResetCalls()
-        {
-            _calls = [];
-        }
-        internal static async Task CallsIncrByAsync(TkChannelEnum channel, int accountId)
-        {
-            await CallsIncrByAsync(channel, accountId, DateTime.Now.ToString("yyyyMMddHHmm"), 300);
-            await CallsIncrByAsync(channel, accountId, DateTime.Now.ToString("yyyyMMddHH"), 14400);
-            await CallsIncrByAsync(channel, accountId, DateTime.Now.ToString("yyyyMMdd"));
-        }
-
-        internal static async Task CallsIncrByAsync(TkChannelEnum channel, int accountId, string flag, int expire = 259200)
-        {
-            string key = $"{channel}:{accountId}:{flag}";
-            if (_calls.TryGetValue(key, out int value))
-            {
-                _calls[key] = ++value;
-            }
-            else
-            {
-                _calls[key] = 1;
-            }
-            string cache_key = $":RiskControl:calls:{key}";
-            await RedisKit.IncrByAsync(cache_key);
-            await RedisKit.ExpireAsync(cache_key, expire);
-
-        }
-
-        internal static async Task SetCallsAsync(TkChannelEnum channel, int accountId, string flag, int val)
-        {
-            string key = $"{channel}:{accountId}:{flag}";
-            _calls.TryAdd(key, val);
-            string cache_key = $":RiskControl:calls:{key}";
-            await RedisHelper.SetAsync(cache_key, val);
-            await RedisHelper.ExpireAsync(cache_key, 3 * 86400);
-        }
-        public static async Task<int> GetAllNodesCallsAsync(TkChannelEnum channel, int accountId, string flag)
-        {
-            var key = $"{channel}:{accountId}:{flag}";
-            try
-            {
-                var tasks = EndPointCore.List()
-                    .Where(node => node.is_public_api && !string.IsNullOrEmpty(node.redis_server))
-                    .Select(async node =>
-                    {
-                        try
-                        {
-                            var redisServer = EndPointCore.GetRedisServer(node);
-                            if (string.IsNullOrEmpty(redisServer)) return 0;
-
-                            var cacheKey = $":RiskControl:calls:{key}";
-                            using var scope = await RedisClientFactory.CreateScopeAsync(redisServer);
-                            int count = await scope.Client.GetAsync<int>(cacheKey);
-                            return count;
-                        }
-                        catch (Exception ex) { }
-                        return 0;
-                    });
-
-                var results = await Task.WhenAll(tasks);
-                var totalCalls = results.Sum();
-                _calls.TryAdd(key, totalCalls);
-                return totalCalls;
-            }
-            catch (Exception)
-            {
-                // TODO: 添加日志
-                return 0;
-            }
-        }
-
-
-        internal static async Task SetTkEndpointCallsAsync(int accountId, int ep_id, string flag, int val)
-        {
-            string key = $"tk_endpoint:{accountId}:{ep_id}:{flag}";
-            if (_calls.ContainsKey(key))
-            {
-                _calls[key] = val;
-            }
-            else
-            {
-                _calls[key] = val;
-            }
-            string cache_key = $":RiskControl:calls:{key}";
-            await RedisHelper.IncrByAsync(cache_key);
-            await RedisHelper.ExpireAsync(cache_key, 3 * 86400);
-        }
-
-
-        public static async Task<int> GetAllNodesTkEndpointCallsAsync(int accountId, int ep_id, string flag)
-        {
-            string key = $"tk_endpoint:{accountId}:{ep_id}:{flag}";
-            try
-            {
-                var tasks = EndPointCore.List()
-                    .Where(node => node.is_public_api && !string.IsNullOrEmpty(node.redis_server))
-                    .Select(async node =>
-                    {
-                        try
-                        {
-                            var redisServer = EndPointCore.GetRedisServer(node);
-                            if (string.IsNullOrEmpty(redisServer)) return 0;
-
-                            string cacheKey = $":RiskControl:calls:{key}";
-                            using var scope = await RedisClientFactory.CreateScopeAsync(redisServer);
-                            return await scope.Client.GetAsync<int>(cacheKey);
-                        }
-                        catch (Exception ex) { }
-                        return 0;
-                    });
-
-                var results = await Task.WhenAll(tasks);
-                var totalCalls = results.Sum();
-                _calls.TryAdd(key, totalCalls);
-                return totalCalls;
-            }
-            catch (Exception)
-            {
-                // TODO: 添加日志
-                return 0;
-            }
-        }
-
-        public static int GetCalls(TkChannelEnum channel, int accountId, string flag)
-        {
-            try
-            {
-                string key = $"{channel}:{accountId}:{flag}";
-                if (_calls.ContainsKey(key)) return _calls[key];
-
-                string cache_key = $":RiskControl:calls:{key}";
-                int num = RedisHelper.Get<int>(cache_key);
-                _calls.TryAdd(key, num);
-                return num;
-            }
-            catch (Exception ex) { return 0; }
-        }
-
-        internal static async Task SaveIncomeAmtAsync(TkChannelEnum channel, string accountName, decimal income_amt)
-        {
-            string key = $"{channel}:{accountName}:{DateTime.Now:yyyyMMdd}";
-            lock (_incomeAmt)
-            {
-                if (!_incomeAmt.TryAdd(key, income_amt))
-                    _incomeAmt[key] = income_amt;
-            }
-            string cache_key = $":RiskControl:{key}:income_amt";
-            try
-            {
-                var tasks = EndPointCore.List()
-                    .Where(node => node.is_public_api && !string.IsNullOrEmpty(node.redis_server))
-                    .Select(async node =>
-                    {
-                        try
-                        {
-                            var redisServer = EndPointCore.GetRedisServer(node);
-                            if (string.IsNullOrEmpty(redisServer)) return false;
-
-                            using var scope = await RedisClientFactory.CreateScopeAsync(redisServer);
-                            return await scope.Client.SetAsync(cache_key, income_amt, 3 * 86400);
-                        }
-                        catch (Exception ex)
-                        {
-                            return false;
-                        }
-                    });
-
-                await Task.WhenAll(tasks);
-            }
-            catch (Exception)
-            {
-            }
-        }
-
-
-
-        public static decimal GetIncomeAmt(TkChannelEnum channel, string accountName)
-        {
-            try
-            {
-                string key = $"{channel}:{accountName}:{DateTime.Now:yyyyMMdd}";
-                if (_incomeAmt.ContainsKey(key)) return _incomeAmt[key];
-
-                string cache_key = $":RiskControl:{key}:income_amt";
-                return RedisHelper.Get<decimal>(cache_key);
-            }
-            catch (Exception ex) { return 0; }
-        }
-
-
-        public static string GetRiskCookie(string accountName)
-        {
-            try
-            {
-                string cache_key = $":RiskControl:riskCookie:{accountName}";
-                return RedisHelper.Get<string>(cache_key);
-            }
-            catch (Exception ex) { return null; }
-        }
-
-        public static bool SetRiskCookie(string accountName, string flagVal)
-        {
-            try
-            {
-                string cache_key = $":RiskControl:riskCookie:{accountName}";
-                return RedisHelper.Set(cache_key, flagVal);
-            }
-            catch (Exception ex) { return false; }
-        }
-
-    }
-
-}
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc.Controllers;
+using Microsoft.AspNetCore.Mvc.Filters;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using dodohold.core;
+using System.Threading.Channels;
+using YunhuiKit;
+
+
+namespace molilian.core
+{
+    public partial class RiskControlCore
+    {
+        private static Dictionary<string, int> _calls = new();
+        private static Dictionary<string, decimal> _incomeAmt = new();
+
+        public static void Refresh()
+        {
+            _incomeAmt = [];
+        }
+        public static void ResetCalls()
+        {
+            _calls = [];
+        }
+        internal static async Task CallsIncrByAsync(TkChannelEnum channel, int accountId)
+        {
+            await CallsIncrByAsync(channel, accountId, DateTime.Now.ToString("yyyyMMddHHmm"), 300);
+            await CallsIncrByAsync(channel, accountId, DateTime.Now.ToString("yyyyMMddHH"), 14400);
+            await CallsIncrByAsync(channel, accountId, DateTime.Now.ToString("yyyyMMdd"));
+        }
+
+        internal static async Task CallsIncrByAsync(TkChannelEnum channel, int accountId, string flag, int expire = 259200)
+        {
+            string key = $"{channel}:{accountId}:{flag}";
+            if (_calls.TryGetValue(key, out int value))
+            {
+                _calls[key] = ++value;
+            }
+            else
+            {
+                _calls[key] = 1;
+            }
+            string cache_key = $":RiskControl:calls:{key}";
+            await RedisKit.IncrByAsync(cache_key);
+            await RedisKit.ExpireAsync(cache_key, expire);
+
+        }
+
+        internal static async Task SetCallsAsync(TkChannelEnum channel, int accountId, string flag, int val)
+        {
+            string key = $"{channel}:{accountId}:{flag}";
+            _calls.TryAdd(key, val);
+            string cache_key = $":RiskControl:calls:{key}";
+            await RedisHelper.SetAsync(cache_key, val);
+            await RedisHelper.ExpireAsync(cache_key, 3 * 86400);
+        }
+        public static async Task<int> GetAllNodesCallsAsync(TkChannelEnum channel, int accountId, string flag)
+        {
+            var key = $"{channel}:{accountId}:{flag}";
+            try
+            {
+                var tasks = EndPointCore.List()
+                    .Where(node => node.is_public_api && !string.IsNullOrEmpty(node.redis_server))
+                    .Select(async node =>
+                    {
+                        try
+                        {
+                            var redisServer = EndPointCore.GetRedisServer(node);
+                            if (string.IsNullOrEmpty(redisServer)) return 0;
+
+                            var cacheKey = $":RiskControl:calls:{key}";
+                            using var scope = await RedisClientFactory.CreateScopeAsync(redisServer);
+                            int count = await scope.Client.GetAsync<int>(cacheKey);
+                            return count;
+                        }
+                        catch (Exception ex) { }
+                        return 0;
+                    });
+
+                var results = await Task.WhenAll(tasks);
+                var totalCalls = results.Sum();
+                _calls.TryAdd(key, totalCalls);
+                return totalCalls;
+            }
+            catch (Exception)
+            {
+                // TODO: 添加日志
+                return 0;
+            }
+        }
+
+
+        internal static async Task SetTkEndpointCallsAsync(int accountId, int ep_id, string flag, int val)
+        {
+            string key = $"tk_endpoint:{accountId}:{ep_id}:{flag}";
+            if (_calls.ContainsKey(key))
+            {
+                _calls[key] = val;
+            }
+            else
+            {
+                _calls[key] = val;
+            }
+            string cache_key = $":RiskControl:calls:{key}";
+            await RedisHelper.IncrByAsync(cache_key);
+            await RedisHelper.ExpireAsync(cache_key, 3 * 86400);
+        }
+
+
+        public static async Task<int> GetAllNodesTkEndpointCallsAsync(int accountId, int ep_id, string flag)
+        {
+            string key = $"tk_endpoint:{accountId}:{ep_id}:{flag}";
+            try
+            {
+                var tasks = EndPointCore.List()
+                    .Where(node => node.is_public_api && !string.IsNullOrEmpty(node.redis_server))
+                    .Select(async node =>
+                    {
+                        try
+                        {
+                            var redisServer = EndPointCore.GetRedisServer(node);
+                            if (string.IsNullOrEmpty(redisServer)) return 0;
+
+                            string cacheKey = $":RiskControl:calls:{key}";
+                            using var scope = await RedisClientFactory.CreateScopeAsync(redisServer);
+                            return await scope.Client.GetAsync<int>(cacheKey);
+                        }
+                        catch (Exception ex) { }
+                        return 0;
+                    });
+
+                var results = await Task.WhenAll(tasks);
+                var totalCalls = results.Sum();
+                _calls.TryAdd(key, totalCalls);
+                return totalCalls;
+            }
+            catch (Exception)
+            {
+                // TODO: 添加日志
+                return 0;
+            }
+        }
+
+        public static int GetCalls(TkChannelEnum channel, int accountId, string flag)
+        {
+            try
+            {
+                string key = $"{channel}:{accountId}:{flag}";
+                if (_calls.ContainsKey(key)) return _calls[key];
+
+                string cache_key = $":RiskControl:calls:{key}";
+                int num = RedisHelper.Get<int>(cache_key);
+                _calls.TryAdd(key, num);
+                return num;
+            }
+            catch (Exception ex) { return 0; }
+        }
+
+        internal static async Task SaveIncomeAmtAsync(TkChannelEnum channel, string accountName, decimal income_amt)
+        {
+            string key = $"{channel}:{accountName}:{DateTime.Now:yyyyMMdd}";
+            lock (_incomeAmt)
+            {
+                if (!_incomeAmt.TryAdd(key, income_amt))
+                    _incomeAmt[key] = income_amt;
+            }
+            string cache_key = $":RiskControl:{key}:income_amt";
+            try
+            {
+                var tasks = EndPointCore.List()
+                    .Where(node => node.is_public_api && !string.IsNullOrEmpty(node.redis_server))
+                    .Select(async node =>
+                    {
+                        try
+                        {
+                            var redisServer = EndPointCore.GetRedisServer(node);
+                            if (string.IsNullOrEmpty(redisServer)) return false;
+
+                            using var scope = await RedisClientFactory.CreateScopeAsync(redisServer);
+                            return await scope.Client.SetAsync(cache_key, income_amt, 3 * 86400);
+                        }
+                        catch (Exception ex)
+                        {
+                            return false;
+                        }
+                    });
+
+                await Task.WhenAll(tasks);
+            }
+            catch (Exception)
+            {
+            }
+        }
+
+
+
+        public static decimal GetIncomeAmt(TkChannelEnum channel, string accountName)
+        {
+            try
+            {
+                string key = $"{channel}:{accountName}:{DateTime.Now:yyyyMMdd}";
+                if (_incomeAmt.ContainsKey(key)) return _incomeAmt[key];
+
+                string cache_key = $":RiskControl:{key}:income_amt";
+                return RedisHelper.Get<decimal>(cache_key);
+            }
+            catch (Exception ex) { return 0; }
+        }
+
+
+        public static string GetRiskCookie(string accountName)
+        {
+            try
+            {
+                string cache_key = $":RiskControl:riskCookie:{accountName}";
+                return RedisHelper.Get<string>(cache_key);
+            }
+            catch (Exception ex) { return null; }
+        }
+
+        public static bool SetRiskCookie(string accountName, string flagVal)
+        {
+            try
+            {
+                string cache_key = $":RiskControl:riskCookie:{accountName}";
+                return RedisHelper.Set(cache_key, flagVal);
+            }
+            catch (Exception ex) { return false; }
+        }
+
+    }
+
+}

+ 372 - 372
molilian.core/Core/taoke/TaokeOpenCore.cs

@@ -1,372 +1,372 @@
-using dodohold.core;
-using Google.Protobuf.WellKnownTypes;
-using System.Text.Json;
-using System.Threading;
-using Top.Api.Response;
-using static dodohold.core.ZTOExpress.CreateOrderArgs;
-
-
-namespace molilian.core
-{
-    public partial class TaokeOpenCore
-    {
-        static string _test_base64String = string.Empty;
-        public TkConfigDTO _config;
-
-        private static string _end_point;
-        static TaokeOpenCore()
-        {
-            _end_point = Environment.GetEnvironmentVariable("EndPoint");
-        }
-
-        public TaokeOpenCore()
-        {
-            _config = TkConfigCore.Get();
-        }
-
-
-        public async Task<APIResult> PromotionQueryAsync(string img, string oaid = "", string ip = "", string scene = "", bool recommand = true, bool debug = false, int aid = 0)
-        {
-            PromotionQueryDTO result = new()
-            {
-                end_point = _end_point,
-                ip = ip,
-                oaid = oaid,
-                scene = scene,
-                img = img
-            };
-            if (string.IsNullOrEmpty(oaid))
-            {
-                result.success = false;
-                result.message = "调用失败";
-                result.reason = "无效OAID";
-                _ = TkLogCore.PromotionImgLogAsync(result);
-                return new APIResult(new
-                {
-                    result.success,
-                    result.message,
-                    result.reason,
-                }, APIResultCodeEnum.OK, new JsonSerializerOptions
-                {
-                    Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping
-                });
-            }
-
-            if (string.IsNullOrEmpty(img))
-            {
-                result.success = false;
-                result.message = "调用失败";
-                result.reason = "无效图片";
-                _ = TkLogCore.PromotionImgLogAsync(result);
-                return new APIResult(new
-                {
-                    result.success,
-                    result.message,
-                    result.reason,
-                }, APIResultCodeEnum.OK, new JsonSerializerOptions
-                {
-                    Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping
-                });
-            }
-            try
-            {
-                TkPoolDTO account;
-                if (aid == 0)
-                {
-                    account = await TkPoolCore.GetOneAsync(TkPoolCore.TkAction.promotionQuery, false, string.Empty, 0);
-                }
-                else
-                {
-                    account = await TkPoolCore.GetOneAsync(aid);
-                }
-                if (account == null)
-                {
-                    result.success = false;
-                    result.message = "调用失败";
-                    result.reason = "没有匹配账号";
-                    _ = TkLogCore.PromotionImgLogAsync(result);
-                    return new APIResult(new
-                    {
-                        result.success,
-                        result.message,
-                        result.reason,
-                    }, APIResultCodeEnum.OK, new JsonSerializerOptions
-                    {
-                        Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping
-                    });
-                }
-
-                string nodeName = ProxyNodesCore.GetSelectOne(account.nodeName);
-                var proxy = ProxyNodesCore.GetOne(nodeName);
-                TaobaoOpenPlus taobao = new(account.appkey, account.appSecret, account.open_pid, debug, proxy);
-                int timeout = _config.similar_timeout;
-#if DEBUG
-                timeout = 50000;
-#endif
-
-                result.accountId = account.id;
-                result.accountName = account.name;
-
-                result.proxy_nodename = nodeName;
-                result.proxy_node = proxy?.Address?.Host;
-
-                using var cts = new CancellationTokenSource();
-                cts.CancelAfter(timeout);
-
-                DateTime stime = DateTime.Now;
-
-#if DEBUG
-                account.api_id = 17;
-                account.api = "aliyun";
-                account.refpid = "mm_6695915495_3161300068_115818650332";
-#endif
-
-                switch (account.api)
-                {
-                    case "veapi":
-                    case "ddx":
-                    case "other_api":
-                        if (account.api_id > 0)
-                        {
-                            result.proxy_nodename = null;
-                            result.proxy_node = null;
-
-                            var api = OtherApiPoolCore.Get(account.api_id);
-                            var core = new OtherApiPlus(account.name, api.key, api.sessionKey, account.api, api.api_url);
-                            core.picSimilaritem(img, ref result);
-
-                            var ts2 = DateTime.Now - stime;
-                            result.elapsedTime2 = (int)ts2.TotalMilliseconds;
-
-                            _ = TkLogCore.PromotionImgLogAsync(result);
-                            return new APIResult(new
-                            {
-                                result.success,
-                                result.message,
-                                result.reason,
-                                promotionImg = result.PromotionImg,
-                                exposeTracks = "https://api.molilian.com/tracks/track?track_id=7&unique_id=",
-                                clickTracks = "https://api.molilian.com/tracks/track?track_id=8&unique_id=",
-                            }, APIResultCodeEnum.OK, new JsonSerializerOptions
-                            {
-                                Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping
-                            });
-                        }
-                        break;
-                    case "aliyun":
-                        {
-                            result.proxy_nodename = null;
-                            result.proxy_node = null;
-
-                            AliyunCore api;
-                            if (account.api_id == 0)
-                            {
-                                api = new AliyunCore(account.appkey, account.appSecret);
-                            }
-                            else
-                            {
-                                api = new AliyunCore(account.api_id);
-                            }
-
-                            var aliDelayTask = Task.Delay(1500, cts.Token);
-                            var aliRequestTask = api.picSimilaritemAsync(img, account.open_pid, result);
-
-                            var aliCompletedTask = await Task.WhenAny(aliDelayTask, aliRequestTask);
-                            if (aliCompletedTask == aliRequestTask)
-                            {
-                                result = await aliRequestTask;
-                            }
-                            else
-                            {
-                                result.success = false;
-                                result.message = "调用失败";
-                                result.reason = "请求超时";
-                            }
-
-                            var ts2 = DateTime.Now - stime;
-                            result.elapsedTime2 = (int)ts2.TotalMilliseconds;
-
-                            _ = TkLogCore.PromotionImgLogAsync(result);
-                            return new APIResult(new
-                            {
-                                result.success,
-                                result.message,
-                                result.reason,
-                                promotionImg = result.PromotionImg,
-                                exposeTracks = $"https://api.molilian.com/tracks/track?track_id=7&unique_id=",
-                                clickTracks = $"https://api.molilian.com/tracks/track?track_id=8&unique_id="
-                            }, APIResultCodeEnum.OK, new JsonSerializerOptions
-                            {
-                                Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping
-                            });
-                        }
-                }
-
-                var apiDelayTask = Task.Delay(500, cts.Token);
-                var apiRequestTask = Task.Run(() =>
-                {
-                    return taobao.PromotionQueryRequest(img, oaid);
-
-                }, cts.Token);
-
-                TbkThorMfrsPromotionQueryResponse data;
-                var apiCompletedTask = await Task.WhenAny(apiRequestTask, apiDelayTask);
-                if (apiCompletedTask == apiRequestTask)
-                {
-                    data = await apiRequestTask;
-                    if (data.IsError)
-                    {
-                        result.success = false;
-                        result.message = "调用失败";
-                        result.reason = data.ErrMsg;
-                        _ = TkLogCore.PromotionImgLogAsync(result);
-                        return new APIResult(new
-                        {
-                            result.success,
-                            result.message,
-                            result.reason,
-                        }, APIResultCodeEnum.OK, new JsonSerializerOptions
-                        {
-                            Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping
-                        });
-                    }
-                    if (!"0".Equals(data.Status))
-                    {
-                        result.success = false;
-                        result.message = "调用失败";
-                        result.reason = data.Desc;
-                        _ = TkLogCore.PromotionImgLogAsync(result);
-                        return new APIResult(new
-                        {
-                            result.success,
-                            result.message,
-                            result.reason,
-                        }, APIResultCodeEnum.OK, new JsonSerializerOptions
-                        {
-                            Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping
-                        });
-                    }
-                }
-                else
-                {
-
-                }
-
-
-                //500ms后再提交一次
-                _ = Task.Run(() =>
-                {
-                    return taobao.PromotionQueryRequest(img, oaid);
-                });
-
-
-                var ts = DateTime.Now - stime;
-                result.elapsedTime2 = (int)ts.TotalMilliseconds;
-
-
-                //result.url = data.Url;
-                result.url = $"https://mo.m.taobao.com/etao/search_pic_detail?appKey={account.appkey}&oaid={oaid}&pid={account.open_pid}";
-                string cookies = string.Empty;
-                var requestTask = Task.Run(async () =>
-                {
-                    if (recommand)
-                    {
-                        //DateTime stime = DateTime.Now;
-                        //(result.PromotionImg, cookies) = await taobao.GetRecommendData(TaobaoOpenPlus.RecommendRequestType.ori, oaid, cookies, cts.Token);
-                        //var ts = DateTime.Now - stime;
-                        //result.elapsedTime4 = (int)ts.TotalMilliseconds;
-
-                        var similarPromotionTask = taobao.GetRecommendDataWithRetry(TaobaoOpenPlus.RecommendRequestType.first, oaid, cookies, cts.Token);
-                        var promotionImgTask = taobao.GetRecommendDataWithRetry(TaobaoOpenPlus.RecommendRequestType.ori, oaid, cookies, cts.Token);
-
-                        await Task.WhenAll(similarPromotionTask, promotionImgTask);
-
-                        (result.SimilarPromotion, cookies, result.elapsedTime3) = await similarPromotionTask;
-                        (result.PromotionImg, cookies, result.elapsedTime4) = await promotionImgTask;
-                    }
-                    else
-                    {
-                        result.elapsedTime3 = 1;
-                        (result.PromotionImg, cookies, result.elapsedTime4) = await taobao.GetRecommendDataWithRetry(TaobaoOpenPlus.RecommendRequestType.ori, oaid, cookies, cts.Token);
-                    }
-                    result.similarPromotion = result.SimilarPromotion.Convert2Json();
-                    result.promotionImg = result.PromotionImg.Convert2Json();
-                    return result;
-
-
-                }, cts.Token);
-
-                var delayTask = Task.Delay(timeout, cts.Token);
-                var completedTask = await Task.WhenAny(requestTask, delayTask);
-                if (completedTask == requestTask)
-                {
-                    result = await requestTask;
-                }
-                else
-                {
-                    //============================== 放弃转链-请求超时 ==============================
-                    cts.Cancel();
-                    result.success = false;
-                    result.message = "调用失败";
-                    result.reason = "请求超时";
-                    if (!recommand) result.elapsedTime3 = 1;
-
-                    _ = TkLogCore.PromotionImgLogAsync(result);
-                    return new APIResult(new
-                    {
-                        result.success,
-                        result.message,
-                        result.reason,
-                    }, APIResultCodeEnum.OK, new JsonSerializerOptions
-                    {
-                        Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping
-                    });
-                }
-
-
-                //(result.SimilarPromotion, cookies) = await taobao.GetRecommendData(TaobaoOpenPlus.RecommendRequestType.first, oaid, cookies);
-                //(result.PromotionImg, cookies) = await taobao.GetRecommendData(TaobaoOpenPlus.RecommendRequestType.ori, oaid, cookies);
-                //result.similarPromotion = result.SimilarPromotion.Convert2Json();
-                //result.promotionImg = result.PromotionImg.Convert2Json();
-
-                result.success = true;
-                result.message = "调用成功";
-                _ = TkLogCore.PromotionImgLogAsync(result);
-                return new APIResult(new
-                {
-                    result.success,
-                    result.message,
-                    result.reason,
-                    similarPromotion = result.SimilarPromotion,
-                    promotionImg = result.PromotionImg,
-                    result.url,
-                    exposeTracks = "https://api.molilian.com/tracks/track?track_id=7&unique_id=",
-                    clickTracks = "https://api.molilian.com/tracks/track?track_id=8&unique_id=",
-                }, APIResultCodeEnum.OK, new JsonSerializerOptions
-                {
-                    Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping
-                });
-
-            }
-            catch (Exception ex)
-            {
-                result.success = false;
-                result.message = "调用失败";
-                result.reason = ex.Message;
-                _ = TkLogCore.PromotionImgLogAsync(result);
-                return new APIResult(new
-                {
-                    result.success,
-                    result.message,
-                    result.reason,
-                }, APIResultCodeEnum.OK, new JsonSerializerOptions
-                {
-                    Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping
-                });
-            }
-
-        }
-
-    }
-
-}
+using dodohold.core;
+using Google.Protobuf.WellKnownTypes;
+using System.Text.Json;
+using System.Threading;
+using Top.Api.Response;
+using static dodohold.core.ZTOExpress.CreateOrderArgs;
+
+
+namespace molilian.core
+{
+    public partial class TaokeOpenCore
+    {
+        static string _test_base64String = string.Empty;
+        public TkConfigDTO _config;
+
+        private static string _end_point;
+        static TaokeOpenCore()
+        {
+            _end_point = Environment.GetEnvironmentVariable("EndPoint");
+        }
+
+        public TaokeOpenCore()
+        {
+            _config = TkConfigCore.Get();
+        }
+
+
+        public async Task<APIResult> PromotionQueryAsync(string img, string oaid = "", string ip = "", string scene = "", bool recommand = true, bool debug = false, int aid = 0)
+        {
+            PromotionQueryDTO result = new()
+            {
+                end_point = _end_point,
+                ip = ip,
+                oaid = oaid,
+                scene = scene,
+                img = img
+            };
+            if (string.IsNullOrEmpty(oaid))
+            {
+                result.success = false;
+                result.message = "调用失败";
+                result.reason = "无效OAID";
+                _ = TkLogCore.PromotionImgLogAsync(result);
+                return new APIResult(new
+                {
+                    result.success,
+                    result.message,
+                    result.reason,
+                }, APIResultCodeEnum.OK, new JsonSerializerOptions
+                {
+                    Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping
+                });
+            }
+
+            if (string.IsNullOrEmpty(img))
+            {
+                result.success = false;
+                result.message = "调用失败";
+                result.reason = "无效图片";
+                _ = TkLogCore.PromotionImgLogAsync(result);
+                return new APIResult(new
+                {
+                    result.success,
+                    result.message,
+                    result.reason,
+                }, APIResultCodeEnum.OK, new JsonSerializerOptions
+                {
+                    Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping
+                });
+            }
+            try
+            {
+                TkPoolDTO account;
+                if (aid == 0)
+                {
+                    account = await TkPoolCore.GetOneAsync(TkPoolCore.TkAction.promotionQuery, false, string.Empty, 0);
+                }
+                else
+                {
+                    account = await TkPoolCore.GetOneAsync(aid);
+                }
+                if (account == null)
+                {
+                    result.success = false;
+                    result.message = "调用失败";
+                    result.reason = "没有匹配账号";
+                    _ = TkLogCore.PromotionImgLogAsync(result);
+                    return new APIResult(new
+                    {
+                        result.success,
+                        result.message,
+                        result.reason,
+                    }, APIResultCodeEnum.OK, new JsonSerializerOptions
+                    {
+                        Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping
+                    });
+                }
+
+                string nodeName = ProxyNodesCore.GetSelectOne(account.nodeName);
+                var proxy = ProxyNodesCore.GetOne(nodeName);
+                TaobaoOpenPlus taobao = new(account.appkey, account.appSecret, account.open_pid, debug, proxy);
+                int timeout = _config.similar_timeout;
+#if DEBUG
+                timeout = 50000;
+#endif
+
+                result.accountId = account.id;
+                result.accountName = account.name;
+
+                result.proxy_nodename = nodeName;
+                result.proxy_node = proxy?.Address?.Host;
+
+                using var cts = new CancellationTokenSource();
+                cts.CancelAfter(timeout);
+
+                DateTime stime = DateTime.Now;
+
+#if DEBUG
+                account.api_id = 17;
+                account.api = "aliyun";
+                account.refpid = "mm_6695915495_3161300068_115818650332";
+#endif
+
+                switch (account.api)
+                {
+                    case "veapi":
+                    case "ddx":
+                    case "other_api":
+                        if (account.api_id > 0)
+                        {
+                            result.proxy_nodename = null;
+                            result.proxy_node = null;
+
+                            var api = OtherApiPoolCore.Get(account.api_id);
+                            var core = new OtherApiPlus(account.name, api.key, api.sessionKey, account.api, api.api_url);
+                            core.picSimilaritem(img, ref result);
+
+                            var ts2 = DateTime.Now - stime;
+                            result.elapsedTime2 = (int)ts2.TotalMilliseconds;
+
+                            _ = TkLogCore.PromotionImgLogAsync(result);
+                            return new APIResult(new
+                            {
+                                result.success,
+                                result.message,
+                                result.reason,
+                                promotionImg = result.PromotionImg,
+                                exposeTracks = "https://api.molilian.com/tracks/track?track_id=7&unique_id=",
+                                clickTracks = "https://api.molilian.com/tracks/track?track_id=8&unique_id=",
+                            }, APIResultCodeEnum.OK, new JsonSerializerOptions
+                            {
+                                Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping
+                            });
+                        }
+                        break;
+                    case "aliyun":
+                        {
+                            result.proxy_nodename = null;
+                            result.proxy_node = null;
+
+                            AliyunCore api;
+                            if (account.api_id == 0)
+                            {
+                                api = new AliyunCore(account.appkey, account.appSecret);
+                            }
+                            else
+                            {
+                                api = new AliyunCore(account.api_id);
+                            }
+
+                            var aliDelayTask = Task.Delay(1500, cts.Token);
+                            var aliRequestTask = api.picSimilaritemAsync(img, account.open_pid, result);
+
+                            var aliCompletedTask = await Task.WhenAny(aliDelayTask, aliRequestTask);
+                            if (aliCompletedTask == aliRequestTask)
+                            {
+                                result = await aliRequestTask;
+                            }
+                            else
+                            {
+                                result.success = false;
+                                result.message = "调用失败";
+                                result.reason = "请求超时";
+                            }
+
+                            var ts2 = DateTime.Now - stime;
+                            result.elapsedTime2 = (int)ts2.TotalMilliseconds;
+
+                            _ = TkLogCore.PromotionImgLogAsync(result);
+                            return new APIResult(new
+                            {
+                                result.success,
+                                result.message,
+                                result.reason,
+                                promotionImg = result.PromotionImg,
+                                exposeTracks = $"https://api.molilian.com/tracks/track?track_id=7&unique_id=",
+                                clickTracks = $"https://api.molilian.com/tracks/track?track_id=8&unique_id="
+                            }, APIResultCodeEnum.OK, new JsonSerializerOptions
+                            {
+                                Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping
+                            });
+                        }
+                }
+
+                var apiDelayTask = Task.Delay(500, cts.Token);
+                var apiRequestTask = Task.Run(() =>
+                {
+                    return taobao.PromotionQueryRequest(img, oaid);
+
+                }, cts.Token);
+
+                TbkThorMfrsPromotionQueryResponse data;
+                var apiCompletedTask = await Task.WhenAny(apiRequestTask, apiDelayTask);
+                if (apiCompletedTask == apiRequestTask)
+                {
+                    data = await apiRequestTask;
+                    if (data.IsError)
+                    {
+                        result.success = false;
+                        result.message = "调用失败";
+                        result.reason = data.ErrMsg;
+                        _ = TkLogCore.PromotionImgLogAsync(result);
+                        return new APIResult(new
+                        {
+                            result.success,
+                            result.message,
+                            result.reason,
+                        }, APIResultCodeEnum.OK, new JsonSerializerOptions
+                        {
+                            Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping
+                        });
+                    }
+                    if (!"0".Equals(data.Status))
+                    {
+                        result.success = false;
+                        result.message = "调用失败";
+                        result.reason = data.Desc;
+                        _ = TkLogCore.PromotionImgLogAsync(result);
+                        return new APIResult(new
+                        {
+                            result.success,
+                            result.message,
+                            result.reason,
+                        }, APIResultCodeEnum.OK, new JsonSerializerOptions
+                        {
+                            Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping
+                        });
+                    }
+                }
+                else
+                {
+
+                }
+
+
+                //500ms后再提交一次
+                _ = Task.Run(() =>
+                {
+                    return taobao.PromotionQueryRequest(img, oaid);
+                });
+
+
+                var ts = DateTime.Now - stime;
+                result.elapsedTime2 = (int)ts.TotalMilliseconds;
+
+
+                //result.url = data.Url;
+                result.url = $"https://mo.m.taobao.com/etao/search_pic_detail?appKey={account.appkey}&oaid={oaid}&pid={account.open_pid}";
+                string cookies = string.Empty;
+                var requestTask = Task.Run(async () =>
+                {
+                    if (recommand)
+                    {
+                        //DateTime stime = DateTime.Now;
+                        //(result.PromotionImg, cookies) = await taobao.GetRecommendData(TaobaoOpenPlus.RecommendRequestType.ori, oaid, cookies, cts.Token);
+                        //var ts = DateTime.Now - stime;
+                        //result.elapsedTime4 = (int)ts.TotalMilliseconds;
+
+                        var similarPromotionTask = taobao.GetRecommendDataWithRetry(TaobaoOpenPlus.RecommendRequestType.first, oaid, cookies, cts.Token);
+                        var promotionImgTask = taobao.GetRecommendDataWithRetry(TaobaoOpenPlus.RecommendRequestType.ori, oaid, cookies, cts.Token);
+
+                        await Task.WhenAll(similarPromotionTask, promotionImgTask);
+
+                        (result.SimilarPromotion, cookies, result.elapsedTime3) = await similarPromotionTask;
+                        (result.PromotionImg, cookies, result.elapsedTime4) = await promotionImgTask;
+                    }
+                    else
+                    {
+                        result.elapsedTime3 = 1;
+                        (result.PromotionImg, cookies, result.elapsedTime4) = await taobao.GetRecommendDataWithRetry(TaobaoOpenPlus.RecommendRequestType.ori, oaid, cookies, cts.Token);
+                    }
+                    result.similarPromotion = result.SimilarPromotion.Convert2Json();
+                    result.promotionImg = result.PromotionImg.Convert2Json();
+                    return result;
+
+
+                }, cts.Token);
+
+                var delayTask = Task.Delay(timeout, cts.Token);
+                var completedTask = await Task.WhenAny(requestTask, delayTask);
+                if (completedTask == requestTask)
+                {
+                    result = await requestTask;
+                }
+                else
+                {
+                    //============================== 放弃转链-请求超时 ==============================
+                    cts.Cancel();
+                    result.success = false;
+                    result.message = "调用失败";
+                    result.reason = "请求超时";
+                    if (!recommand) result.elapsedTime3 = 1;
+
+                    _ = TkLogCore.PromotionImgLogAsync(result);
+                    return new APIResult(new
+                    {
+                        result.success,
+                        result.message,
+                        result.reason,
+                    }, APIResultCodeEnum.OK, new JsonSerializerOptions
+                    {
+                        Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping
+                    });
+                }
+
+
+                //(result.SimilarPromotion, cookies) = await taobao.GetRecommendData(TaobaoOpenPlus.RecommendRequestType.first, oaid, cookies);
+                //(result.PromotionImg, cookies) = await taobao.GetRecommendData(TaobaoOpenPlus.RecommendRequestType.ori, oaid, cookies);
+                //result.similarPromotion = result.SimilarPromotion.Convert2Json();
+                //result.promotionImg = result.PromotionImg.Convert2Json();
+
+                result.success = true;
+                result.message = "调用成功";
+                _ = TkLogCore.PromotionImgLogAsync(result);
+                return new APIResult(new
+                {
+                    result.success,
+                    result.message,
+                    result.reason,
+                    similarPromotion = result.SimilarPromotion,
+                    promotionImg = result.PromotionImg,
+                    result.url,
+                    exposeTracks = "https://api.molilian.com/tracks/track?track_id=7&unique_id=",
+                    clickTracks = "https://api.molilian.com/tracks/track?track_id=8&unique_id=",
+                }, APIResultCodeEnum.OK, new JsonSerializerOptions
+                {
+                    Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping
+                });
+
+            }
+            catch (Exception ex)
+            {
+                result.success = false;
+                result.message = "调用失败";
+                result.reason = ex.Message;
+                _ = TkLogCore.PromotionImgLogAsync(result);
+                return new APIResult(new
+                {
+                    result.success,
+                    result.message,
+                    result.reason,
+                }, APIResultCodeEnum.OK, new JsonSerializerOptions
+                {
+                    Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping
+                });
+            }
+
+        }
+
+    }
+
+}

+ 148 - 148
molilian.core/Core/taoke/TkActivityCore.cs

@@ -1,148 +1,148 @@
-using dodohold.core;
-using Microsoft.AspNetCore.Mvc;
-using Spire.Xls.Charts;
-using TencentCloud.Bsca.V20210811.Models;
-
-namespace molilian.core
-{
-    public partial class TkActivityCore
-    {
-
-        private static string _end_point;
-        static TkActivityCore()
-        {
-            _end_point = Environment.GetEnvironmentVariable("EndPoint");
-        }
-
-
-        public static async Task<ActionResult> ParseAsync(string page_id, string prefix, string item_id, string strategy_id, string ip, string oaid)
-        {
-            AlimamaPlus alimama = null;
-
-            TkActivityDTO result = new()
-            {
-                channel = TkChannelEnum.tb,
-                page_id = page_id,
-                raw_item_id = item_id,
-                item_id = $"{prefix}:{item_id}",
-                end_point = _end_point,
-                ip = ip,
-                oaid = oaid
-            };
-
-
-            if (string.IsNullOrEmpty(page_id) || string.IsNullOrEmpty(item_id))
-            {
-                result.success = false;
-                result.message = "放弃转链";
-                result.reason = "无效的参数";
-                _ = TkLogCore.TkActivityLogAsync(result);
-                return new APIResult(new
-                {
-                    result.success,
-                    result.reason,
-                    result.message,
-                    channel = result.channel.ToString(),
-                });
-            }
-
-            try
-            {
-                string reason;
-                if (AlimamaPlus.ShouldIgnoreRequest(ip, oaid, string.Empty, 0, out reason))
-                {
-                    result.success = false;
-                    result.message = "放弃转链";
-                    result.reason = reason;
-                    _ = TkLogCore.TkActivityLogAsync(result);
-                    return new APIResult(new
-                    {
-                        result.success,
-                        result.message,
-                        result.reason,
-                        channel = result.channel.ToString(),
-                    });
-                }
-
-                var account = await TkPoolCore.GetOneAsync(TkPoolCore.TkAction.activity, false, string.Empty, 0, string.Empty, strategy_id);
-                if (account == null)
-                {
-                    result.success = false;
-                    result.message = "放弃转链";
-                    result.reason = "没有匹配账号";
-                    _ = TkLogCore.TkActivityLogAsync(result);
-                    return new APIResult(new
-                    {
-                        result.success,
-                        result.message,
-                        result.reason,
-                        channel = result.channel.ToString(),
-                    });
-                }
-                result.accountId = account.id;
-                result.accountName = account.name;
-                alimama = new AlimamaPlus(account);
-                result = await alimama.TkActivityAsync(result);
-            }
-            catch (Exception ex)
-            {
-                if (ex.Message.Contains("was canceled"))
-                {
-                    result.success = false;
-                    result.message = "放弃转链";
-                    result.reason = "请求超时";
-                }
-                else
-                {
-                    _ = new LoggerLibrary("unionCoupon", "tb_error")
-                        .Info(ip, oaid)
-                        .Info(page_id, result.item_id)
-                        .Info(ex.Message, ex.StackTrace)
-                        .SaveAsync();
-
-                    NotifyCore.Notify(new NifyMessage
-                    {
-                        message = $"【转链异常TB】\n{page_id}\t{result.item_id}\n\n{ex.Message}\n{ex.StackTrace}",
-                        priority = NifyMessagePriority.high,
-                        tags = ["red_circle"]
-                    });
-                    result.success = false;
-                    result.message = "放弃转链";
-                    result.reason = "转链接口异常";
-                }
-            }
-            _ = TkLogCore.TkActivityLogAsync(result);
-            if (!result.success)
-            {
-                return new APIResult(new
-                {
-                    result.success,
-                    result.message,
-                    result.reason,
-                    channel = result?.channel.ToString(),
-                });
-            }
-            return new APIResult(new
-            {
-                result.success,
-                result.message,
-                channel = result?.channel.ToString(),
-                result.deeplink_url,
-                input_item_id = item_id,
-                input_page_id = page_id,
-                result.estimatedCommission,
-                result.maxCommission,
-                result.zkFinalPrice,
-                result.reservePrice,
-                result.title,
-                result.picUrl,
-                result.shortTitle,
-                result.priceAfterCoupon,
-                result.commissionRate,
-                result.couponAmount,
-                exposeTracks = $"https://api.molilian.com/tracks/track?track_id=11&unique_id={item_id}",
-                clickTracks = $"https://api.molilian.com/tracks/track?track_id=12&unique_id={item_id}"
-            });
-        }
-    }
-}
+using dodohold.core;
+using Microsoft.AspNetCore.Mvc;
+using Spire.Xls.Charts;
+using TencentCloud.Bsca.V20210811.Models;
+
+namespace molilian.core
+{
+    public partial class TkActivityCore
+    {
+
+        private static string _end_point;
+        static TkActivityCore()
+        {
+            _end_point = Environment.GetEnvironmentVariable("EndPoint");
+        }
+
+
+        public static async Task<ActionResult> ParseAsync(string page_id, string prefix, string item_id, string strategy_id, string ip, string oaid)
+        {
+            AlimamaPlus alimama = null;
+
+            TkActivityDTO result = new()
+            {
+                channel = TkChannelEnum.tb,
+                page_id = page_id,
+                raw_item_id = item_id,
+                item_id = $"{prefix}:{item_id}",
+                end_point = _end_point,
+                ip = ip,
+                oaid = oaid
+            };
+
+
+            if (string.IsNullOrEmpty(page_id) || string.IsNullOrEmpty(item_id))
+            {
+                result.success = false;
+                result.message = "放弃转链";
+                result.reason = "无效的参数";
+                _ = TkLogCore.TkActivityLogAsync(result);
+                return new APIResult(new
+                {
+                    result.success,
+                    result.reason,
+                    result.message,
+                    channel = result.channel.ToString(),
+                });
+            }
+
+            try
+            {
+                string reason;
+                if (AlimamaPlus.ShouldIgnoreRequest(ip, oaid, string.Empty, 0, out reason))
+                {
+                    result.success = false;
+                    result.message = "放弃转链";
+                    result.reason = reason;
+                    _ = TkLogCore.TkActivityLogAsync(result);
+                    return new APIResult(new
+                    {
+                        result.success,
+                        result.message,
+                        result.reason,
+                        channel = result.channel.ToString(),
+                    });
+                }
+
+                var account = await TkPoolCore.GetOneAsync(TkPoolCore.TkAction.activity, false, string.Empty, 0, string.Empty, strategy_id);
+                if (account == null)
+                {
+                    result.success = false;
+                    result.message = "放弃转链";
+                    result.reason = "没有匹配账号";
+                    _ = TkLogCore.TkActivityLogAsync(result);
+                    return new APIResult(new
+                    {
+                        result.success,
+                        result.message,
+                        result.reason,
+                        channel = result.channel.ToString(),
+                    });
+                }
+                result.accountId = account.id;
+                result.accountName = account.name;
+                alimama = new AlimamaPlus(account);
+                result = await alimama.TkActivityAsync(result);
+            }
+            catch (Exception ex)
+            {
+                if (ex.Message.Contains("was canceled"))
+                {
+                    result.success = false;
+                    result.message = "放弃转链";
+                    result.reason = "请求超时";
+                }
+                else
+                {
+                    _ = new LoggerLibrary("unionCoupon", "tb_error")
+                        .Info(ip, oaid)
+                        .Info(page_id, result.item_id)
+                        .Info(ex.Message, ex.StackTrace)
+                        .SaveAsync();
+
+                    NotifyCore.Notify(new NifyMessage
+                    {
+                        message = $"【转链异常TB】\n{page_id}\t{result.item_id}\n\n{ex.Message}\n{ex.StackTrace}",
+                        priority = NifyMessagePriority.high,
+                        tags = ["red_circle"]
+                    });
+                    result.success = false;
+                    result.message = "放弃转链";
+                    result.reason = "转链接口异常";
+                }
+            }
+            _ = TkLogCore.TkActivityLogAsync(result);
+            if (!result.success)
+            {
+                return new APIResult(new
+                {
+                    result.success,
+                    result.message,
+                    result.reason,
+                    channel = result?.channel.ToString(),
+                });
+            }
+            return new APIResult(new
+            {
+                result.success,
+                result.message,
+                channel = result?.channel.ToString(),
+                result.deeplink_url,
+                input_item_id = item_id,
+                input_page_id = page_id,
+                result.estimatedCommission,
+                result.maxCommission,
+                result.zkFinalPrice,
+                result.reservePrice,
+                result.title,
+                result.picUrl,
+                result.shortTitle,
+                result.priceAfterCoupon,
+                result.commissionRate,
+                result.couponAmount,
+                exposeTracks = $"https://api.molilian.com/tracks/track?track_id=11&unique_id={item_id}",
+                clickTracks = $"https://api.molilian.com/tracks/track?track_id=12&unique_id={item_id}"
+            });
+        }
+    }
+}

+ 152 - 152
molilian.core/Core/taoke/TkEndpointCore.cs

@@ -1,152 +1,152 @@
-using dodohold.core;
-using molilian.core;
-using Org.BouncyCastle.Crypto;
-using System;
-using System.Collections.Concurrent;
-using System.Linq;
-using YunhuiKit;
-using static molilian.core.TkPoolCore;
-
-public partial class TkEndpointCore
-{
-    private static SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
-
-    private static IEnumerable<TkEndpointConfigDTO> _cached;
-
-    // 添加到 TkEndpointCore 类中
-    private static readonly ConcurrentDictionary<(int, bool?), List<TkEndpointConfigDTO>> _accountEndpointCache = new();
-
-
-    private static async Task<IEnumerable<TkEndpointConfigDTO>> ListAsync(bool force = false)
-    {
-        // 内存缓存检查
-        if (!force && _cached != null) return _cached;
-        string cache_key = "cache:tk_endpoint_config";
-
-        if (!force)
-        {
-            var cachedList = await RedisHelper.GetAsync<IEnumerable<TkEndpointConfigDTO>>(cache_key);
-            if (cachedList != null)
-            {
-                _cached = cachedList;
-                return cachedList;
-            }
-        }
-        // 获取新数据
-        await _semaphore.WaitAsync();
-        try
-        {
-            // 双重检查,防止并发情况下重复加载
-            if (!force)
-            {
-                var cachedList = await RedisHelper.GetAsync<IEnumerable<TkEndpointConfigDTO>>(cache_key);
-                if (cachedList != null)
-                {
-                    _cached = cachedList;
-                    return cachedList;
-                }
-            }
-
-            // 从数据库加载数据
-            var list = new DBContext.Table("tk_endpoint_config")
-                .Where("status=@status", new { status = 1 })
-                .Select<TkEndpointConfigDTO>();
-
-            if (list == null) return default;
-
-            // 更新调用次数
-            //foreach (var item in list)
-            //{
-            //    item.current_daily_calls = await RiskControlCore.GetAllNodesTkEndpointCallsAsync(item.id, DateTime.Now.ToString("yyyyMMdd"));
-            //    item.current_hourly_calls = await RiskControlCore.GetAllNodesTkEndpointCallsAsync(item.id, DateTime.Now.ToString("yyyyMMddHH"));
-            //}
-
-            // 更新缓存
-            await RedisHelper.SetAsync(cache_key, list, 30 * 86400);
-            _cached = list;
-            _accountEndpointCache.Clear();
-            return list;
-        }
-        finally
-        {
-            _semaphore.Release();
-        }
-    }
-
-
-    public static async Task<List<TkEndpointConfigDTO>> GetEndpointsByAccountAsync(int accountId, bool? isTaobaoUrl = null, string parseEndpoints = null)
-    {
-        var cacheKey = (accountId, isTaobaoUrl);
-
-        if (_accountEndpointCache.TryGetValue(cacheKey, out var cached))
-            return cached.Select(x => x.DeepCopy()).ToList();
-
-        // 2. 获取所有端点(包含全局和专属)
-        var allEndpoints = (await ListAsync())?.Where(e => e.status).ToList() ?? [];
-
-        // 3. 合并逻辑:专属配置覆盖全局配置(关键!)
-        var mergedEndpoints = allEndpoints
-            .GroupBy(e => e.endpoint)
-            .Select(g =>
-            {
-                // 优先取专属配置
-                var accountSpecific = g.FirstOrDefault(e => e.tk_pool_id == accountId);
-                if (accountSpecific != null)
-                    return accountSpecific.DeepCopy(); // 深拷贝专属配置
-
-                // 否则取全局配置(tk_pool_id=0)
-                var global = g.FirstOrDefault(e => e.tk_pool_id == 0);
-                return global?.DeepCopy(); // 深拷贝全局配置
-            })
-            .Where(e => e != null)
-            .ToList();
-
-        // 4. 根据isTaobaoUrl过滤
-        if (isTaobaoUrl.HasValue)
-        {
-            mergedEndpoints = mergedEndpoints.Where(e =>
-            {
-                if (e.taobao_id_handling == 0) return true;
-                if (e.taobao_id_handling == 1 && isTaobaoUrl.Value) return true;
-                if (e.taobao_id_handling == 2 && !isTaobaoUrl.Value) return true;
-                return false;
-            }).ToList();
-        }
-
-        // 5. 排除指定的端点
-        if (!string.IsNullOrEmpty(parseEndpoints))
-        {
-            var parseEndpointIds = parseEndpoints.Split(',')
-                .Select(idStr => int.TryParse(idStr.Trim(), out var id) ? id : (int?)null)
-                .Where(id => id.HasValue)
-                .Select(id => id.Value)
-                .ToHashSet();
-
-            mergedEndpoints = mergedEndpoints.Where(e => parseEndpointIds.Contains(e.ep_id)).ToList();
-        }
-
-        // 6. 存入缓存
-        _accountEndpointCache.TryAdd(cacheKey, mergedEndpoints.Select(x => x.DeepCopy()).ToList());
-        return mergedEndpoints;
-    }
-
-
-
-    // 添加缓存清理方法
-    public static void ClearEndpointCache(int accountId)
-    {
-        _accountEndpointCache.TryRemove((accountId, true), out _);
-        _accountEndpointCache.TryRemove((accountId, false), out _);
-        _accountEndpointCache.TryRemove((accountId, null), out _);
-    }
-
-
-
-    public static void Refresh()
-    {
-        _ = AllListAsync(true);
-        _ = ListAsync(true);
-        _accountEndpointCache.Clear();
-    }
-
-}
+using dodohold.core;
+using molilian.core;
+using Org.BouncyCastle.Crypto;
+using System;
+using System.Collections.Concurrent;
+using System.Linq;
+using YunhuiKit;
+using static molilian.core.TkPoolCore;
+
+public partial class TkEndpointCore
+{
+    private static SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
+
+    private static IEnumerable<TkEndpointConfigDTO> _cached;
+
+    // 添加到 TkEndpointCore 类中
+    private static readonly ConcurrentDictionary<(int, bool?), List<TkEndpointConfigDTO>> _accountEndpointCache = new();
+
+
+    private static async Task<IEnumerable<TkEndpointConfigDTO>> ListAsync(bool force = false)
+    {
+        // 内存缓存检查
+        if (!force && _cached != null) return _cached;
+        string cache_key = "cache:tk_endpoint_config";
+
+        if (!force)
+        {
+            var cachedList = await RedisHelper.GetAsync<IEnumerable<TkEndpointConfigDTO>>(cache_key);
+            if (cachedList != null)
+            {
+                _cached = cachedList;
+                return cachedList;
+            }
+        }
+        // 获取新数据
+        await _semaphore.WaitAsync();
+        try
+        {
+            // 双重检查,防止并发情况下重复加载
+            if (!force)
+            {
+                var cachedList = await RedisHelper.GetAsync<IEnumerable<TkEndpointConfigDTO>>(cache_key);
+                if (cachedList != null)
+                {
+                    _cached = cachedList;
+                    return cachedList;
+                }
+            }
+
+            // 从数据库加载数据
+            var list = new DBContext.Table("tk_endpoint_config")
+                .Where("status=@status", new { status = 1 })
+                .Select<TkEndpointConfigDTO>();
+
+            if (list == null) return default;
+
+            // 更新调用次数
+            //foreach (var item in list)
+            //{
+            //    item.current_daily_calls = await RiskControlCore.GetAllNodesTkEndpointCallsAsync(item.id, DateTime.Now.ToString("yyyyMMdd"));
+            //    item.current_hourly_calls = await RiskControlCore.GetAllNodesTkEndpointCallsAsync(item.id, DateTime.Now.ToString("yyyyMMddHH"));
+            //}
+
+            // 更新缓存
+            await RedisHelper.SetAsync(cache_key, list, 30 * 86400);
+            _cached = list;
+            _accountEndpointCache.Clear();
+            return list;
+        }
+        finally
+        {
+            _semaphore.Release();
+        }
+    }
+
+
+    public static async Task<List<TkEndpointConfigDTO>> GetEndpointsByAccountAsync(int accountId, bool? isTaobaoUrl = null, string parseEndpoints = null)
+    {
+        var cacheKey = (accountId, isTaobaoUrl);
+
+        if (_accountEndpointCache.TryGetValue(cacheKey, out var cached))
+            return cached.Select(x => x.DeepCopy()).ToList();
+
+        // 2. 获取所有端点(包含全局和专属)
+        var allEndpoints = (await ListAsync())?.Where(e => e.status).ToList() ?? [];
+
+        // 3. 合并逻辑:专属配置覆盖全局配置(关键!)
+        var mergedEndpoints = allEndpoints
+            .GroupBy(e => e.endpoint)
+            .Select(g =>
+            {
+                // 优先取专属配置
+                var accountSpecific = g.FirstOrDefault(e => e.tk_pool_id == accountId);
+                if (accountSpecific != null)
+                    return accountSpecific.DeepCopy(); // 深拷贝专属配置
+
+                // 否则取全局配置(tk_pool_id=0)
+                var global = g.FirstOrDefault(e => e.tk_pool_id == 0);
+                return global?.DeepCopy(); // 深拷贝全局配置
+            })
+            .Where(e => e != null)
+            .ToList();
+
+        // 4. 根据isTaobaoUrl过滤
+        if (isTaobaoUrl.HasValue)
+        {
+            mergedEndpoints = mergedEndpoints.Where(e =>
+            {
+                if (e.taobao_id_handling == 0) return true;
+                if (e.taobao_id_handling == 1 && isTaobaoUrl.Value) return true;
+                if (e.taobao_id_handling == 2 && !isTaobaoUrl.Value) return true;
+                return false;
+            }).ToList();
+        }
+
+        // 5. 排除指定的端点
+        if (!string.IsNullOrEmpty(parseEndpoints))
+        {
+            var parseEndpointIds = parseEndpoints.Split(',')
+                .Select(idStr => int.TryParse(idStr.Trim(), out var id) ? id : (int?)null)
+                .Where(id => id.HasValue)
+                .Select(id => id.Value)
+                .ToHashSet();
+
+            mergedEndpoints = mergedEndpoints.Where(e => parseEndpointIds.Contains(e.ep_id)).ToList();
+        }
+
+        // 6. 存入缓存
+        _accountEndpointCache.TryAdd(cacheKey, mergedEndpoints.Select(x => x.DeepCopy()).ToList());
+        return mergedEndpoints;
+    }
+
+
+
+    // 添加缓存清理方法
+    public static void ClearEndpointCache(int accountId)
+    {
+        _accountEndpointCache.TryRemove((accountId, true), out _);
+        _accountEndpointCache.TryRemove((accountId, false), out _);
+        _accountEndpointCache.TryRemove((accountId, null), out _);
+    }
+
+
+
+    public static void Refresh()
+    {
+        _ = AllListAsync(true);
+        _ = ListAsync(true);
+        _accountEndpointCache.Clear();
+    }
+
+}

+ 313 - 313
molilian.core/Core/taoke/TkEndpointManager.cs

@@ -1,313 +1,313 @@
-using dodohold.core;
-using Google.Protobuf.WellKnownTypes;
-using molilian.core;
-using System;
-using System.Collections.Concurrent;
-using System.Linq;
-
-public partial class TkEndpointManager
-{
-    // 只保留轮询索引
-    private static readonly ConcurrentDictionary<int, AccountEndpointState> accountStates = new();
-
-    // 记录每个账号最后一次挂起操作的时间
-    private static readonly ConcurrentDictionary<int, DateTime> lastSuspendTimes = new();
-
-
-    public static void Refresh()
-    {
-        accountStates.Clear();
-        lastSuspendTimes.Clear();
-    }
-
-
-    // 从数据库获取所有可用的API端点
-    private static async Task<Dictionary<int, string>> GetAllAvailableApisAsync(int accountId, bool? isTaobaoUrl = null, string parseEndpoint = null)
-    {
-        var endpoints = await TkEndpointCore.GetEndpointsByAccountAsync(accountId, isTaobaoUrl, parseEndpoint);
-        return endpoints?
-            .Where(e => e.status)
-            .ToDictionary(e => e.ep_id, e => e.endpoint) ?? new Dictionary<int, string>();
-    }
-
-    // 从数据库获取端点的挂起时间配置
-    private static async Task<ConcurrentDictionary<string, int>> GetEndpointHoldMinutesAsync(int accountId, bool? isTaobaoUrl = null, string parseEndpoint = null)
-    {
-        var endpoints = await TkEndpointCore.GetEndpointsByAccountAsync(accountId, isTaobaoUrl, parseEndpoint);
-        var dict = new ConcurrentDictionary<string, int>();
-
-        if (endpoints != null)
-        {
-            foreach (var endpoint in endpoints)
-            {
-                dict.TryAdd(endpoint.endpoint, endpoint.suspend_duration);
-            }
-        }
-
-        return dict;
-    }
-    public static bool IsEndpointSuspended(int accountId, string endpoint)
-    {
-        return RedisHelper.Exists($"tk_suspend:{accountId}:{endpoint}");
-    }
-
-    /// <summary>
-    /// 检查指定账号是否至少拥有一个可用的endpoint
-    /// </summary>
-    /// <param name="accountId">账号ID</param>
-    /// <returns>true如果至少有一个可用endpoint,否则false</returns>
-    public static async Task<bool> HasAvailableEndpointAsync(int accountId)
-    {
-        var availableApis = await GetAllAvailableApisAsync(accountId);
-        if (availableApis.Count == 0) return false;
-
-        if (!accountStates.TryGetValue(accountId, out var state))
-        {
-            return true; // 如果账号没有状态记录,所有节点都是正常的
-        }
-
-        return availableApis.Any(api => !IsEndpointSuspended(accountId, api.Value));
-    }
-
-    /// <summary>
-    /// 获取多个账号的节点状态信息
-    /// </summary>
-    public static async Task<Dictionary<int, Dictionary<string, string>>> GetStatus(IEnumerable<int> accountIds)
-    {
-        var result = new Dictionary<int, Dictionary<string, string>>();
-
-        foreach (var accountId in accountIds.Distinct())
-        {
-            var availableApis = await GetAllAvailableApisAsync(accountId);
-            result[accountId] = await GetStatus(accountId, availableApis);
-        }
-
-        return result;
-    }
-
-    /// <summary>
-    /// 获取指定账号的节点状态信息
-    /// </summary>
-    public static async Task<Dictionary<string, string>> GetStatus(int accountId, Dictionary<int, string> availableApis)
-    {
-        var status = new Dictionary<string, string>();
-        if (availableApis.Count == 0)
-        {
-            return status;
-        }
-
-        if (!accountStates.TryGetValue(accountId, out var state))
-        {
-            // 如果账号没有状态记录,所有节点都是正常的
-            foreach ((var api_id, var api) in availableApis)
-            {
-                status[api] = "正常";
-            }
-            return status;
-        }
-
-        foreach ((var api_id, var api) in availableApis)
-        {
-            if (IsEndpointSuspended(accountId, api))
-            {
-                // Redis没有挂起到期时间,展示"挂起"即可
-                status[api] = $"挂起";
-            }
-            else
-            {
-                status[api] = "正常";
-            }
-        }
-        return status;
-    }
-
-    public static async Task<(int, string)> GetConvertApiAsync(int accountId, bool isTaobaoUrl, string parseEndpoint)
-    {
-        var endpoints = await TkEndpointCore.GetEndpointsByAccountAsync(accountId, isTaobaoUrl, parseEndpoint);
-        if (endpoints == null || !endpoints.Any(e => e.status)) return (0, string.Empty);
-
-        // 1. 过滤可用端点(状态正常)
-        var availableApis = endpoints.Where(e => e.status);
-
-        var state = accountStates.GetOrAdd(accountId, _ => new AccountEndpointState());
-        var now = DateTime.Now;
-
-        // 2. 先找出所有未被挂起、未达限制的端点
-        var availableEndpoints = availableApis
-            .Where(e => !IsEndpointSuspended(accountId, e.endpoint))
-            .Where(e => e.hourly_calls_limit <= 0 || e.current_hourly_calls < e.hourly_calls_limit)
-            .Where(e => e.daily_calls_limit <= 0 || e.current_daily_calls < e.daily_calls_limit)
-            .ToArray();
-
-        // 3. 如果存在可用端点,直接返回
-        if (availableEndpoints.Length > 0)
-        {
-            // 生成唯一的轮询key,确保每种可用端点组合都有独立索引
-            string roundRobinKey = $"{accountId}:{string.Join(",", availableEndpoints.Select(e => e.ep_id).OrderBy(x => x))}";
-            var currentIndex = state.GetOrAddAccountIndex(roundRobinKey);
-            var newIndex = (currentIndex + 1) % availableEndpoints.Length;
-            state.UpdateAccountIndex(roundRobinKey, newIndex);
-            var selectedEndpoint = availableEndpoints[newIndex];
-
-            // 异步更新调用统计(不阻塞当前请求)
-            _ = UpdateEndpointStatsAsync(accountId, selectedEndpoint, now);
-            return (selectedEndpoint.ep_id, selectedEndpoint.endpoint);
-        }
-
-        // 4. 检查是否仅因时间间隔限制
-        var intervalLimitedEndpoints = availableApis
-            .Where(e => !IsEndpointSuspended(accountId, e.endpoint))
-            .Where(e => e.hourly_calls_limit <= 0 || e.current_hourly_calls < e.hourly_calls_limit)
-            .Where(e => e.daily_calls_limit <= 0 || e.current_daily_calls < e.daily_calls_limit)
-            .Where(e => e.interval_seconds > 0 &&
-                       e.last_call_time != null &&
-                       (now - e.last_call_time).TotalSeconds < e.interval_seconds)
-            .ToArray();
-
-        if (intervalLimitedEndpoints.Length > 0)
-        {
-            // 如果是时间间隔限制,返回空字符串
-            return (0, string.Empty);
-        }
-
-        // 5. 检查所有端点的状态
-        var allEndpointsSuspended = availableApis.All(e => IsEndpointSuspended(accountId, e.endpoint));
-        var allEndpointsLimited = availableApis.All(e =>
-            (e.hourly_calls_limit > 0 && e.current_hourly_calls >= e.hourly_calls_limit) ||
-            (e.daily_calls_limit > 0 && e.current_daily_calls >= e.daily_calls_limit));
-
-        // 如果所有端点都被挂起或达到限制,返回 "ALL"
-        if (allEndpointsSuspended || allEndpointsLimited)
-        {
-            return (0, "ALL");
-        }
-
-        // 6. 其他情况(理论上不应该到达这里)
-        return (0, string.Empty);
-    }
-
-    // 辅助方法:异步更新调用统计(原逻辑,仅拆分以保持清晰)
-    private static async Task UpdateEndpointStatsAsync(int accountId, TkEndpointConfigDTO selectedEndpoint, DateTime now)
-    {
-        var currentHour = now.ToString("yyyyMMddHH");
-        var currentDay = now.ToString("yyyyMMdd");
-
-        // 更新调用计数
-        selectedEndpoint.current_hourly_calls++;
-        selectedEndpoint.current_daily_calls++;
-        selectedEndpoint.last_call_time = now;
-
-        // 保存到 Redis
-        await RiskControlCore.SetTkEndpointCallsAsync(accountId, selectedEndpoint.id, currentHour, selectedEndpoint.current_hourly_calls);
-        await RiskControlCore.SetTkEndpointCallsAsync(accountId, selectedEndpoint.id, currentDay, selectedEndpoint.current_daily_calls);
-
-        // 检查是否需要挂起(原逻辑)
-        if (selectedEndpoint.hourly_calls_limit > 0 && selectedEndpoint.current_hourly_calls >= selectedEndpoint.hourly_calls_limit)
-        {
-            _ = SuspendAsync(accountId, selectedEndpoint.endpoint, SuspendReason.HourlyLimit, selectedEndpoint.suspend_duration);
-        }
-        if (selectedEndpoint.daily_calls_limit > 0 && selectedEndpoint.current_daily_calls >= selectedEndpoint.daily_calls_limit)
-        {
-            _ = SuspendAsync(accountId, selectedEndpoint.endpoint, SuspendReason.DailyLimit, selectedEndpoint.suspend_duration);
-        }
-    }
-
-    public static async Task SuspendAsync(int accountId, string endpoint, SuspendReason reason = SuspendReason.Passive, int? customHoldMinutes = null)
-    {
-        var holdMinutes = await GetEndpointHoldMinutesAsync(accountId);
-        TimeSpan suspendDuration;
-        if (customHoldMinutes.HasValue)
-        {
-            suspendDuration = TimeSpan.FromMinutes(customHoldMinutes.Value);
-        }
-        else
-        {
-            suspendDuration = reason switch
-            {
-                SuspendReason.HourlyLimit => CalculateHourlySuspendDuration(),
-                SuspendReason.DailyLimit => CalculateDailySuspendDuration(),
-                _ => TimeSpan.FromMinutes(holdMinutes.GetValueOrDefault(endpoint, 240))
-            };
-        }
-
-        RedisHelper.Set($"tk_suspend:{accountId}:{endpoint}", 1, (int)suspendDuration.TotalSeconds);
-
-        _ = new LoggerLibrary("转链接口风控", accountId.ToString()).Info($"{accountId}_{endpoint}", reason.ToString()).SaveAsync();
-        _ = NotifyCore.NotifyAsync($"【转链接口风控】{accountId}_{endpoint} ({reason})");
-    }
-
-
-    /// <summary>
-    /// 主动释放指定账号的挂起状态
-    /// </summary>
-    /// <param name="accountId">账号ID</param>
-    /// <param name="endpoint">要释放的端点名称,如果为null则释放所有端点的挂起状态</param>
-    public static void ReleaseSuspend(int accountId, string endpoint = null)
-    {
-        if (endpoint == null)
-        {
-            var pattern = $"tk_suspend:{accountId}:*";
-            var keys = RedisHelper.Keys(pattern);
-            foreach (var key in keys)
-            {
-                RedisHelper.Del(key);
-            }
-        }
-        else
-        {
-            RedisHelper.Del($"tk_suspend:{accountId}:{endpoint}");
-        }
-
-        var action = endpoint == null ? "释放所有挂起" : $"释放挂起({endpoint})";
-        _ = new LoggerLibrary("转链接口风控", accountId.ToString()).Info($"{accountId}", action).SaveAsync();
-        _ = NotifyCore.NotifyAsync($"【转链接口风控】{accountId} {action}");
-    }
-
-
-    private static TimeSpan CalculateHourlySuspendDuration()
-    {
-        var now = DateTime.Now;
-        var nextHour = now.AddHours(1).Date.AddHours(now.Hour + 1); // 下一个整点(如 14:30 → 15:00)
-        return nextHour - now;
-    }
-
-    private static TimeSpan CalculateDailySuspendDuration()
-    {
-        var now = DateTime.Now;
-        var tomorrow = now.Date.AddDays(1); // 次日零点
-        return tomorrow - now;
-    }
-
-    public enum SuspendReason
-    {
-        /// <summary>
-        /// 主动暂停(手动操作)
-        /// </summary>
-        Active,
-
-        /// <summary>
-        /// 风控导致的被动暂停
-        /// </summary>
-        Passive,
-
-        /// <summary>
-        /// 每小时总量限制触发的暂停
-        /// </summary>
-        HourlyLimit,
-
-        /// <summary>
-        /// 每日总量限制触发的暂停
-        /// </summary>
-        DailyLimit
-    }
-
-    private class AccountEndpointState
-    {
-        // 只保留轮询索引
-        private readonly ConcurrentDictionary<string, int> roundRobinIndices = new();
-        public int GetOrAddAccountIndex(string key) => roundRobinIndices.GetOrAdd(key, -1);
-        public void UpdateAccountIndex(string key, int newIndex) => roundRobinIndices[key] = newIndex;
-    }
-
-
-}
+using dodohold.core;
+using Google.Protobuf.WellKnownTypes;
+using molilian.core;
+using System;
+using System.Collections.Concurrent;
+using System.Linq;
+
+public partial class TkEndpointManager
+{
+    // 只保留轮询索引
+    private static readonly ConcurrentDictionary<int, AccountEndpointState> accountStates = new();
+
+    // 记录每个账号最后一次挂起操作的时间
+    private static readonly ConcurrentDictionary<int, DateTime> lastSuspendTimes = new();
+
+
+    public static void Refresh()
+    {
+        accountStates.Clear();
+        lastSuspendTimes.Clear();
+    }
+
+
+    // 从数据库获取所有可用的API端点
+    private static async Task<Dictionary<int, string>> GetAllAvailableApisAsync(int accountId, bool? isTaobaoUrl = null, string parseEndpoint = null)
+    {
+        var endpoints = await TkEndpointCore.GetEndpointsByAccountAsync(accountId, isTaobaoUrl, parseEndpoint);
+        return endpoints?
+            .Where(e => e.status)
+            .ToDictionary(e => e.ep_id, e => e.endpoint) ?? new Dictionary<int, string>();
+    }
+
+    // 从数据库获取端点的挂起时间配置
+    private static async Task<ConcurrentDictionary<string, int>> GetEndpointHoldMinutesAsync(int accountId, bool? isTaobaoUrl = null, string parseEndpoint = null)
+    {
+        var endpoints = await TkEndpointCore.GetEndpointsByAccountAsync(accountId, isTaobaoUrl, parseEndpoint);
+        var dict = new ConcurrentDictionary<string, int>();
+
+        if (endpoints != null)
+        {
+            foreach (var endpoint in endpoints)
+            {
+                dict.TryAdd(endpoint.endpoint, endpoint.suspend_duration);
+            }
+        }
+
+        return dict;
+    }
+    public static bool IsEndpointSuspended(int accountId, string endpoint)
+    {
+        return RedisHelper.Exists($"tk_suspend:{accountId}:{endpoint}");
+    }
+
+    /// <summary>
+    /// 检查指定账号是否至少拥有一个可用的endpoint
+    /// </summary>
+    /// <param name="accountId">账号ID</param>
+    /// <returns>true如果至少有一个可用endpoint,否则false</returns>
+    public static async Task<bool> HasAvailableEndpointAsync(int accountId)
+    {
+        var availableApis = await GetAllAvailableApisAsync(accountId);
+        if (availableApis.Count == 0) return false;
+
+        if (!accountStates.TryGetValue(accountId, out var state))
+        {
+            return true; // 如果账号没有状态记录,所有节点都是正常的
+        }
+
+        return availableApis.Any(api => !IsEndpointSuspended(accountId, api.Value));
+    }
+
+    /// <summary>
+    /// 获取多个账号的节点状态信息
+    /// </summary>
+    public static async Task<Dictionary<int, Dictionary<string, string>>> GetStatus(IEnumerable<int> accountIds)
+    {
+        var result = new Dictionary<int, Dictionary<string, string>>();
+
+        foreach (var accountId in accountIds.Distinct())
+        {
+            var availableApis = await GetAllAvailableApisAsync(accountId);
+            result[accountId] = await GetStatus(accountId, availableApis);
+        }
+
+        return result;
+    }
+
+    /// <summary>
+    /// 获取指定账号的节点状态信息
+    /// </summary>
+    public static async Task<Dictionary<string, string>> GetStatus(int accountId, Dictionary<int, string> availableApis)
+    {
+        var status = new Dictionary<string, string>();
+        if (availableApis.Count == 0)
+        {
+            return status;
+        }
+
+        if (!accountStates.TryGetValue(accountId, out var state))
+        {
+            // 如果账号没有状态记录,所有节点都是正常的
+            foreach ((var api_id, var api) in availableApis)
+            {
+                status[api] = "正常";
+            }
+            return status;
+        }
+
+        foreach ((var api_id, var api) in availableApis)
+        {
+            if (IsEndpointSuspended(accountId, api))
+            {
+                // Redis没有挂起到期时间,展示"挂起"即可
+                status[api] = $"挂起";
+            }
+            else
+            {
+                status[api] = "正常";
+            }
+        }
+        return status;
+    }
+
+    public static async Task<(int, string)> GetConvertApiAsync(int accountId, bool isTaobaoUrl, string parseEndpoint)
+    {
+        var endpoints = await TkEndpointCore.GetEndpointsByAccountAsync(accountId, isTaobaoUrl, parseEndpoint);
+        if (endpoints == null || !endpoints.Any(e => e.status)) return (0, string.Empty);
+
+        // 1. 过滤可用端点(状态正常)
+        var availableApis = endpoints.Where(e => e.status);
+
+        var state = accountStates.GetOrAdd(accountId, _ => new AccountEndpointState());
+        var now = DateTime.Now;
+
+        // 2. 先找出所有未被挂起、未达限制的端点
+        var availableEndpoints = availableApis
+            .Where(e => !IsEndpointSuspended(accountId, e.endpoint))
+            .Where(e => e.hourly_calls_limit <= 0 || e.current_hourly_calls < e.hourly_calls_limit)
+            .Where(e => e.daily_calls_limit <= 0 || e.current_daily_calls < e.daily_calls_limit)
+            .ToArray();
+
+        // 3. 如果存在可用端点,直接返回
+        if (availableEndpoints.Length > 0)
+        {
+            // 生成唯一的轮询key,确保每种可用端点组合都有独立索引
+            string roundRobinKey = $"{accountId}:{string.Join(",", availableEndpoints.Select(e => e.ep_id).OrderBy(x => x))}";
+            var currentIndex = state.GetOrAddAccountIndex(roundRobinKey);
+            var newIndex = (currentIndex + 1) % availableEndpoints.Length;
+            state.UpdateAccountIndex(roundRobinKey, newIndex);
+            var selectedEndpoint = availableEndpoints[newIndex];
+
+            // 异步更新调用统计(不阻塞当前请求)
+            _ = UpdateEndpointStatsAsync(accountId, selectedEndpoint, now);
+            return (selectedEndpoint.ep_id, selectedEndpoint.endpoint);
+        }
+
+        // 4. 检查是否仅因时间间隔限制
+        var intervalLimitedEndpoints = availableApis
+            .Where(e => !IsEndpointSuspended(accountId, e.endpoint))
+            .Where(e => e.hourly_calls_limit <= 0 || e.current_hourly_calls < e.hourly_calls_limit)
+            .Where(e => e.daily_calls_limit <= 0 || e.current_daily_calls < e.daily_calls_limit)
+            .Where(e => e.interval_seconds > 0 &&
+                       e.last_call_time != null &&
+                       (now - e.last_call_time).TotalSeconds < e.interval_seconds)
+            .ToArray();
+
+        if (intervalLimitedEndpoints.Length > 0)
+        {
+            // 如果是时间间隔限制,返回空字符串
+            return (0, string.Empty);
+        }
+
+        // 5. 检查所有端点的状态
+        var allEndpointsSuspended = availableApis.All(e => IsEndpointSuspended(accountId, e.endpoint));
+        var allEndpointsLimited = availableApis.All(e =>
+            (e.hourly_calls_limit > 0 && e.current_hourly_calls >= e.hourly_calls_limit) ||
+            (e.daily_calls_limit > 0 && e.current_daily_calls >= e.daily_calls_limit));
+
+        // 如果所有端点都被挂起或达到限制,返回 "ALL"
+        if (allEndpointsSuspended || allEndpointsLimited)
+        {
+            return (0, "ALL");
+        }
+
+        // 6. 其他情况(理论上不应该到达这里)
+        return (0, string.Empty);
+    }
+
+    // 辅助方法:异步更新调用统计(原逻辑,仅拆分以保持清晰)
+    private static async Task UpdateEndpointStatsAsync(int accountId, TkEndpointConfigDTO selectedEndpoint, DateTime now)
+    {
+        var currentHour = now.ToString("yyyyMMddHH");
+        var currentDay = now.ToString("yyyyMMdd");
+
+        // 更新调用计数
+        selectedEndpoint.current_hourly_calls++;
+        selectedEndpoint.current_daily_calls++;
+        selectedEndpoint.last_call_time = now;
+
+        // 保存到 Redis
+        await RiskControlCore.SetTkEndpointCallsAsync(accountId, selectedEndpoint.id, currentHour, selectedEndpoint.current_hourly_calls);
+        await RiskControlCore.SetTkEndpointCallsAsync(accountId, selectedEndpoint.id, currentDay, selectedEndpoint.current_daily_calls);
+
+        // 检查是否需要挂起(原逻辑)
+        if (selectedEndpoint.hourly_calls_limit > 0 && selectedEndpoint.current_hourly_calls >= selectedEndpoint.hourly_calls_limit)
+        {
+            _ = SuspendAsync(accountId, selectedEndpoint.endpoint, SuspendReason.HourlyLimit, selectedEndpoint.suspend_duration);
+        }
+        if (selectedEndpoint.daily_calls_limit > 0 && selectedEndpoint.current_daily_calls >= selectedEndpoint.daily_calls_limit)
+        {
+            _ = SuspendAsync(accountId, selectedEndpoint.endpoint, SuspendReason.DailyLimit, selectedEndpoint.suspend_duration);
+        }
+    }
+
+    public static async Task SuspendAsync(int accountId, string endpoint, SuspendReason reason = SuspendReason.Passive, int? customHoldMinutes = null)
+    {
+        var holdMinutes = await GetEndpointHoldMinutesAsync(accountId);
+        TimeSpan suspendDuration;
+        if (customHoldMinutes.HasValue)
+        {
+            suspendDuration = TimeSpan.FromMinutes(customHoldMinutes.Value);
+        }
+        else
+        {
+            suspendDuration = reason switch
+            {
+                SuspendReason.HourlyLimit => CalculateHourlySuspendDuration(),
+                SuspendReason.DailyLimit => CalculateDailySuspendDuration(),
+                _ => TimeSpan.FromMinutes(holdMinutes.GetValueOrDefault(endpoint, 240))
+            };
+        }
+
+        RedisHelper.Set($"tk_suspend:{accountId}:{endpoint}", 1, (int)suspendDuration.TotalSeconds);
+
+        _ = new LoggerLibrary("转链接口风控", accountId.ToString()).Info($"{accountId}_{endpoint}", reason.ToString()).SaveAsync();
+        _ = NotifyCore.NotifyAsync($"【转链接口风控】{accountId}_{endpoint} ({reason})");
+    }
+
+
+    /// <summary>
+    /// 主动释放指定账号的挂起状态
+    /// </summary>
+    /// <param name="accountId">账号ID</param>
+    /// <param name="endpoint">要释放的端点名称,如果为null则释放所有端点的挂起状态</param>
+    public static void ReleaseSuspend(int accountId, string endpoint = null)
+    {
+        if (endpoint == null)
+        {
+            var pattern = $"tk_suspend:{accountId}:*";
+            var keys = RedisHelper.Keys(pattern);
+            foreach (var key in keys)
+            {
+                RedisHelper.Del(key);
+            }
+        }
+        else
+        {
+            RedisHelper.Del($"tk_suspend:{accountId}:{endpoint}");
+        }
+
+        var action = endpoint == null ? "释放所有挂起" : $"释放挂起({endpoint})";
+        _ = new LoggerLibrary("转链接口风控", accountId.ToString()).Info($"{accountId}", action).SaveAsync();
+        _ = NotifyCore.NotifyAsync($"【转链接口风控】{accountId} {action}");
+    }
+
+
+    private static TimeSpan CalculateHourlySuspendDuration()
+    {
+        var now = DateTime.Now;
+        var nextHour = now.AddHours(1).Date.AddHours(now.Hour + 1); // 下一个整点(如 14:30 → 15:00)
+        return nextHour - now;
+    }
+
+    private static TimeSpan CalculateDailySuspendDuration()
+    {
+        var now = DateTime.Now;
+        var tomorrow = now.Date.AddDays(1); // 次日零点
+        return tomorrow - now;
+    }
+
+    public enum SuspendReason
+    {
+        /// <summary>
+        /// 主动暂停(手动操作)
+        /// </summary>
+        Active,
+
+        /// <summary>
+        /// 风控导致的被动暂停
+        /// </summary>
+        Passive,
+
+        /// <summary>
+        /// 每小时总量限制触发的暂停
+        /// </summary>
+        HourlyLimit,
+
+        /// <summary>
+        /// 每日总量限制触发的暂停
+        /// </summary>
+        DailyLimit
+    }
+
+    private class AccountEndpointState
+    {
+        // 只保留轮询索引
+        private readonly ConcurrentDictionary<string, int> roundRobinIndices = new();
+        public int GetOrAddAccountIndex(string key) => roundRobinIndices.GetOrAdd(key, -1);
+        public void UpdateAccountIndex(string key, int newIndex) => roundRobinIndices[key] = newIndex;
+    }
+
+
+}

Деякі файли не було показано, через те що забагато файлів було змінено