OverrideRuleCore.Counters.cs 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384
  1. using dodohold.core;
  2. using System.Globalization;
  3. namespace molilian.core
  4. {
  5. public partial class OverrideRuleCore
  6. {
  7. // KEYS: 小时触发、日触发、小时重写、日重写。沿用现有 key,保留历史数据。
  8. // 同一脚本内检查两项额度并更新计数,避免并发超额或只扣掉其中一项额度。
  9. private const string RecordRewriteScript = @"
  10. local hourlyRewrites = tonumber(redis.call('GET', KEYS[3]) or '0')
  11. local dailyRewrites = tonumber(redis.call('GET', KEYS[4]) or '0')
  12. local hourlyLimit = tonumber(ARGV[1])
  13. local dailyLimit = tonumber(ARGV[2])
  14. redis.call('INCR', KEYS[1])
  15. redis.call('EXPIRE', KEYS[1], 7200)
  16. redis.call('INCR', KEYS[2])
  17. redis.call('EXPIRE', KEYS[2], 259200)
  18. if (hourlyLimit > 0 and hourlyRewrites >= hourlyLimit)
  19. or (dailyLimit > 0 and dailyRewrites >= dailyLimit) then
  20. return 0
  21. end
  22. redis.call('INCR', KEYS[3])
  23. redis.call('EXPIRE', KEYS[3], 15552000)
  24. redis.call('INCR', KEYS[4])
  25. redis.call('EXPIRE', KEYS[4], 15552000)
  26. return 1";
  27. private static string[] GetCounterKeys(OverrideRuleDTO rule, DateTime now) =>
  28. [
  29. $":override_rule_calls:{rule.id}:{now:yyyyMMddHH}",
  30. $":override_rule_calls:{rule.id}:{now:yyyyMMdd}",
  31. $":keyword_stats:{rule.platform}:{rule.id}:{now:yyyyMMddHH}",
  32. $":keyword_stats:{rule.platform}:{rule.id}:{now:yyyyMMdd}"
  33. ];
  34. private static async Task<bool> TryRecordRewriteAsync(OverrideRuleDTO rule, DateTime now)
  35. {
  36. try
  37. {
  38. // RedisUtility 与 RedisKit 均使用 RedisConfig,复用已初始化的连接池。
  39. // CSRedis 高层 Eval 仅支持一个 key;这里通过原生接口显式传入全部四个 key。
  40. var pool = RedisHelper.Instance.Nodes.Values.Single();
  41. var connection = await pool.GetAsync();
  42. Exception? error = null;
  43. try
  44. {
  45. var keys = GetCounterKeys(rule, now).Select(key => pool.Prefix + key).ToArray();
  46. var result = await connection.Value.EvalAsync(RecordRewriteScript, keys,
  47. rule.hourly_calls_limit, rule.daily_calls_limit);
  48. return Convert.ToInt64(result, CultureInfo.InvariantCulture) == 1;
  49. }
  50. catch (Exception ex)
  51. {
  52. error = ex;
  53. throw;
  54. }
  55. finally
  56. {
  57. pool.Return(connection, error);
  58. }
  59. }
  60. catch (Exception ex)
  61. {
  62. _ = new LoggerLibrary("OverrideRule", "Redis")
  63. .Info($"Rewrite limit check failed: {ex.Message}", ex.StackTrace).SaveAsync();
  64. // 无法确认额度时跳过受限规则;无限额规则保留统计故障时继续工作的行为。
  65. return rule.hourly_calls_limit <= 0 && rule.daily_calls_limit <= 0;
  66. }
  67. }
  68. public static async Task LoadStatisticsAsync(OverrideRuleDTO rule, DateTime now)
  69. {
  70. var counts = await RedisHelper.MGetAsync<int>(GetCounterKeys(rule, now));
  71. rule.current_hourly_calls = counts[0];
  72. rule.current_daily_calls = counts[1];
  73. rule.current_hourly_rewrites = counts[2];
  74. rule.current_daily_rewrites = counts[3];
  75. }
  76. }
  77. }