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; } = [];
}
}