AiOpenRiskControlCore.cs 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. using Dapper;
  2. using dodohold.core;
  3. namespace molilian.core
  4. {
  5. public static class AiOpenRiskControlCore
  6. {
  7. public const int EndpointId = 8;
  8. public const string EndpointName = "aiopen";
  9. public const string DailyLimitReasonCode = "AI_OPEN_DAILY_LIMIT";
  10. public static bool IsApplicableAccount(TkPoolDTO? account)
  11. {
  12. if (account == null || string.IsNullOrWhiteSpace(account.parseEndpoint))
  13. {
  14. return false;
  15. }
  16. int[] endpointIds = account.parseEndpoint
  17. .Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
  18. .Select(value => int.TryParse(value, out int endpointId) ? endpointId : 0)
  19. .Where(endpointId => endpointId > 0)
  20. .Distinct()
  21. .ToArray();
  22. return endpointIds.Length == 1 && endpointIds[0] == EndpointId;
  23. }
  24. public static async Task RecordDailyLimitAndSuspendAsync(
  25. TkPoolDTO account,
  26. string reason,
  27. int bizErrorCode,
  28. int resultCode)
  29. {
  30. if (!IsApplicableAccount(account))
  31. {
  32. return;
  33. }
  34. DateTime triggerTime = DateTime.Now;
  35. DateTime releaseTime = triggerTime.Date.AddDays(1);
  36. TimeSpan suspendDuration = releaseTime - triggerTime;
  37. // Stop assigning this account locally before performing report aggregation.
  38. // The first DB writer below broadcasts the same TTL to all other nodes.
  39. await TkEndpointManager.SuspendForDurationAsync(
  40. account.id,
  41. EndpointName,
  42. suspendDuration,
  43. TkEndpointManager.SuspendReason.RemoteDailyLimit,
  44. notifyOtherNodes: false,
  45. emitNotification: false);
  46. var endpointConfigs = await TkEndpointCore.GetEndpointsByAccountReadonlyAsync(
  47. account.id,
  48. parseEndpoints: account.parseEndpoint);
  49. int endpointCounterId = endpointConfigs
  50. .FirstOrDefault(endpoint => endpoint.ep_id == EndpointId)
  51. ?.id ?? EndpointId;
  52. int requestCount = await RiskControlCore.GetAllNodesTkEndpointCallsAsync(
  53. account.id,
  54. endpointCounterId,
  55. triggerTime.ToString("yyyyMMdd"));
  56. int successCount = await TkLogCore.GetTotalAsync(
  57. $":parse_total:{TkChannelEnum.tb}_{account.id}:success:{triggerTime:yyyyMMdd}");
  58. bool shouldBroadcast = false;
  59. try
  60. {
  61. using var conn = CenterHub.GetOpenConnection();
  62. const string sql = @"
  63. INSERT IGNORE INTO tk_aiopen_risk_daily
  64. (report_date, account_id, account_name, endpoint_id, endpoint_name,
  65. reason_code, reason, biz_error_code, result_code, first_trigger_time,
  66. request_count_at_trigger, success_count_at_trigger, release_time,
  67. create_time)
  68. VALUES
  69. (@report_date, @account_id, @account_name, @endpoint_id, @endpoint_name,
  70. @reason_code, LEFT(@reason, 512), @biz_error_code, @result_code, @first_trigger_time,
  71. @request_count_at_trigger, @success_count_at_trigger, @release_time,
  72. @create_time);";
  73. int inserted = await conn.ExecuteAsync(sql, new
  74. {
  75. report_date = triggerTime.Date,
  76. account_id = account.id,
  77. account_name = account.company,
  78. endpoint_id = EndpointId,
  79. endpoint_name = EndpointName,
  80. reason_code = DailyLimitReasonCode,
  81. reason,
  82. biz_error_code = bizErrorCode,
  83. result_code = resultCode,
  84. first_trigger_time = triggerTime,
  85. request_count_at_trigger = requestCount,
  86. success_count_at_trigger = successCount,
  87. release_time = releaseTime,
  88. create_time = triggerTime
  89. });
  90. shouldBroadcast = inserted > 0;
  91. }
  92. catch (Exception ex)
  93. {
  94. // Suspending all nodes is more important than report persistence. Broadcast
  95. // even when the migration has not yet been applied or DB is down.
  96. shouldBroadcast = true;
  97. _ = new LoggerLibrary("AiOpenRiskControl", "save_error")
  98. .Info($"accountId={account.id}, requestCount={requestCount}, reason={reason}")
  99. .Info(ex.Message, ex.StackTrace)
  100. .SaveAsync();
  101. }
  102. if (shouldBroadcast)
  103. {
  104. await TkEndpointManager.SuspendForDurationAsync(
  105. account.id,
  106. EndpointName,
  107. suspendDuration,
  108. TkEndpointManager.SuspendReason.RemoteDailyLimit,
  109. notifyOtherNodes: true,
  110. emitNotification: true);
  111. }
  112. }
  113. public static async Task<Dictionary<int, AiOpenRiskDailyDTO>> GetTodayEventsAsync(IEnumerable<int> accountIds)
  114. {
  115. int[] ids = accountIds.Where(id => id > 0).Distinct().ToArray();
  116. if (ids.Length == 0)
  117. {
  118. return [];
  119. }
  120. try
  121. {
  122. using var conn = CenterHub.GetOpenConnection();
  123. const string sql = @"
  124. SELECT id, report_date, account_id, account_name, endpoint_id, endpoint_name,
  125. reason_code, reason, biz_error_code, result_code, first_trigger_time,
  126. request_count_at_trigger, success_count_at_trigger, release_time,
  127. create_time
  128. FROM tk_aiopen_risk_daily
  129. WHERE report_date = @report_date
  130. AND account_id IN @account_ids
  131. AND reason_code = @reason_code;";
  132. var rows = await conn.QueryAsync<AiOpenRiskDailyDTO>(sql, new
  133. {
  134. report_date = DateTime.Now.Date,
  135. account_ids = ids,
  136. reason_code = DailyLimitReasonCode
  137. });
  138. return rows.ToDictionary(row => row.account_id);
  139. }
  140. catch (Exception ex)
  141. {
  142. _ = new LoggerLibrary("AiOpenRiskControl", "query_error")
  143. .Info(ex.Message, ex.StackTrace)
  144. .SaveAsync();
  145. return [];
  146. }
  147. }
  148. }
  149. }