Selaa lähdekoodia

✨ feat(文本覆盖规则): 扩大兼容性

dodo hold 10 kuukautta sitten
vanhempi
sitoutus
e30215a8eb

+ 11 - 2
molilian.api/Controllers/admin/OverrideRulesController.cs

@@ -7,6 +7,7 @@ using static QRCoder.PayloadGenerator;
 using MySqlX.XDevAPI;
 using OfficeOpenXml.FormulaParsing.LexicalAnalysis;
 using StackExchange.Redis;
+using YunhuiKit;
 namespace molilian.api.Controllers
 {
 
@@ -26,7 +27,7 @@ namespace molilian.api.Controllers
 
 
         [HttpPost]
-        public ActionResult list([FromBody] JsonElement form)
+        public async Task<ActionResult> list([FromBody] JsonElement form)
         {
             var token = provider.Get(_accessor.HttpContext);
 
@@ -62,8 +63,16 @@ namespace molilian.api.Controllers
                .Where(filter, new { keyword })
                .Page(size, page)
                .Order(orderBy)
-               .PageList<dynamic>(getTotal);
+               .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 });
         }
 

+ 6 - 3
molilian.api/Controllers/public/TkController.cs

@@ -53,6 +53,7 @@ namespace molilian.api.Controllers
             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);
 
 
 
@@ -78,7 +79,7 @@ namespace molilian.api.Controllers
             {
                 return new APIResult(new { success = false, message = "验证错误4" }, APIResultCodeEnum.Unauthorized);
             }
-            return await UnionParseCore.UnionParseAsync(content, channel, commerceType, ip, oaid, riskStrategy, launchScene, 0, clickId, type);
+            return await UnionParseCore.UnionParseAsync(content, channel, commerceType, ip, oaid, riskStrategy, launchScene, 0, special_text, clickId, type);
         }
 
         [HttpPost]
@@ -94,12 +95,13 @@ namespace molilian.api.Controllers
             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);
 
 #if DEBUG
             //ip = "127.0.0.1";
             //oaid = "test-oaid";
 #endif
-            return await UnionParseCore.UnionParseAsync(content, channel, commerceType, ip, oaid, riskStrategy, launchScene, accountid, clickId, type);
+            return await UnionParseCore.UnionParseAsync(content, channel, commerceType, ip, oaid, riskStrategy, launchScene, accountid, special_text, clickId, type);
         }
 
 
@@ -118,6 +120,7 @@ namespace molilian.api.Controllers
             //入参参数。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);
 
 #if DEBUG
             //ip = "127.0.0.1";
@@ -125,7 +128,7 @@ namespace molilian.api.Controllers
 #endif
             for (int i = 0; i < count; i++)
             {
-                _ = await UnionParseCore.UnionParseAsync(content[i], channel, commerceType, ip, oaid, riskStrategy, launchScene, accountid, clickId, type);
+                _ = await UnionParseCore.UnionParseAsync(content[i], channel, commerceType, ip, oaid, riskStrategy, launchScene, accountid, special_text, clickId, type);
             }
             return new APIResult(new { msg = "ok" });
         }

+ 71 - 4
molilian.core/Core/OverrideRuleCore.cs

@@ -1,5 +1,7 @@
 using dodohold.core;
+using System.Collections.Generic;
 using System.Data;
+using System.Security.Cryptography;
 using System.Text.RegularExpressions;
 using YunhuiKit;
 
@@ -16,6 +18,14 @@ namespace molilian.core
 
         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
             {
@@ -81,13 +91,15 @@ namespace molilian.core
             }
         }
 
-        public static async Task<OverrideRuleDTO> ProcessAsync(string content)
+        public static async Task<OverrideRuleDTO> ProcessAsync(string platform, string content, string ip, string oaid, string riskStrategy, int launchScene, int special_text)
         {
+            if (string.IsNullOrEmpty(platform)) return null;
             var list = await ListAsync();
+            if (list == default) return null;
 
             foreach (var item in list)
             {
-                if (!"tk".Equals(item.platform)) continue;
+                if (!platform.Equals(item.platform)) continue;
                 if (string.IsNullOrEmpty(item.original_text)) continue;
 
                 switch (item.rule)
@@ -95,11 +107,66 @@ namespace molilian.core
                     case "regex":
                         Match match = Regex.Match(content, item.original_text);
                         if (!match.Success) continue;
-                        return item;
+                        break;
+                    case "text":
+                        if (!content.Equals(item.original_text)) continue;
+                        break;
                     default:
                         if (!content.Contains(item.original_text)) continue;
-                        return item;
+                        break;
+                }
+                if (item.use_risk_control)
+                {
+                    bool is_ignore = AlimamaPlus.ShouldIgnoreRequest(ip, oaid, riskStrategy, launchScene, out _);
+                    if (is_ignore) continue;
+                }
+
+                if (item.use_special_text == 1 && special_text != 1) continue;
+
+
+                // 每小时调用次数限制
+                if (item.hourly_calls_limit > 0)
+                {
+                    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 (currentHourlyCalls > item.hourly_calls_limit) continue;
+                    }
+                    catch (Exception ex)
+                    {
+                        // Redis错误时记录日志但不阻塞业务
+                        _ = new LoggerLibrary("OverrideRule", "Redis").Info($"Hourly limit check failed: {ex.Message}", ex.StackTrace).SaveAsync();
+                    }
+                }
+
+                // 每日调用次数限制
+                if (item.daily_calls_limit > 0)
+                {
+                    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 (currentDailyCalls >= item.daily_calls_limit) continue;
+                    }
+                    catch (Exception ex)
+                    {
+                        // Redis错误时记录日志但不阻塞业务
+                        _ = new LoggerLibrary("OverrideRule", "Redis").Info($"Daily limit check failed: {ex.Message}", ex.StackTrace).SaveAsync();
+                    }
                 }
+                return item;
             }
             return null;
         }

+ 68 - 31
molilian.core/Core/taoke/UnionParseCore/UnionParseCore.cs

@@ -12,7 +12,7 @@ namespace molilian.core
 {
     public partial class UnionParseCore
     {
-        public static async Task<ActionResult> UnionParseAsync(string content, string channel, int commerceType, string ip, string oaid, string riskStrategy, int launchScene, int accountid = 0, string clickId = "", string type = "")
+        public static async Task<ActionResult> UnionParseAsync(string content, string channel, int commerceType, string ip, string oaid, string riskStrategy, int launchScene, int accountid = 0, int special_text = -1, string clickId = "", string type = "")
 
         {
             content = content.Trim();
@@ -21,9 +21,9 @@ namespace molilian.core
             {
                 return channel switch
                 {
-                    "jd" => await DeeplinkJdParseAsync(content, commerceType, ip, oaid, riskStrategy, launchScene, accountid, clickId),
-                    "pdd" => await DeeplinkPddParseAsync(content, commerceType, ip, oaid, accountid),
-                    "tb" => await DeeplinkTaobaoParseAsync(content, commerceType, ip, oaid, riskStrategy, launchScene, accountid),
+                    "jd" => await DeeplinkJdParseAsync(content, commerceType, ip, oaid, riskStrategy, launchScene, accountid, special_text, clickId),
+                    "pdd" => await DeeplinkPddParseAsync(content, commerceType, ip, oaid, accountid, special_text),
+                    "tb" => await DeeplinkTaobaoParseAsync(content, commerceType, ip, oaid, riskStrategy, launchScene, accountid, special_text),
                 };
             }
 
@@ -31,12 +31,12 @@ namespace molilian.core
             {
                 //"wemeet" => await WemeetParseAsync(content, ip, oaid),
                 //"bdpan" => await PanParseAsync(content, ip, oaid),
-                "jd" => await JdParseAsync(content, type, commerceType, ip, oaid, riskStrategy, launchScene, accountid, clickId),
-                "ks" => await KsParseAsync(content, commerceType, ip, oaid, accountid),
-                "dy" => await DyParseAsync(content, commerceType, ip, oaid),
-                "pdd" => await PddParseAsync(content, type, commerceType, ip, oaid, accountid),
-                "tb" => await TaobaoParseAsync(content, commerceType, ip, oaid, riskStrategy, launchScene, accountid),
-                _ => await DeeplinkParseCore.ParseAsync(content, channel, ip, oaid),
+                "jd" => await JdParseAsync(content, type, commerceType, ip, oaid, riskStrategy, launchScene, accountid, special_text, clickId),
+                "ks" => await KsParseAsync(content, commerceType, ip, oaid, accountid, special_text),
+                "dy" => await DyParseAsync(content, commerceType, ip, oaid, special_text),
+                "pdd" => await PddParseAsync(content, type, commerceType, ip, oaid, accountid, special_text),
+                "tb" => await TaobaoParseAsync(content, commerceType, ip, oaid, riskStrategy, launchScene, accountid, special_text),
+                _ => await DeeplinkParseCore.ParseAsync(content, channel, ip, oaid, special_text),
             };
         }
         //return await DeeplinkParseCore.ParseAsync(content, channel, ip, oaid);
@@ -144,7 +144,7 @@ namespace molilian.core
 
 
         public static async Task<APIResult> TaobaoParseAsync(string content, int commerceType,
-            string ip = "", string oaid = "", string riskStrategy = "", int launchScene = -1,
+            string ip = "", string oaid = "", string riskStrategy = "", int launchScene = -1, int special_text = -1,
             int accountid = 0)
         {
             bool other_aff = false;
@@ -185,25 +185,13 @@ namespace molilian.core
                 bool is_other = AlimamaPlus.MatchOtherInfo(content, arr2);
 
 
-                var overrideContent = await OverrideRuleCore.ProcessAsync(content);
+                var overrideContent = await OverrideRuleCore.ProcessAsync("tb", content, ip, oaid, riskStrategy, launchScene, special_text);
                 if (overrideContent != null)
                 {
-                    bool is_ignore = AlimamaPlus.ShouldIgnoreRequest(ip, oaid, riskStrategy, launchScene, out reason);
-                    if (is_ignore)
-                    {
-                        result.success = false;
-                        result.message = "放弃转链";
-                        result.itemName = "点击打开淘宝APP";
-                        result.reason = reason;
-                        result.subCode = TkSubCodeEnum.TrafficCtrl;
-
-                    }
-                    else
-                    {
-                        result.success = true;
-                        result.deeplink_url = overrideContent.output_text;
-                    }
+                    result.success = true;
+                    result.deeplink_url = overrideContent.output_text;
 
+                    _ = TkLogCore.ParseLogAsync(result);
                     return TaobaoParseOutput(result, swData, new
                     {
                         result.success,
@@ -573,7 +561,7 @@ namespace molilian.core
 
         public static async Task<APIResult> JdParseAsync(string content, string type, int commerceType, string ip, string oaid,
             string riskStrategy = "", int launchScene = -1,
-            int accountid = 0, string clickId = "", CancellationToken cancellationToken = default)
+            int accountid = 0, int special_text = -1, string clickId = "", CancellationToken cancellationToken = default)
         {
             var config = TkConfigCore.Get();
             var result = JdUnionPlus.GetFormattedObject(content, ip, oaid, clickId);
@@ -585,6 +573,18 @@ namespace molilian.core
             bool is_hide = false;
             try
             {
+                var overrideContent = await OverrideRuleCore.ProcessAsync("jd", content, ip, oaid, riskStrategy, launchScene, special_text);
+                if (overrideContent != null)
+                {
+                    result.success = true;
+                    result.message = "OK";
+                    result.commercial = true;
+                    result.deeplink_url = overrideContent.output_text;
+
+                    _ = TkLogCore.ParseLogAsync(result, is_hide);
+                    return JdParseOutput(result);
+                }
+
                 switch (type)
                 {
                     case "goods":
@@ -753,7 +753,7 @@ namespace molilian.core
         }
 
 
-        public static async Task<APIResult> DyParseAsync(string content, int commerceType, string ip, string oaid, CancellationToken cancellationToken = default)
+        public static async Task<APIResult> DyParseAsync(string content, int commerceType, string ip, string oaid, int special_text = -1, CancellationToken cancellationToken = default)
         {
             var result = DyUnionPlus.GetFormattedObject(content, ip, oaid);
             content = content.UrlDecode();
@@ -761,6 +761,16 @@ namespace molilian.core
             bool IfExceptional = false;
             try
             {
+                var overrideContent = await OverrideRuleCore.ProcessAsync("dy", content, ip, oaid, string.Empty, 0, special_text);
+                if (overrideContent != null)
+                {
+                    result.success = true;
+                    result.message = "OK";
+                    result.deeplink_url = overrideContent.output_text;
+
+                    _ = TkLogCore.ParseLogAsync(result);
+                    return new APIResult(result);
+                }
 #if DEBUG
 #else
                 // 2024-06-19 联盟业务下线
@@ -772,6 +782,7 @@ namespace molilian.core
 #endif
 
 
+
                 if (DyUnionPlus.ShouldIgnoreRequest(ip, oaid, out string reason))
                 {
                     result.success = false;
@@ -836,10 +847,23 @@ namespace molilian.core
         }
 
         public static async Task<APIResult> PddParseAsync(string content, string type, int commerceType, string ip, string oaid,
-            int accountid = 0, CancellationToken cancellationToken = default)
+            int accountid = 0, int special_text = -1, CancellationToken cancellationToken = default)
         {
             var result = PddUnionPlus.GetFormattedObject(content, ip, oaid);
 
+
+            var overrideContent = await OverrideRuleCore.ProcessAsync("pdd", content, ip, oaid, string.Empty, 0, special_text);
+            if (overrideContent != null)
+            {
+                result.success = true;
+                result.message = "OK";
+                result.deeplink_url = overrideContent.output_text;
+
+                _ = TkLogCore.ParseLogAsync(result);
+                return PddParseOutput(result);
+            }
+
+
             switch (type)
             {
                 case "goods":
@@ -1034,7 +1058,7 @@ namespace molilian.core
 
 
         public static async Task<APIResult> KsParseAsync(string content, int commerceType, string ip, string oaid,
-            int accountid = 0, CancellationToken cancellationToken = default)
+            int accountid = 0, int special_text = -1, CancellationToken cancellationToken = default)
         {
             var result = KsUnionPlus.GetFormattedObject(content, ip, oaid);
             content = content.UrlDecode();
@@ -1044,6 +1068,19 @@ namespace molilian.core
             {
                 var config = TkConfigCore.Get();
 
+
+                var overrideContent = await OverrideRuleCore.ProcessAsync("ks", content, ip, oaid, string.Empty, 0, special_text);
+                if (overrideContent != null)
+                {
+                    result.success = true;
+                    result.message = "OK";
+                    result.deeplink_url = overrideContent.output_text;
+
+                    _ = TkLogCore.ParseLogAsync(result);
+                    return KsParseOutput(result);
+                }
+
+
                 if (!string.IsNullOrEmpty(config.ks_blacklist_regular))
                 {
                     var blacklist = config.ks_blacklist_regular.Split('\n')

+ 27 - 3
molilian.core/Core/taoke/UnionParseCore/dp2dp.cs

@@ -18,7 +18,7 @@ namespace molilian.core
     {
         public static async Task<APIResult> DeeplinkTaobaoParseAsync(string content, int commerceType,
             string ip = "", string oaid = "", string riskStrategy = "", int launchScene = -1,
-            int accountid = 0)
+            int accountid = 0, int special_text = -1)
         {
             bool other_aff = false;
             AlimamaPlus alimama = null;
@@ -484,7 +484,7 @@ namespace molilian.core
 
         public static async Task<APIResult> DeeplinkJdParseAsync(string content, int commerceType, string ip, string oaid,
             string riskStrategy = "", int launchScene = -1,
-            int accountid = 0, string clickId = "", CancellationToken cancellationToken = default)
+            int accountid = 0, int special_text = -1, string clickId = "", CancellationToken cancellationToken = default)
         {
             var config = TkConfigCore.Get();
             var result = JdUnionPlus.GetFormattedObject(content, ip, oaid, clickId);
@@ -496,8 +496,21 @@ namespace molilian.core
             bool IfExceptional = false;
             try
             {
+                var overrideContent = await OverrideRuleCore.ProcessAsync("jd", content, ip, oaid, riskStrategy, launchScene, special_text);
+                if (overrideContent != null)
+                {
+                    result.success = true;
+                    result.message = "OK";
+                    result.commercial = true;
+                    result.deeplink_url = overrideContent.output_text;
+
+                    _ = TkLogCore.ParseLogAsync(result, false);
+                    return JdParseOutput(result);
+                }
+
                 var parseResult = parseJdDeeplinkInput(content);
 
+
                 if (!parseResult.success)
                 {
                     result.channel = TkChannelEnum.jd;
@@ -650,7 +663,7 @@ namespace molilian.core
 
 
         public static async Task<APIResult> DeeplinkPddParseAsync(string content, int commerceType, string ip, string oaid,
-            int accountid = 0, CancellationToken cancellationToken = default)
+            int accountid = 0, int special_text = -1, CancellationToken cancellationToken = default)
         {
             var result = PddUnionPlus.GetFormattedObject(content, ip, oaid);
             content = content.UrlDecode();
@@ -661,6 +674,17 @@ namespace molilian.core
             {
                 var config = TkConfigCore.Get();
 
+                var overrideContent = await OverrideRuleCore.ProcessAsync("pdd", content, ip, oaid, string.Empty, 0, special_text);
+                if (overrideContent != null)
+                {
+                    result.success = true;
+                    result.message = "OK";
+                    result.deeplink_url = overrideContent.output_text;
+
+                    _ = TkLogCore.ParseLogAsync(result);
+                    return PddParseOutput(result);
+                }
+
                 if (!string.IsNullOrEmpty(config.pdd_blacklist_regular))
                 {
                     var blacklist = config.pdd_blacklist_regular.Split('\n')

+ 24 - 5
molilian.core/Core/tool/DeeplinkParseCore.cs

@@ -35,7 +35,7 @@ namespace molilian.core
             };
         }
 
-        public static async Task<ActionResult> ParseAsync(string content, string channel, string ip, string oaid)
+        public static async Task<ActionResult> ParseAsync(string content, string channel, string ip, string oaid, int special_text = -1)
         {
             var result = GetFormattedObject(content, channel, ip, oaid);
             content = content.Trim();
@@ -56,6 +56,29 @@ namespace molilian.core
                 });
             }
 
+            result.channel_id = rule.channel_id;
+            result.channel_name = rule.channel_name;
+
+
+            var overrideContent = await OverrideRuleCore.ProcessAsync(channel, content, ip, oaid, string.Empty, 0, special_text);
+            if (overrideContent != null)
+            {
+                result.success = true;
+                result.message = "OK";
+                result.deeplink_url = overrideContent.output_text;
+
+                _ = TkLogCore.ParseLogAsync(result);
+                return new APIResult(new
+                {
+                    result.success,
+                    result.message,
+                    channel = result.channel_name,
+                    result.deeplink_url
+                });
+            }
+
+
+
             if (ShouldIgnoreRequest(rule.IgnorePercentageCity, ip, oaid, out string reason))
             {
                 result.success = false;
@@ -70,10 +93,6 @@ namespace molilian.core
                     result.reason,
                 });
             }
-
-            result.channel_id = rule.channel_id;
-            result.channel_name = rule.channel_name;
-
             result = await GenerateDeeplink(content, rule, result);
 
             _ = TkLogCore.ParseLogAsync(result);

+ 30 - 0
molilian.core/DTO/OverrideRuleDTO.cs

@@ -23,5 +23,35 @@ namespace molilian.core
         [JsonConverter(typeof(NullableDateTimeConverter))]
         public DateTime last_time { get; set; }
 
+        /// <summary>
+        /// 备注
+        /// </summary>
+        public string description { get; set; } = string.Empty;
+        [JsonConverter(typeof(NumericBooleanConverter))]
+
+        public bool use_risk_control { get; set; }
+
+        /// <summary>
+        /// 每日最高调用次数限制
+        /// </summary>
+        public int daily_calls_limit { get; set; } = 0;
+
+        /// <summary>
+        /// 每小时最高调用次数限制
+        /// </summary>
+        public int hourly_calls_limit { get; set; } = 0;
+
+
+        [IgnoreInsert, IgnoreUpdate]
+        public int current_daily_calls { get; set; } = 0;
+        [IgnoreInsert, IgnoreUpdate]
+        public int current_hourly_calls { get; set; } = 0;
+
+        /// <summary>
+        /// 是否验证参数:0不限制, 1入参special_text=1 时才匹配
+        /// </summary>
+        public int use_special_text { get; set; }
+        public int sort { get; set; }
+
     }
 }