using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.Mvc.Filters; using System; using System.Collections.Generic; using System.Linq; using System.Text; using dodohold.core; using Dataoke; using Google.Protobuf.WellKnownTypes; using System.Diagnostics; using System.Text.Json; using TencentCloud.Tcm.V20210413.Models; using System.Security.Cryptography; using System.Data; using System.Threading.Channels; using YunhuiKit; using System.IO; namespace molilian.core { public partial class TkOrderTrackingCore { private const int DefaultLinkSummaryTtlSeconds = 24 * 3600; private const int MaxLinkSummaryTtlSeconds = 7 * 24 * 3600; private const string LinkSummaryCacheVersion = "v2"; public class TkOrderLinkSummaryDTO { public TkChannelEnum channel { get; set; } public int accountId { get; set; } = 0; public string ip { get; set; } = string.Empty; public string oaid { get; set; } = string.Empty; public string mktId { get; set; } = string.Empty; public string itemId { get; set; } = string.Empty; public string itemName { get; set; } = string.Empty; public string taoToken { get; set; } = string.Empty; public string shortLinkurl { get; set; } = string.Empty; public string deeplink_url { get; set; } = string.Empty; public string item_url { get; set; } = string.Empty; public string item_deeplink_url { get; set; } = string.Empty; public DateTime create_time { get; set; } = DateTime.Now; } public static void SaveLinkSummary(TkDataDTO item, TkPoolDTO? account = null) { if (!ShouldSaveLinkSummary(item, account)) return; TkOrderLinkSummaryDTO newData = new() { accountId = item.accountId, channel = item.channel, ip = item.ip, oaid = item.oaid, mktId = item.mktId, itemId = item.itemId, itemName = item.itemName, taoToken = item.taoToken, shortLinkurl = item.shortLinkurl, deeplink_url = item.deeplink_url, item_url = item.item_url, item_deeplink_url = item.item_deeplink_url, create_time = item.create_time, }; var config = TkConfigCore.Get(); int ttl = GetLinkSummaryTtlSeconds(config); string cacheKey = BuildItemSummaryKey(item.accountId, item.itemId); if (!string.IsNullOrEmpty(cacheKey)) RedisHelper.Set(cacheKey, newData, ttl); string mktId = item.mktId; if (!string.IsNullOrEmpty(mktId) && mktId.Contains('-')) { mktId = mktId.Split('-')[^1]; cacheKey = BuildMktSummaryKey(item.accountId, mktId); RedisHelper.Set(cacheKey, newData, ttl); } cacheKey = BuildTitleSummaryKey(item.accountId, item.itemName); if (!string.IsNullOrEmpty(cacheKey)) RedisHelper.Set(cacheKey, newData, ttl); } private static bool ShouldSaveLinkSummary(TkDataDTO item, TkPoolDTO? account) { if (item == null || item.accountId <= 0) return false; if (!item.success) return false; if (string.IsNullOrEmpty(item.itemId)) return false; bool hasLink = !string.IsNullOrEmpty(item.shortLinkurl) || !string.IsNullOrEmpty(item.deeplink_url) || !string.IsNullOrEmpty(item.item_url) || !string.IsNullOrEmpty(item.item_deeplink_url); if (!hasLink) return false; account ??= GetCachedAccount(item.accountId); return account?.enable_order_summary_cache ?? true; } private static TkPoolDTO? GetCachedAccount(int accountId) { try { var list = TkPoolCore.GetCachedAccounts(); return list?.FirstOrDefault(e => e.id == accountId); } catch { return null; } } private static int GetLinkSummaryTtlSeconds(TkConfigDTO config) { long configuredHours = config?.fake_click_ttl ?? 0; long ttl = configuredHours > 0 ? configuredHours * 3600 : DefaultLinkSummaryTtlSeconds; return (int)Math.Clamp(ttl, 3600, MaxLinkSummaryTtlSeconds); } private static string BuildItemSummaryKey(int accountId, string itemId) { if (accountId <= 0 || string.IsNullOrEmpty(itemId)) return string.Empty; return $":cache:order_summary:{LinkSummaryCacheVersion}:{accountId}:item:{itemId}"; } private static string BuildMktSummaryKey(int accountId, string mktId) { if (accountId <= 0 || string.IsNullOrEmpty(mktId)) return string.Empty; return $":cache:order_summary:{LinkSummaryCacheVersion}:{accountId}:mktId:{mktId}"; } private static string BuildTitleSummaryKey(int accountId, string itemTitle) { if (accountId <= 0 || string.IsNullOrEmpty(itemTitle)) return string.Empty; return $":cache:order_summary:{LinkSummaryCacheVersion}:{accountId}:title:{Sha256Hex(itemTitle)}"; } private static string BuildLegacyItemSummaryKey(int accountId, string itemId) { if (accountId <= 0 || string.IsNullOrEmpty(itemId)) return string.Empty; return $":cache:order_summary:{accountId}:{itemId}"; } private static string BuildLegacyMktSummaryKey(int accountId, string mktId) { if (accountId <= 0 || string.IsNullOrEmpty(mktId)) return string.Empty; return $":cache:order_summary:{accountId}:mktId:{mktId}"; } private static string BuildLegacyTitleSummaryKey(int accountId, string itemTitle) { if (accountId <= 0 || string.IsNullOrEmpty(itemTitle)) return string.Empty; return $":cache:order_summary:{accountId}:{itemTitle}"; } private static string Sha256Hex(string value) { byte[] hash = SHA256.HashData(Encoding.UTF8.GetBytes(value)); return Convert.ToHexString(hash).ToLowerInvariant(); } private static async Task GetFirstSummaryAsync(YunhuiKit.RedisClient redis, IEnumerable keys) { foreach (var key in keys.Where(e => !string.IsNullOrEmpty(e))) { var result = await redis.GetAsync(key); if (IsUsableOrderSummary(result)) return result; } return null; } private static bool IsUsableOrderSummary(TkOrderLinkSummaryDTO? summary) { if (summary == null) return false; return !string.IsNullOrEmpty(summary.shortLinkurl) || !string.IsNullOrEmpty(summary.deeplink_url) || !string.IsNullOrEmpty(summary.item_url) || !string.IsNullOrEmpty(summary.item_deeplink_url); } public static async Task<(int, TkOrderLinkSummaryDTO)> GetLinkSummaryAsync(TkPoolDTO account, string itemId, string mktId, string itemTitle) { try { int accountId = account.id; mktId = !string.IsNullOrEmpty(mktId) && mktId.Contains('-') ? mktId.Split('-')[^1] : mktId; // 获取所有关联账户ID(包括当前账户) var relatedAccountIds = new List { accountId }; if (!string.IsNullOrEmpty(account.related_account_ids)) { relatedAccountIds.AddRange(account.related_account_ids .Split(',') .Where(id => !string.IsNullOrWhiteSpace(id)) .Select(int.Parse)); } var tasks = EndPointCore.List() .Where(node => node.is_public_api && !string.IsNullOrEmpty(node.redis_server)) .Select(async node => { try { var redisServer = EndPointCore.GetRedisServer(node); if (string.IsNullOrEmpty(redisServer)) return (0, null); using var scope = await RedisClientFactory.CreateScopeAsync(redisServer); var redis = scope.Client; // 按优先级依次查询不同的缓存key TkOrderLinkSummaryDTO result = null; // 遍历所有关联账户ID foreach (var relatedId in relatedAccountIds) { if (!string.IsNullOrEmpty(mktId)) { result = await GetFirstSummaryAsync(redis, new[] { BuildMktSummaryKey(relatedId, mktId), BuildLegacyMktSummaryKey(relatedId, mktId) }); if (IsUsableOrderSummary(result)) return (1, result); } if (!string.IsNullOrEmpty(itemId)) { result = await GetFirstSummaryAsync(redis, new[] { BuildItemSummaryKey(relatedId, itemId), BuildLegacyItemSummaryKey(relatedId, itemId) }); if (IsUsableOrderSummary(result)) return (2, result); if (!string.IsNullOrEmpty(itemTitle)) { result = await GetFirstSummaryAsync(redis, new[] { BuildTitleSummaryKey(relatedId, itemTitle), BuildLegacyTitleSummaryKey(relatedId, itemTitle) }); if (IsUsableOrderSummary(result)) return (3, result); } } } } catch (Exception ex) { } return (0, null); }); var results = await Task.WhenAll(tasks); return results.FirstOrDefault(r => r != (0, null)); } catch (Exception) { // TODO: 添加日志记录 return (0, null); } } public static async Task RemoveLinkSummaryAsync(int accountId, string itemId, string mktId, string itemTitle) { try { var tasks = EndPointCore.List() .Where(node => node.is_public_api && !string.IsNullOrEmpty(node.redis_server)) .Select(async node => { try { var redisServer = EndPointCore.GetRedisServer(node); if (string.IsNullOrEmpty(redisServer)) return; using var scope = await RedisClientFactory.CreateScopeAsync(redisServer); var redis = scope.Client; mktId = !string.IsNullOrEmpty(mktId) && mktId.Contains('-') ? mktId.Split('-')[^1] : mktId; var keys = new[] { BuildItemSummaryKey(accountId, itemId), BuildLegacyItemSummaryKey(accountId, itemId), BuildMktSummaryKey(accountId, mktId), BuildLegacyMktSummaryKey(accountId, mktId), BuildTitleSummaryKey(accountId, itemTitle), BuildLegacyTitleSummaryKey(accountId, itemTitle) }.Where(k => !string.IsNullOrEmpty(k)); // 批量删除所有相关缓存 await redis.DelAsync(keys.ToArray()); } catch (Exception ex) { } }); await Task.WhenAll(tasks); } catch (Exception) { // TODO: 添加日志记录 } } //public static async void RemoveLinkSummary(int accountId, string itemId, string mktId, string itemTitle) //{ // await EndPointCore.ProcessEndPointNodesAsync(node => // { // if (!node.is_public_api) return Task.CompletedTask; // if (string.IsNullOrEmpty(node.redis_server)) return Task.CompletedTask; // var redis = RedisClientManager.GetRedisClient(node.redis_server); // string cacheKey = $":cache:order_summary:{accountId}:{itemId}"; // redis.Del(cacheKey); // cacheKey = $":cache:order_summary:{accountId}:mktId:{mktId}"; // redis.Del(cacheKey); // cacheKey = $":cache:order_summary:{accountId}:{itemTitle}"; // redis.Del(cacheKey); // return Task.CompletedTask; // }); //} public static async Task MatchOrder(TkPoolDTO account, TkOrderDetailDTO item) { DateTime now = DateTime.Now; if (item.tbPaidTime < now.AddDays(-24)) return false; (int match_type, var summary) = await GetLinkSummaryAsync(account, item.itemId, item.mktId, item.itemTitle); if (summary == null) return false; var config = TkConfigCore.Get(); Random rand = new(); int click_num = rand.Next(config.fake_click_min, config.fake_click_max + 1); if (account.fake_click_min != 0 && account.fake_click_max != 0) click_num = rand.Next(account.fake_click_min, account.fake_click_max + 1); DateTime paidTime = item.tbPaidTime; if (paidTime < summary.create_time) return false; if (paidTime < now.AddDays(-24)) return false; int minutes = now.Minute / 10 * 10; DateTime rounded = new(now.Year, now.Month, now.Day, now.Hour, minutes, 0); string batchId = rounded.ToString("yyyyMMddHHmm"); string accountName = item.accountName; string shortLinkUrl = !string.IsNullOrEmpty(summary.item_url) ? summary.item_url : summary.shortLinkurl; string deeplinkUrl = !string.IsNullOrEmpty(summary.item_deeplink_url) ? summary.item_deeplink_url : summary.deeplink_url; switch (account.fake_click_link_type) { case FakeClickLinkType.Deeplink: shortLinkUrl = string.Empty; break; case FakeClickLinkType.H5: deeplinkUrl = string.Empty; break; } var exist = new DBContext.Table("tk_order_tracking").Fields("id").Get("tradeId=@tradeId", new { item.tradeId }); DateTime expTime = now.Hour >= 21 ? now.AddHours(3) : now.Date.AddDays(1); var data = new TkOrderTrackingDTO() { match_type = match_type, channel = summary.channel, accountId = item.accountId, accountName = accountName, batchId = batchId, ip = summary.ip, oaid = summary.oaid, mktId = summary.mktId, itemId = summary.itemId, itemName = summary.itemName, taoToken = summary.taoToken, shortLinkUrl = shortLinkUrl, deeplinkUrl = deeplinkUrl, tradeId = item.tradeId, tradeParentId = item.tradeParentId, create_time = summary.create_time, click_time = item.clickTime, paid_time = item.tbPaidTime, exp_time = expTime, click_num = click_num, }; if (exist == null) { using var conn = DBContext.GetOpenConnection(); conn.Insert(data); } //RemoveLinkSummary(item.accountId, item.itemId, item.mktId, item.itemTitle); await RemoveLinkSummaryAsync(item.accountId, item.itemId, item.mktId, item.itemTitle); return true; } } }