using dodohold.core; namespace molilian.core { /// /// 隐藏号判断:隐藏号命中的转链只返回 deeplink,照常写转链日志,但不产生任何统计、上报数据 /// public static class HiddenAccountCore { private const int LimitExpireSeconds = 86400; public static Task IsHiddenAsync(TkChannelEnum channel, int accountId) { return IsHiddenAsync(channel.ToString(), accountId); } public static async Task IsHiddenAsync(string channel, int accountId) { if (accountId <= 0) return false; switch ((channel ?? string.Empty).ToLowerInvariant()) { case "tb": var tbAccount = await TkPoolCore.ALLGetOneAsync(accountId); return tbAccount != null && tbAccount.is_hide; case "pdd": var pddAccounts = await PddPoolCore.AllListAsync(); var pddAccount = pddAccounts?.FirstOrDefault(e => e.id == accountId); return pddAccount != null && pddAccount.is_hide; default: return false; } } /// /// 隐藏号专用限制(config 配置,所有隐藏号共用,淘宝/拼多多分开计数): /// 城市过滤 + 同 IP / OAID 每天转成功次数。通过时占用名额,转链结束后调用 CompleteLimitAsync。 /// public static bool TryReserveLimit(TkChannelEnum channel, string ip, string oaid, out HiddenLimitTicket ticket, out string reason) { ticket = new HiddenLimitTicket(); reason = string.Empty; if (TestParseCore.InWhitelist(ip, oaid)) return true; try { var config = TkConfigCore.Get(); if (config == null) return true; if (!string.IsNullOrEmpty(config.hide_ignore_city)) { string? ipInfo = IP2RegionPlus.Search(ip); if (AlimamaPlus.IgnoreRegionIncluded(config.hide_ignore_city, ipInfo, out string regionInfo)) { reason = $"地区控制:{regionInfo}"; return false; } } string date = DateTime.Now.ToString("yyyyMMdd"); if (!TryReserveCount(BuildLimitKey(channel, date, "ip", ip), config.hide_limit_per_ip_24h, ticket)) { ReleaseReserved(ticket); reason = "IP控制"; return false; } if (!TryReserveCount(BuildLimitKey(channel, date, "oaid", oaid), config.hide_limit_per_oaid_24h, ticket)) { ReleaseReserved(ticket); reason = "OAID控制"; return false; } return true; } catch (Exception ex) { // 与全局限制一致:异常时不拦截 _ = new LoggerLibrary("HiddenAccountCore", "TryReserveLimit") .Info(ex.Message, ex.StackTrace) .SaveAsync(); return true; } } /// /// 转链结束:成功则计入当天次数(同步到其他节点),失败则归还占用的名额 /// public static async Task CompleteLimitAsync(HiddenLimitTicket ticket, bool success) { if (ticket == null || ticket.Keys.Count == 0) return; try { if (!success) { ReleaseReserved(ticket); return; } // 本节点在占用时已计数,只同步到其他节点 await EndPointCore.ProcessEndPointNodesAsync(node => { if (string.IsNullOrEmpty(node.redis_server)) return Task.CompletedTask; if (!node.is_public_api) return Task.CompletedTask; if (string.Equals(node.name, EndPointCore.CurrentEndPoint, StringComparison.OrdinalIgnoreCase)) return Task.CompletedTask; try { var redis = RedisClientManager.GetRedisClient(node.redis_server); foreach (var key in ticket.Keys) { redis.IncrBy(key); redis.Expire(key, LimitExpireSeconds); } } catch (Exception ex) { _ = new LoggerLibrary("HiddenAccountCore", "CompleteLimit") .Info(node.name, ex.Message) .SaveAsync(); } return Task.CompletedTask; }); } catch (Exception ex) { _ = new LoggerLibrary("HiddenAccountCore", "CompleteLimit") .Info(ex.Message, ex.StackTrace) .SaveAsync(); } } private static bool TryReserveCount(string key, int limit, HiddenLimitTicket ticket) { if (limit <= 0 || string.IsNullOrEmpty(key)) return true; long count = RedisHelper.IncrBy(key); RedisHelper.Expire(key, LimitExpireSeconds); if (count > limit) { RedisHelper.IncrBy(key, -1); return false; } ticket.Keys.Add(key); return true; } private static void ReleaseReserved(HiddenLimitTicket ticket) { foreach (var key in ticket.Keys) RedisHelper.IncrBy(key, -1); ticket.Keys.Clear(); } private static string BuildLimitKey(TkChannelEnum channel, string date, string type, string value) { if (string.IsNullOrEmpty(value)) return string.Empty; return $":hide_limit:{channel}:{date}:{type}:{value}"; } } /// /// 隐藏号限制占用的名额(Redis 计数 key) /// public sealed class HiddenLimitTicket { public List Keys { get; } = []; } }