Kaynağa Gözat

feat(隐藏号): 同一隐藏号当天同 ip/oaid 只转成功一次

- 淘宝(含 dp)、拼多多(含商品、dp)选到隐藏号后先占位 ip/oaid,已转成功或并发进行中直接放弃转链(频次限制)
- 转链成功标记当天已转并同步到所有对外节点,失败释放占位
- 淘宝放弃时归还 QPS 名额,放弃响应不输出埋点
dodo hold 1 gün önce
ebeveyn
işleme
79d66003b7

+ 81 - 0
molilian.core/Core/HiddenAccountCore.cs

@@ -1,3 +1,6 @@
+using CSRedis;
+using dodohold.core;
+
 namespace molilian.core
 {
     /// <summary>
@@ -5,6 +8,11 @@ namespace molilian.core
     /// </summary>
     public static class HiddenAccountCore
     {
+        // 同一隐藏号当天同 ip / oaid 只转成功一次
+        private const int OnceDoneExpireSeconds = 86400;
+        // 转链进行中的占位,防止同一用户并发请求重复转成功
+        private const int OncePendingExpireSeconds = 120;
+
         public static Task<bool> IsHiddenAsync(TkChannelEnum channel, int accountId)
         {
             return IsHiddenAsync(channel.ToString(), accountId);
@@ -27,5 +35,78 @@ namespace molilian.core
                     return false;
             }
         }
+
+        /// <summary>
+        /// 占用隐藏号当天的 ip / oaid 名额。当天已转成功或正在转链时返回 false。
+        /// </summary>
+        public static bool TryReserveOnce(TkChannelEnum channel, int accountId, string ip, string oaid)
+        {
+            var keys = BuildOnceKeys(channel, accountId, ip, oaid);
+            var reserved = new List<string>();
+            foreach (var key in keys)
+            {
+                if (!RedisHelper.Set(key, "pending", OncePendingExpireSeconds, RedisExistence.Nx))
+                {
+                    if (reserved.Count > 0) RedisHelper.Del(reserved.ToArray());
+                    return false;
+                }
+                reserved.Add(key);
+            }
+            return true;
+        }
+
+        /// <summary>
+        /// 转链结束:成功则标记当天已转(同步到所有节点),失败则释放占位
+        /// </summary>
+        public static async Task CompleteOnceAsync(TkChannelEnum channel, int accountId, string ip, string oaid, bool success)
+        {
+            var keys = BuildOnceKeys(channel, accountId, ip, oaid);
+            if (keys.Count == 0) return;
+
+            try
+            {
+                if (!success)
+                {
+                    RedisHelper.Del(keys.ToArray());
+                    return;
+                }
+
+                foreach (var key in keys) RedisHelper.Set(key, "done", OnceDoneExpireSeconds);
+
+                await EndPointCore.ProcessEndPointNodesAsync(node =>
+                {
+                    if (string.IsNullOrEmpty(node.redis_server)) return Task.CompletedTask;
+                    if (!node.is_public_api) return Task.CompletedTask;
+
+                    try
+                    {
+                        var redis = RedisClientManager.GetRedisClient(node.redis_server);
+                        foreach (var key in keys) redis.Set(key, "done", OnceDoneExpireSeconds);
+                    }
+                    catch (Exception ex)
+                    {
+                        _ = new LoggerLibrary("HiddenAccountCore", "CompleteOnce")
+                            .Info(node.name, ex.Message)
+                            .SaveAsync();
+                    }
+                    return Task.CompletedTask;
+                });
+            }
+            catch (Exception ex)
+            {
+                _ = new LoggerLibrary("HiddenAccountCore", "CompleteOnce")
+                    .Info(ex.Message, ex.StackTrace)
+                    .SaveAsync();
+            }
+        }
+
+        private static List<string> BuildOnceKeys(TkChannelEnum channel, int accountId, string ip, string oaid)
+        {
+            string prefix = $":hide_once:{channel}_{accountId}:{DateTime.Now:yyyyMMdd}";
+            var keys = new List<string>();
+            if (!string.IsNullOrEmpty(ip)) keys.Add($"{prefix}:ip:{ip}");
+            if (!string.IsNullOrEmpty(oaid)) keys.Add($"{prefix}:oaid:{oaid}");
+            return keys;
+        }
     }
 }

+ 84 - 0
molilian.core/Core/taoke/UnionParseCore/UnionParseCore.cs

@@ -451,6 +451,7 @@ namespace molilian.core
             bool other_aff = false;
             AlimamaPlus alimama = null;
             TkDataDTO? hiddenAccountFailure = null;
+            TkPoolDTO? hiddenOnceAccount = null;
             //content = content.UrlDecode();
             var result = AlimamaPlus.GetFormattedObject(request);
 
@@ -765,6 +766,38 @@ namespace molilian.core
                         result.accountId);
                 }
 
+                //============================== 放弃转链-隐藏号同 ip/oaid 当天只转一次 ==============================
+                if (account.is_hide)
+                {
+                    if (!HiddenAccountCore.TryReserveOnce(TkChannelEnum.tb, account.id, request.Ip, request.Oaid))
+                    {
+                        if (request.AccountId <= 0) TkPoolCore.ReleaseQps(account, qpsAcquiredSinceTicks);
+                        result.success = false;
+                        result.message = "放弃转链";
+                        result.reason = "频次限制";
+                        result.subCode = TkSubCodeEnum.TrafficCtrl;
+                        result.itemName = "点击打开淘宝APP";
+
+                        _ = TkLogCore.ParseLogAsync(result);
+                        return TaobaoParseOutput(result, swData, new
+                        {
+                            success = ValidateDeeplinkIfRequired(result.success, result.deeplink_url),
+                            result.commercial,
+                            result.with_middle_page,
+                            result.message,
+                            result.content,
+                            sub_message = result.reason,
+                            home_page = "tbopen://m.taobao.com/tbopen/index.html".Equals(result.deeplink_url),
+                            result.couponAmount,
+                            result.taoToken,
+                            result.shortLinkurl,
+                            other_aff,
+                            result.deeplink_url,
+                        });
+                    }
+                    hiddenOnceAccount = account;
+                }
+
 
                 alimama = new AlimamaPlus(account);
                 hiddenAccountFailure = account.is_hide ? CreateTaobaoFailureResult(result) : null;
@@ -836,6 +869,11 @@ namespace molilian.core
                     result.itemName = "点击打开淘宝APP";
                 }
             }
+            // 隐藏号:转链成功则当天该 ip/oaid 不再转,失败则释放占位
+            if (hiddenOnceAccount != null)
+            {
+                _ = HiddenAccountCore.CompleteOnceAsync(TkChannelEnum.tb, hiddenOnceAccount.id, request.Ip, request.Oaid, result.success);
+            }
             _ = TkLogCore.ParseLogAsync(result, alimama);
 
             switch (result.reason)
@@ -1407,6 +1445,7 @@ namespace molilian.core
         {
             var result = PddUnionPlus.GetFormattedObject(request);
             PddDataDTO? hiddenAccountFailure = null;
+            PddPoolDTO? hiddenOnceAccount = null;
 
             var overrideContent = await OverrideRuleCore.ProcessAsync(request);
             if (overrideContent != null)
@@ -1553,6 +1592,23 @@ namespace molilian.core
                         result.accountId);
                 }
 
+                //============================== 放弃转链-隐藏号同 ip/oaid 当天只转一次 ==============================
+                if (account.is_hide)
+                {
+                    if (!HiddenAccountCore.TryReserveOnce(TkChannelEnum.pdd, account.id, request.Ip, request.Oaid))
+                    {
+                        result.success = false;
+                        result.message = "放弃转链";
+                        result.reason = "频次限制";
+                        result.subCode = (int)TkSubCodeEnum.TrafficCtrl;
+                        result.deeplink_url = string.IsNullOrEmpty(result.shortLinkurl) ? string.Empty : PddUnionPlus.GetDeeplink(result.shortLinkurl);
+                        _ = TkLogCore.ParseLogAsync(result);
+                        // 传入隐藏号失败对象仅用于不输出埋点
+                        return PddParseOutput(result, APIResultCodeEnum.OK, CreatePddFailureResult(result));
+                    }
+                    hiddenOnceAccount = account;
+                }
+
                 // 更新当前日使用统计
                 PddPoolCore.UpdateAccountUsage(account.id);
 
@@ -1587,6 +1643,11 @@ namespace molilian.core
                     result.reason = "转链接口异常";
                 }
             }
+            // 隐藏号:转链成功则当天该 ip/oaid 不再转,失败则释放占位
+            if (hiddenOnceAccount != null)
+            {
+                _ = HiddenAccountCore.CompleteOnceAsync(TkChannelEnum.pdd, hiddenOnceAccount.id, request.Ip, request.Oaid, result.success);
+            }
             _ = TkLogCore.ParseLogAsync(result);
             return PddParseOutput(result, IfExceptional ? APIResultCodeEnum.NotAcceptable : APIResultCodeEnum.OK, hiddenAccountFailure);
 
@@ -1595,6 +1656,7 @@ namespace molilian.core
         {
             var result = PddUnionPlus.GetFormattedObject(request);
             PddDataDTO? hiddenAccountFailure = null;
+            PddPoolDTO? hiddenOnceAccount = null;
 
             string content = request.Content;
             content = content.UrlDecode();
@@ -1754,6 +1816,23 @@ namespace molilian.core
                         result.accountId);
                 }
 
+                //============================== 放弃转链-隐藏号同 ip/oaid 当天只转一次 ==============================
+                if (account.is_hide)
+                {
+                    if (!HiddenAccountCore.TryReserveOnce(TkChannelEnum.pdd, account.id, request.Ip, request.Oaid))
+                    {
+                        result.success = false;
+                        result.message = "放弃转链";
+                        result.reason = "频次限制";
+                        result.subCode = (int)TkSubCodeEnum.TrafficCtrl;
+                        result.deeplink_url = string.IsNullOrEmpty(result.shortLinkurl) ? string.Empty : PddUnionPlus.GetDeeplink(result.shortLinkurl);
+                        _ = TkLogCore.ParseLogAsync(result);
+                        // 传入隐藏号失败对象仅用于不输出埋点
+                        return PddParseOutput(result, APIResultCodeEnum.OK, CreatePddFailureResult(result));
+                    }
+                    hiddenOnceAccount = account;
+                }
+
                 // 更新当前日使用统计
                 PddPoolCore.UpdateAccountUsage(account.id);
 
@@ -1792,6 +1871,11 @@ namespace molilian.core
                     result.reason = "转链接口异常";
                 }
             }
+            // 隐藏号:转链成功则当天该 ip/oaid 不再转,失败则释放占位
+            if (hiddenOnceAccount != null)
+            {
+                _ = HiddenAccountCore.CompleteOnceAsync(TkChannelEnum.pdd, hiddenOnceAccount.id, request.Ip, request.Oaid, result.success);
+            }
             _ = TkLogCore.ParseLogAsync(result);
             return PddParseOutput(result, IfExceptional ? APIResultCodeEnum.NotAcceptable : APIResultCodeEnum.OK, hiddenAccountFailure);
 

+ 63 - 0
molilian.core/Core/taoke/UnionParseCore/dp2dp.cs

@@ -21,6 +21,7 @@ namespace molilian.core
             bool other_aff = false;
             AlimamaPlus alimama = null;
             TkDataDTO? hiddenAccountFailure = null;
+            TkPoolDTO? hiddenOnceAccount = null;
             var result = AlimamaPlus.GetFormattedObject(request);
 
             result.riskStrategy = request.RiskStrategy;
@@ -415,6 +416,39 @@ namespace molilian.core
                 result.accountName = account.company;
                 if (account.is_hide) _ = TracksCore.RevertParseRequestAsync(request);
 
+                //============================== 放弃转链-隐藏号同 ip/oaid 当天只转一次 ==============================
+                if (account.is_hide)
+                {
+                    if (!HiddenAccountCore.TryReserveOnce(TkChannelEnum.tb, account.id, request.Ip, request.Oaid))
+                    {
+                        if (request.AccountId <= 0) TkPoolCore.ReleaseQps(account, qpsAcquiredSinceTicks);
+                        result.success = false;
+                        result.message = "放弃转链";
+                        result.reason = "频次限制";
+                        result.subCode = TkSubCodeEnum.TrafficCtrl;
+                        result.itemName = "点击打开淘宝APP";
+                        result.deeplink_url = string.Empty;
+
+                        _ = TkLogCore.ParseLogAsync(result);
+                        return TaobaoParseOutput(result, swData, new
+                        {
+                            success = ValidateDeeplinkIfRequired(result.success, result.deeplink_url),
+                            result.commercial,
+                            result.with_middle_page,
+                            result.message,
+                            sub_message = result.reason,
+                            home_page = "tbopen://m.taobao.com/tbopen/index.html".Equals(result.deeplink_url),
+                            result.content,
+                            result.couponAmount,
+                            result.taoToken,
+                            result.shortLinkurl,
+                            other_aff,
+                            result.deeplink_url,
+                        });
+                    }
+                    hiddenOnceAccount = account;
+                }
+
                 alimama = new AlimamaPlus(account);
                 hiddenAccountFailure = account.is_hide ? CreateTaobaoFailureResult(result) : null;
 
@@ -486,6 +520,12 @@ namespace molilian.core
                 }
             }
 
+            // 隐藏号:转链成功则当天该 ip/oaid 不再转,失败则释放占位
+            if (hiddenOnceAccount != null)
+            {
+                _ = HiddenAccountCore.CompleteOnceAsync(TkChannelEnum.tb, hiddenOnceAccount.id, request.Ip, request.Oaid, result.success);
+            }
+
             if (!result.success)
             {
                 result.deeplink_url = string.Empty;
@@ -722,6 +762,7 @@ namespace molilian.core
         {
             var result = PddUnionPlus.GetFormattedObject(request);
             PddDataDTO? hiddenAccountFailure = null;
+            PddPoolDTO? hiddenOnceAccount = null;
             var content = request.Content.UrlDecode();
             result.rawContent = content;
 
@@ -848,6 +889,23 @@ namespace molilian.core
                 result.proxy_node = account.nodeName;
                 if (account.is_hide) _ = TracksCore.RevertParseRequestAsync(request);
 
+                //============================== 放弃转链-隐藏号同 ip/oaid 当天只转一次 ==============================
+                if (account.is_hide)
+                {
+                    if (!HiddenAccountCore.TryReserveOnce(TkChannelEnum.pdd, account.id, request.Ip, request.Oaid))
+                    {
+                        result.success = false;
+                        result.message = "放弃转链";
+                        result.reason = "频次限制";
+                        result.subCode = (int)TkSubCodeEnum.TrafficCtrl;
+                        result.deeplink_url = PddUnionPlus.GetDeeplink(result.shortLinkurl);
+                        _ = TkLogCore.ParseLogAsync(result);
+                        // 传入隐藏号失败对象仅用于不输出埋点
+                        return PddParseOutput(result, APIResultCodeEnum.OK, CreatePddFailureResult(result));
+                    }
+                    hiddenOnceAccount = account;
+                }
+
                 // 更新当前日使用统计
                 PddPoolCore.UpdateAccountUsage(account.id);
 
@@ -883,6 +941,11 @@ namespace molilian.core
                     result.reason = "转链接口异常";
                 }
             }
+            // 隐藏号:转链成功则当天该 ip/oaid 不再转,失败则释放占位
+            if (hiddenOnceAccount != null)
+            {
+                _ = HiddenAccountCore.CompleteOnceAsync(TkChannelEnum.pdd, hiddenOnceAccount.id, request.Ip, request.Oaid, result.success);
+            }
             _ = TkLogCore.ParseLogAsync(result);
             return PddParseOutput(result, IfExceptional ? APIResultCodeEnum.NotAcceptable : APIResultCodeEnum.OK, hiddenAccountFailure);
         }