Browse Source

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 7 tháng trước cách đây
mục cha
commit
3f0b0b7a79
100 tập tin đã thay đổi với 19973 bổ sung và 19563 xóa
  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>

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 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>

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 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;
+    }
+
+
+}

Một số tệp đã không được hiển thị bởi vì quá nhiều tập tin thay đổi trong này khác