HiddenAccountCore.cs 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. using dodohold.core;
  2. namespace molilian.core
  3. {
  4. /// <summary>
  5. /// 隐藏号判断:隐藏号命中的转链只返回 deeplink,照常写转链日志,但不产生任何统计、上报数据
  6. /// </summary>
  7. public static class HiddenAccountCore
  8. {
  9. private const int LimitExpireSeconds = 86400;
  10. public static Task<bool> IsHiddenAsync(TkChannelEnum channel, int accountId)
  11. {
  12. return IsHiddenAsync(channel.ToString(), accountId);
  13. }
  14. public static async Task<bool> IsHiddenAsync(string channel, int accountId)
  15. {
  16. if (accountId <= 0) return false;
  17. switch ((channel ?? string.Empty).ToLowerInvariant())
  18. {
  19. case "tb":
  20. var tbAccount = await TkPoolCore.ALLGetOneAsync(accountId);
  21. return tbAccount != null && tbAccount.is_hide;
  22. case "pdd":
  23. var pddAccounts = await PddPoolCore.AllListAsync();
  24. var pddAccount = pddAccounts?.FirstOrDefault(e => e.id == accountId);
  25. return pddAccount != null && pddAccount.is_hide;
  26. default:
  27. return false;
  28. }
  29. }
  30. /// <summary>
  31. /// 隐藏号专用限制(config 配置,所有隐藏号共用,淘宝/拼多多分开计数):
  32. /// 城市过滤 + 同 IP / OAID 每天转成功次数。通过时占用名额,转链结束后调用 CompleteLimitAsync。
  33. /// </summary>
  34. public static bool TryReserveLimit(TkChannelEnum channel, string ip, string oaid, out HiddenLimitTicket ticket, out string reason)
  35. {
  36. ticket = new HiddenLimitTicket();
  37. reason = string.Empty;
  38. if (TestParseCore.InWhitelist(ip, oaid)) return true;
  39. try
  40. {
  41. var config = TkConfigCore.Get();
  42. if (config == null) return true;
  43. if (!string.IsNullOrEmpty(config.hide_ignore_city))
  44. {
  45. string? ipInfo = IP2RegionPlus.Search(ip);
  46. if (AlimamaPlus.IgnoreRegionIncluded(config.hide_ignore_city, ipInfo, out string regionInfo))
  47. {
  48. reason = $"地区控制:{regionInfo}";
  49. return false;
  50. }
  51. }
  52. string date = DateTime.Now.ToString("yyyyMMdd");
  53. if (!TryReserveCount(BuildLimitKey(channel, date, "ip", ip), config.hide_limit_per_ip_24h, ticket))
  54. {
  55. ReleaseReserved(ticket);
  56. reason = "IP控制";
  57. return false;
  58. }
  59. if (!TryReserveCount(BuildLimitKey(channel, date, "oaid", oaid), config.hide_limit_per_oaid_24h, ticket))
  60. {
  61. ReleaseReserved(ticket);
  62. reason = "OAID控制";
  63. return false;
  64. }
  65. return true;
  66. }
  67. catch (Exception ex)
  68. {
  69. // 与全局限制一致:异常时不拦截
  70. _ = new LoggerLibrary("HiddenAccountCore", "TryReserveLimit")
  71. .Info(ex.Message, ex.StackTrace)
  72. .SaveAsync();
  73. return true;
  74. }
  75. }
  76. /// <summary>
  77. /// 转链结束:成功则计入当天次数(同步到其他节点),失败则归还占用的名额
  78. /// </summary>
  79. public static async Task CompleteLimitAsync(HiddenLimitTicket ticket, bool success)
  80. {
  81. if (ticket == null || ticket.Keys.Count == 0) return;
  82. try
  83. {
  84. if (!success)
  85. {
  86. ReleaseReserved(ticket);
  87. return;
  88. }
  89. // 本节点在占用时已计数,只同步到其他节点
  90. await EndPointCore.ProcessEndPointNodesAsync(node =>
  91. {
  92. if (string.IsNullOrEmpty(node.redis_server)) return Task.CompletedTask;
  93. if (!node.is_public_api) return Task.CompletedTask;
  94. if (string.Equals(node.name, EndPointCore.CurrentEndPoint, StringComparison.OrdinalIgnoreCase)) return Task.CompletedTask;
  95. try
  96. {
  97. var redis = RedisClientManager.GetRedisClient(node.redis_server);
  98. foreach (var key in ticket.Keys)
  99. {
  100. redis.IncrBy(key);
  101. redis.Expire(key, LimitExpireSeconds);
  102. }
  103. }
  104. catch (Exception ex)
  105. {
  106. _ = new LoggerLibrary("HiddenAccountCore", "CompleteLimit")
  107. .Info(node.name, ex.Message)
  108. .SaveAsync();
  109. }
  110. return Task.CompletedTask;
  111. });
  112. }
  113. catch (Exception ex)
  114. {
  115. _ = new LoggerLibrary("HiddenAccountCore", "CompleteLimit")
  116. .Info(ex.Message, ex.StackTrace)
  117. .SaveAsync();
  118. }
  119. }
  120. private static bool TryReserveCount(string key, int limit, HiddenLimitTicket ticket)
  121. {
  122. if (limit <= 0 || string.IsNullOrEmpty(key)) return true;
  123. long count = RedisHelper.IncrBy(key);
  124. RedisHelper.Expire(key, LimitExpireSeconds);
  125. if (count > limit)
  126. {
  127. RedisHelper.IncrBy(key, -1);
  128. return false;
  129. }
  130. ticket.Keys.Add(key);
  131. return true;
  132. }
  133. private static void ReleaseReserved(HiddenLimitTicket ticket)
  134. {
  135. foreach (var key in ticket.Keys) RedisHelper.IncrBy(key, -1);
  136. ticket.Keys.Clear();
  137. }
  138. private static string BuildLimitKey(TkChannelEnum channel, string date, string type, string value)
  139. {
  140. if (string.IsNullOrEmpty(value)) return string.Empty;
  141. return $":hide_limit:{channel}:{date}:{type}:{value}";
  142. }
  143. }
  144. /// <summary>
  145. /// 隐藏号限制占用的名额(Redis 计数 key)
  146. /// </summary>
  147. public sealed class HiddenLimitTicket
  148. {
  149. public List<string> Keys { get; } = [];
  150. }
  151. }