using dodohold.core; using System.Globalization; namespace molilian.core { public partial class OverrideRuleCore { // KEYS: 小时触发、日触发、小时重写、日重写。沿用现有 key,保留历史数据。 // 同一脚本内检查两项额度并更新计数,避免并发超额或只扣掉其中一项额度。 private const string RecordRewriteScript = @" local hourlyRewrites = tonumber(redis.call('GET', KEYS[3]) or '0') local dailyRewrites = tonumber(redis.call('GET', KEYS[4]) or '0') local hourlyLimit = tonumber(ARGV[1]) local dailyLimit = tonumber(ARGV[2]) redis.call('INCR', KEYS[1]) redis.call('EXPIRE', KEYS[1], 7200) redis.call('INCR', KEYS[2]) redis.call('EXPIRE', KEYS[2], 259200) if (hourlyLimit > 0 and hourlyRewrites >= hourlyLimit) or (dailyLimit > 0 and dailyRewrites >= dailyLimit) then return 0 end redis.call('INCR', KEYS[3]) redis.call('EXPIRE', KEYS[3], 15552000) redis.call('INCR', KEYS[4]) redis.call('EXPIRE', KEYS[4], 15552000) return 1"; private static string[] GetCounterKeys(OverrideRuleDTO rule, DateTime now) => [ $":override_rule_calls:{rule.id}:{now:yyyyMMddHH}", $":override_rule_calls:{rule.id}:{now:yyyyMMdd}", $":keyword_stats:{rule.platform}:{rule.id}:{now:yyyyMMddHH}", $":keyword_stats:{rule.platform}:{rule.id}:{now:yyyyMMdd}" ]; private static async Task TryRecordRewriteAsync(OverrideRuleDTO rule, DateTime now) { try { // RedisUtility 与 RedisKit 均使用 RedisConfig,复用已初始化的连接池。 // CSRedis 高层 Eval 仅支持一个 key;这里通过原生接口显式传入全部四个 key。 var pool = RedisHelper.Instance.Nodes.Values.Single(); var connection = await pool.GetAsync(); Exception? error = null; try { var keys = GetCounterKeys(rule, now).Select(key => pool.Prefix + key).ToArray(); var result = await connection.Value.EvalAsync(RecordRewriteScript, keys, rule.hourly_calls_limit, rule.daily_calls_limit); return Convert.ToInt64(result, CultureInfo.InvariantCulture) == 1; } catch (Exception ex) { error = ex; throw; } finally { pool.Return(connection, error); } } catch (Exception ex) { _ = new LoggerLibrary("OverrideRule", "Redis") .Info($"Rewrite limit check failed: {ex.Message}", ex.StackTrace).SaveAsync(); // 无法确认额度时跳过受限规则;无限额规则保留统计故障时继续工作的行为。 return rule.hourly_calls_limit <= 0 && rule.daily_calls_limit <= 0; } } public static async Task LoadStatisticsAsync(OverrideRuleDTO rule, DateTime now) { var counts = await RedisHelper.MGetAsync(GetCounterKeys(rule, now)); rule.current_hourly_calls = counts[0]; rule.current_daily_calls = counts[1]; rule.current_hourly_rewrites = counts[2]; rule.current_daily_rewrites = counts[3]; } } }