| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268 |
- 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;
- }
- }
- }
|