using molilian.core; using dodohold.core; using Dapper; using Microsoft.AspNetCore.Mvc; using Org.BouncyCastle.Ocsp; using System.Collections.Concurrent; using System.Diagnostics; using System.Text.Json; using System.Runtime.InteropServices; using System.Net; using System.Threading; using System.Data; using Microsoft.AspNetCore.Mvc.RazorPages; using TencentCloud.Soe.V20180724.Models; using static dodohold.core.ZTOExpress.CreateOrderArgs; using static Spire.Xls.Core.Spreadsheet.HTMLOptions; using System.Net.Http.Json; using System.Text.RegularExpressions; using System.Text; using TencentCloud.Oceanus.V20190422.Models; using YunhuiKit; using TencentCloud.Cdn.V20180606.Models; namespace molilian.api.Controllers { [ApiController] [Route("[controller]/[action]")] public class TestController : ControllerBase { private const string JdStressDeeplinkContent = "openapp.jdmobile://virtual?params=%7B%22category%22%3A%22jump%22%2C%22des%22%3A%22productDetail%22%2C%22skuId%22%3A%2210139003162781%22%2C%22sourceType%22%3A%22Item%22%2C%22sourceValue%22%3A%22view-ware%22%7D"; protected IHttpContextAccessor _accessor; public TestController(IHttpContextAccessor accessor) { _accessor = accessor; } [HttpGet] public async Task jd_unsafe_parse_stress([FromQuery] int t = 5, [FromQuery] int time = 60, [FromQuery] string ip = "127.0.0.1", [FromQuery] string oaid = "", [FromQuery] bool wait = false) { t = t <= 0 ? 5 : t; time = time <= 0 ? 60 : time; string runId = DateTime.Now.ToString("yyyyMMdd_HHmmss_fff"); string logName = $"run_{runId}_t{t}_time{time}"; string startLogStatus = await SaveJdStressStartLogAsync(runId, logName, t, time, ip, oaid, wait); if (wait) { var result = await RunJdStressAsync(runId, logName, t, time, ip, oaid, startLogStatus, CancellationToken.None); return new APIResult(result); } _ = Task.Run(async () => { try { await RunJdStressAsync(runId, logName, t, time, ip, oaid, startLogStatus, CancellationToken.None); } catch (Exception ex) { _ = new LoggerLibrary("jd_api_stress", $"{logName}_runner_error") .Info(ex.Message, ex.StackTrace) .SaveAsync(); } }); return new APIResult(new { success = true, message = "后台压测已启动", runId, requestPerSecond = t, timeSeconds = time, expectedRequests = (long)t * time, wait, log = new { type = "LoggerLibrary", dir = "jd_api_stress", name = logName, status = startLogStatus } }); } [HttpGet] public async Task ecs_list_test() { var list = AliyunPoolCore.EcsList(); return new APIResult(new { success = true, msg = "ok", list }); } [HttpGet] public async Task testReconnectionRedis() { string cacheKey = "test"; RedisKit.SetAsync(cacheKey, 1, 3600); string val = await RedisKit.GetAsync(cacheKey); return new APIResult(new { success = "ok", val }); } [HttpGet] public async Task backfill_track_parse_metrics([FromQuery] string reportDate = "") { DateTime targetDate = DateTime.Now.Date; if (!string.IsNullOrWhiteSpace(reportDate) && !DateTime.TryParse(reportDate, out targetDate)) { return new APIResult(new { success = false, message = "reportDate格式错误,请使用 yyyy-MM-dd" }); } var result = await TracksCore.BackfillParseMetricCountersAsync(targetDate); return new APIResult(new { success = true, message = "ok", data = result }); } [HttpGet] public async Task ChangePublicIpByName(string nodeName) { var proxy_node = new DBContext.Table("proxy_nodes").Get("nodeName=@nodeName", new { nodeName }); if (proxy_node == null) return new APIResult(new { success = false, msg = "没有匹配的 proxy_nodes 记录" }); int aliyun_id = proxy_node.aliyun_id; string proxy_server = proxy_node.server; var account = new DBContext.Table("aliyun_pool").Get(aliyun_id); if (account == null) return new APIResult(new { success = false, msg = "没有匹配的 aliyun_pool 记录" }); var uri = new Uri(proxy_server); string privateIp = uri.Host; AliyunCore core = new AliyunCore(account); var success = await core.ChangePublicIpAsync(privateIp); return new APIResult(new { success = "ok" }); } [HttpGet] public async Task ChangePublicIp(int id) { id = id >= 20000 ? id - 20000 : id; var proxy_node = new DBContext.Table("proxy_nodes").Get(id); if (proxy_node == null) return new APIResult(new { success = false, msg = "没有匹配的 proxy_nodes 记录" }); int aliyun_id = proxy_node.aliyun_id; string proxy_server = proxy_node.server; var account = new DBContext.Table("aliyun_pool").Get(aliyun_id); if (account == null) return new APIResult(new { success = false, msg = "没有匹配的 aliyun_pool 记录" }); var uri = new Uri(proxy_server); string privateIp = uri.Host; AliyunCore core = new AliyunCore(account); var success = await core.ChangePublicIpAsync(privateIp); return new APIResult(new { success = "ok" }); } [HttpGet] //第一部 先加一个网卡, public async Task CreateNetworkInterface(int id, string ecsid) { AliyunCore core = new AliyunCore(id); //创建弹性网卡并绑定公网IP var success = core.EcsCreateNetworkInterface("cn-beijing", ecsid); return new APIResult(new { success }); } [HttpGet] //第二部 给网卡绑定很多个辅助ip public async Task EcsAssignPrivateIpAddresses(int id, string ecsid, int count, bool isSecondary = true) { AliyunCore core = new AliyunCore(id); //创建弹性网卡并绑定公网IP var success = core.EcsAssignPrivateIpAddresses("cn-beijing", ecsid, count, !isSecondary); return new APIResult(new { success }); } [HttpGet] public async Task QueryAccountBalance() { AliyunPlus plus = new AliyunPlus("LTAI5tQbkTjtULQcrWGaw2VJ", "UIkkolVVEddooOKOIUByCqymZkK6ZA"); var response = plus.QueryAccountBalance(); var balance = response.Body.Data.AvailableAmount; return new APIResult(new { response }); } //[HttpGet] //public async Task test(string table = "tk_parse_logs_shop", int count = 100) //{ // //table = "tk_parse_logs_shop"; // //table = "tk_parse_logs_live"; // //table = "tk_parse_logs_video"; // string filter = "success=0 AND message='放弃转链' AND reason='非标准链接'"; // for (int i = 0; i < 100; i++) // { // try // { // var list = new DBContext.Table(table) // .Where(filter, new { }) // .Limit(count).Select(); // if (!list.Any()) // { // return new APIResult(new // { // success = false, // message = "所有任务完成" // }); // } // foreach (var item in list) // { // TkPoolDTO? account = TkPoolCore.GetOne(TkPoolCore.TkAction.parse); // if (account == null) // { // return new APIResult(new // { // success = false, // message = "没有工作账号" // }); // } // var result = item.Convert2Json().Convert2Object(); // result.accountId = account.id; // result.accountName = account.company; // var alimama = new AlimamaPlus(account); // int timeout = alimama._config.rt_max; // using var cts = new CancellationTokenSource(); // cts.CancelAfter(timeout); // result = await alimama.UnionParseAsync(item.content, result); // string reason = "非标准链接".Equals(result.reason) ? "非标准链接ok" : result.reason; // new DBContext.Table(table) // .Add("linkType", (int)result.link_type) // .Add("rawContent", result.rawContent) // .Add("success", result.success) // .Add("message", result.message) // .Add("reason", reason) // .Add("content", result.content) // .Add("itemId", result.itemId) // .Add("itemName", result.itemName) // .Add("pic", result.pic) // .Add("couponAmount", result.couponAmount) // .Add("promotionPrice", result.promotionPrice) // .Add("taoToken", result.taoToken) // .Add("shortLinkurl", result.shortLinkurl) // .Add("deeplink_url", result.deeplink_url) // .Add("num_iid", result.num_iid) // .Add("elapsedTime", result.elapsedTime) // .Add("elapsedTime2", result.elapsedTime2) // .Add("elapsedTime3", result.elapsedTime3) // .Add("subCode", result.subCode) // .Where("id=@id", new { item.id }) // .Update(); // } // } // catch // { // } // } // return new APIResult(new // { // success = true, // message = "ok" // }); //} [HttpGet] public async Task redu_douyin(string command) { ReduPlus plus = new ReduPlus("", "", ""); var result = await plus.DouyinParse(command); return Content(result.Convert2Json()); } [HttpGet] public async Task redu_kuaishou(string command) { ReduPlus plus = new ReduPlus("", "", ""); var result = await plus.KuaishouParse(command); return Content(result.Convert2Json()); } [HttpPost] public async Task test2([FromForm] string jsonContent, [FromForm] string testText) { // 用于存储输出结果的StringBuilder StringBuilder outputBuilder = new StringBuilder(); // 解析JSON数据 JsonDocument jsonDoc = JsonDocument.Parse(jsonContent); // 获取根元素 JsonElement root = jsonDoc.RootElement; // 遍历规则 foreach (JsonElement ruleSet in root.EnumerateArray()) { string platformType = ruleSet.Read("platformType", string.Empty); string supplier = ruleSet.Read("supplier", string.Empty); outputBuilder.AppendLine($"平台类型: {platformType}, 供应商: {supplier}"); JsonElement pwdRules = ruleSet.GetProperty("pwdRules"); int idx = 0; foreach (JsonElement patternElement in pwdRules.EnumerateArray()) { string pattern = patternElement.GetString(); try { // 使用Regex类来编译正则表达式 Regex compiledPattern = new Regex(pattern); if (compiledPattern.IsMatch(testText)) { outputBuilder.AppendLine($"规则 {idx + 1}: 匹配\t{pattern}"); } else { outputBuilder.AppendLine($"规则 {idx + 1}: 不匹配"); } } catch (Exception e) { outputBuilder.AppendLine($"规则 {idx + 1}: 正则表达式错误 - {e.Message}\t{pattern}"); } idx++; } } outputBuilder.AppendLine("测试完成"); return Content(outputBuilder.ToString()); } [HttpGet] public async Task ip(string ip) { string result = IP2RegionPlus.Search(ip); return new APIResult(new { success = true, message = result }); } [HttpPost] public async Task testreg([FromBody] JsonElement form) { var content = form.Read("s", string.Empty); string shortLinkurl = AlimamaPlus.GetTaobaoLink(content); bool is_tao_token = AlimamaPlus.MatchRegexes(content, []); bool is_other = AlimamaPlus.MatchOtherInfo(content, []); return new APIResult(new { shortLinkurl, is_tao_token, is_other }); } //[HttpGet] //public async Task comparison_tk([FromQuery] int count = 100) //{ // string cacheKey = ":lock_key:comparison_tk_logs"; // int last_id = RedisHelper.Get(cacheKey); // string filter = "id>@last_id"; // var result = new DBContext.Table("comparison_tk_logs") // .Where(filter, new { last_id }) // .Page(count, 1) // .Order("ID") // .PageList(false); // var ip = "127.0.0.1"; // var oaid = "test-comparison_tk_logs"; // foreach (var item in result.List) // { // await UnionParseCore.TaobaoParseAsync(item.rawContent, ip, oaid); // RedisHelper.Set(cacheKey, item.id, 10 * 86400); // } // if (result.Count < count) // { // cacheKey = ":lock_key:start_comparison_tk"; // RedisHelper.Set(cacheKey, 1, 600); // } // return new APIResult(new // { // success = true, // message = "ok" // }); //} //[HttpGet] //public async Task comparison_tk_raw([FromQuery] int count = 100) //{ // string cacheKey = ":lock_key:comparison_tk_logs_raw"; // int last_id = RedisHelper.Get(cacheKey); // string filter = "id>@last_id"; // var result = new DBContext.Table("comparison_tk_logs") // .Where(filter, new { last_id }) // .Page(count, 1) // .Order("ID") // .PageList(false); // var ip = "127.0.0.1"; // var oaid = "test-comparison_tk_logs_raw"; // foreach (var item in result.List) // { // await UnionParseCore.TaobaoParseAsync(item.rawContent, ip, oaid); // RedisHelper.Set(cacheKey, item.id, 10 * 86400); // } // if (result.Count < count) // { // cacheKey = ":lock_key:start_comparison_tk"; // RedisHelper.Set(cacheKey, 1, 600); // } // return new APIResult(new // { // success = true, // message = "ok" // }); //} [HttpGet] public async Task testTask() { string url = "https://www.taobao.com"; TkDataDTO result = new TkDataDTO(); result.message = "init"; result.success = true; int timeout = 1000; using var cts = new CancellationTokenSource(); cts.CancelAfter(timeout); var requestTask = Task.Run(() => AlimamaPlus.testTaskAsync(result, cts.Token), cts.Token); var delayTask = Task.Delay(timeout, cts.Token); var completedTask = await Task.WhenAny(requestTask, delayTask); if (completedTask == requestTask) { result = await requestTask; } else { cts.Cancel(); result.success = false; result.message = "放弃转链"; result.reason = "请求超时"; result.itemName = "点击打开淘宝APP"; } return new APIResult(new { success = true, message = "ok", result, }); } [HttpGet] public async Task testThread() { // 查看默认的最小和最大线程数 ThreadPool.GetMinThreads(out int defaultMinWorker, out int defaultMinIOC); ThreadPool.GetMaxThreads(out int defaultMaxWorker, out int defaultMaxIOC); string tmp = $"Default Min worker threads: {defaultMinWorker}, Min I/O completion threads: {defaultMinIOC}"; string tmp2 = $"Default Max worker threads: {defaultMaxWorker}, Max I/O completion threads: {defaultMaxIOC}"; // 获取当前线程池中可用的工作线程数和 I/O 完成端口线程数 ThreadPool.GetAvailableThreads(out int availableWorkerThreads, out int availableIOCompletionThreads); string tmp3 = $"Current available worker threads: {availableWorkerThreads}, available I/O completion threads: {availableIOCompletionThreads}"; return new APIResult(new { tmp, tmp2, tmp3, }); } [HttpPost] public async Task test1([FromBody] JsonElement form) { NotifyCore.Notify(new NifyMessage { message = $"【淘宝联盟:test】cookie 掉线", priority = NifyMessagePriority.high, tags = ["red_circle"] }); return new APIResult(new { success = true, message = "ok" }); } [HttpGet] public async Task RepairTkDailyAccountStats( DateTime startDate = default, DateTime endDate = default, string accountIds = "129,140", string extraNames = "搜同款_楚颜_128众杰科技,搜同款_广哲2", bool dryRun = false, bool repairDailyLogs = true, bool repairRedis = true, bool repairRedisNameKeys = true, int commandTimeoutSeconds = 600) { if (startDate == default) startDate = new DateTime(2026, 6, 16); if (endDate == default) endDate = DateTime.Now.Date; commandTimeoutSeconds = Math.Clamp(commandTimeoutSeconds, 30, 3600); startDate = startDate.Date; endDate = endDate.Date; if (endDate < startDate) { return new APIResult(new { success = false, message = "endDate 不能早于 startDate" }); } var ids = (accountIds ?? string.Empty) .Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) .Select(item => int.TryParse(item, out int id) ? id : 0) .Where(id => id > 0) .Distinct() .ToArray(); if (ids.Length == 0) { return new APIResult(new { success = false, message = "accountIds 不能为空" }); } using var conn = CenterHub.GetOpenConnection(); if (conn.State != ConnectionState.Open) conn.Open(); var errors = new List(); var affectedNames = new HashSet(StringComparer.Ordinal); var accounts = SqlMapper.Query( conn, "SELECT id accountId, company accountName FROM tk_pool WHERE id IN @ids", new { ids }, commandTimeout: commandTimeoutSeconds).ToList(); foreach (string name in accounts.Select(item => item.accountName).Where(item => !string.IsNullOrWhiteSpace(item))) { affectedNames.Add(name); } extraNames ??= string.Empty; foreach (string name in extraNames .Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) .Where(item => !string.IsNullOrWhiteSpace(item))) { affectedNames.Add(name); } foreach (string name in SqlMapper.Query( conn, @" SELECT DISTINCT accountName FROM center_daily_logs WHERE channel = 0 AND accountId IN @ids AND log_date BETWEEN @startDate AND @endDate AND COALESCE(accountName,'')<>''", new { ids, startDate, endDate }, commandTimeout: commandTimeoutSeconds)) { affectedNames.Add(name); } var days = EachDay(startDate, endDate).ToList(); var dailyReports = new List(); var endpointCountsByDate = new Dictionary>(); foreach (var date in days) { try { string tableName = $"tk_parse_logs_{date:yyyyMMdd}"; if (!TableExists(conn, tableName, commandTimeoutSeconds)) { dailyReports.Add(new { date = date.ToString("yyyy-MM-dd"), tableName, skipped = true, reason = "table not exists" }); continue; } var endpointCounts = SqlMapper.Query( conn, $@" SELECT l.accountId, COALESCE(p.company, MAX(l.accountName), '') accountName, COALESCE(l.end_point, '') end_point, COUNT(*) total_count, CAST(COALESCE(SUM(l.success = 1), 0) AS SIGNED) success_count, CAST(COALESCE(SUM(l.success = 0), 0) AS SIGNED) fail_count, CAST(COALESCE(SUM(l.message = '放弃转链' OR l.reason = '放弃转链'), 0) AS SIGNED) abandon_count FROM {tableName} l LEFT JOIN tk_pool p ON p.id = l.accountId WHERE l.accountId IN @ids GROUP BY l.accountId, p.company, l.end_point ORDER BY l.accountId, l.end_point", new { ids }, commandTimeout: commandTimeoutSeconds).ToList(); endpointCountsByDate[date.ToString("yyyyMMdd")] = endpointCounts; var accountCounts = endpointCounts .GroupBy(item => item.accountId) .Select(group => new TkDailyRepairCountRow { accountId = group.Key, accountName = accounts.FirstOrDefault(item => item.accountId == group.Key)?.accountName ?? group.FirstOrDefault()?.accountName ?? string.Empty, total_count = group.Sum(item => item.total_count), success_count = group.Sum(item => item.success_count), fail_count = group.Sum(item => item.fail_count), abandon_count = group.Sum(item => item.abandon_count) }) .ToList(); int dailyLogRows = 0; var allAccountCounts = ids .Select(accountId => accountCounts.FirstOrDefault(item => item.accountId == accountId) ?? new TkDailyRepairCountRow { accountId = accountId, accountName = accounts.FirstOrDefault(item => item.accountId == accountId)?.accountName ?? string.Empty }) .ToList(); foreach (var counts in allAccountCounts) { if (!dryRun && repairDailyLogs) { dailyLogRows += UpsertCenterDailyLog(conn, date, counts, commandTimeoutSeconds); } } dailyReports.Add(new { date = date.ToString("yyyy-MM-dd"), tableName, skipped = false, dailyLogRows, source = "mysql:tk_parse_logs_yyyyMMdd", counts = allAccountCounts, endpointCounts }); } catch (Exception ex) { var error = new { scope = "daily_log", date = date.ToString("yyyy-MM-dd"), error = FormatRepairError(ex) }; errors.Add(error); dailyReports.Add(new { date = date.ToString("yyyy-MM-dd"), skipped = true, reason = "error", error }); } } var endpointReports = new List(); if (repairRedis || repairRedisNameKeys) { var endpoints = EndPointCore.List(true) .Where(node => node.status && node.is_public_api && !string.IsNullOrEmpty(EndPointCore.GetRedisServer(node))) .ToList(); foreach (var endpoint in endpoints) { var redisServer = EndPointCore.GetRedisServer(endpoint); var endpointReport = new List(); try { await using var scope = await RedisClientFactory.CreateScopeAsync(redisServer); var redis = scope.Client; foreach (var date in days) { string dateKey = date.ToString("yyyyMMdd"); if (!endpointCountsByDate.TryGetValue(dateKey, out var endpointCounts)) { continue; } foreach (int accountId in ids) { var counts = endpointCounts.FirstOrDefault(item => item.accountId == accountId && string.Equals(item.end_point, endpoint.name, StringComparison.Ordinal)) ?? new TkDailyRepairEndpointCountRow { accountId = accountId, accountName = accounts.FirstOrDefault(item => item.accountId == accountId)?.accountName ?? string.Empty, end_point = endpoint.name }; if (!dryRun && repairRedis) { await WriteParseCountKeysAsync(redis, $"tb_{accountId}", dateKey, counts); } endpointReport.Add(new { date = date.ToString("yyyy-MM-dd"), bucket = $"tb_{accountId}", counts }); } if (repairRedisNameKeys) { foreach (string name in affectedNames) { var before = await ReadParseCountKeysAsync(redis, name, dateKey); if (!dryRun) { await DeleteParseBucketKeysAsync(redis, name, dateKey); } endpointReport.Add(new { date = date.ToString("yyyy-MM-dd"), deletedBucket = name, before }); } } } } catch (Exception ex) { errors.Add(new { scope = "redis_endpoint", endpoint = endpoint.name, error = FormatRepairError(ex) }); } endpointReports.Add(new { endpoint = endpoint.name, endpoint.description, dryRun, deletedBuckets = endpointReport }); } } return new APIResult(new { success = errors.Count == 0, dryRun, accountIds = ids, extraNames, repairDailyLogs, repairRedis, repairRedisNameKeys, commandTimeoutSeconds, startDate = startDate.ToString("yyyy-MM-dd"), endDate = endDate.ToString("yyyy-MM-dd"), affectedNames = affectedNames.OrderBy(item => item).ToList(), mysql = dailyReports, redis = endpointReports, errors }); } private static IEnumerable EachDay(DateTime startDate, DateTime endDate) { for (var date = startDate.Date; date <= endDate.Date; date = date.AddDays(1)) { yield return date; } } private static bool TableExists(IDbConnection conn, string tableName, int commandTimeoutSeconds) { const string sql = @" SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = @tableName"; return SqlMapper.ExecuteScalar( conn, sql, new { tableName }, commandTimeout: commandTimeoutSeconds) > 0; } private static int UpsertCenterDailyLog(IDbConnection conn, DateTime date, TkDailyRepairCountRow counts, int commandTimeoutSeconds) { if (string.IsNullOrWhiteSpace(counts.accountName)) return 0; string successPercentage = counts.total_count > 0 ? $"{counts.success_count / (double)counts.total_count * 100:f2}%" : string.Empty; string abandonPercentage = counts.total_count > 0 ? $"{counts.abandon_count / (double)counts.total_count * 100:f2}%" : string.Empty; var existingIds = SqlMapper.Query( conn, @" SELECT id FROM center_daily_logs WHERE channel = 0 AND log_date = @date AND accountId = @accountId ORDER BY id", new { date, counts.accountId }, commandTimeout: commandTimeoutSeconds).ToList(); if (existingIds.Count > 0) { int affectedRows = SqlMapper.Execute( conn, @" UPDATE center_daily_logs SET accountName = @accountName, parse_total_count = @totalCount, parse_success_count = @successCount, parse_abandon_count = @abandonCount, parse_success_percentage = @successPercentage, parse_abandon_percentage = @abandonPercentage, last_time = NOW() WHERE id = @id", new { id = existingIds[0], counts.accountName, totalCount = counts.total_count, successCount = counts.success_count, abandonCount = counts.abandon_count, successPercentage, abandonPercentage }, commandTimeout: commandTimeoutSeconds); if (existingIds.Count > 1) { affectedRows += SqlMapper.Execute( conn, "DELETE FROM center_daily_logs WHERE id IN @ids", new { ids = existingIds.Skip(1).ToArray() }, commandTimeout: commandTimeoutSeconds); } return affectedRows; } if (counts.total_count <= 0) return 0; return SqlMapper.Execute( conn, @" INSERT INTO center_daily_logs (channel, accountId, accountName, log_date, create_time, last_time, parse_total_count, parse_success_count, parse_abandon_count, parse_success_percentage, parse_abandon_percentage) VALUES (0, @accountId, @accountName, @date, NOW(), NOW(), @totalCount, @successCount, @abandonCount, @successPercentage, @abandonPercentage)", new { date, counts.accountId, counts.accountName, totalCount = counts.total_count, successCount = counts.success_count, abandonCount = counts.abandon_count, successPercentage, abandonPercentage }, commandTimeout: commandTimeoutSeconds); } private static async Task ReadParseCountKeysAsync(YunhuiKit.RedisClient redis, string bucket, string dateKey) { return new TkDailyRepairCountRow { accountName = bucket, total_count = await redis.GetAsync($":parse_total:{bucket}:{dateKey}"), success_count = await redis.GetAsync($":parse_total:{bucket}:success:{dateKey}"), fail_count = await redis.GetAsync($":parse_total:{bucket}:fail:{dateKey}"), abandon_count = await redis.GetAsync($":parse_total:{bucket}:放弃转链:{dateKey}") }; } private static async Task DeleteParseBucketKeysAsync(YunhuiKit.RedisClient redis, string bucket, string dateKey) { var keys = new List { $":parse_total:{bucket}:{dateKey}", $":parse_total:{bucket}:success:{dateKey}", $":parse_total:{bucket}:fail:{dateKey}", $":parse_total:{bucket}:放弃转链:{dateKey}", $":parse_total:{bucket}:message:{dateKey}", $":parse_total:{bucket}:reason:{dateKey}" }; foreach (string dpBucket in new[] { "dp_none", "dp_home", "dp_success", "dp_fail" }) { keys.Add($":parse_total:{dpBucket}:{bucket}:{dateKey}"); keys.Add($":parse_total:{dpBucket}:{bucket}:success:{dateKey}"); keys.Add($":parse_total:{dpBucket}:{bucket}:fail:{dateKey}"); keys.Add($":parse_total:{dpBucket}:{bucket}:放弃转链:{dateKey}"); keys.Add($":parse_total:{dpBucket}:{bucket}:message:{dateKey}"); keys.Add($":parse_total:{dpBucket}:{bucket}:reason:{dateKey}"); } await redis.DelAsync(keys.ToArray()); } private static string FormatRepairError(Exception ex) { return ex.InnerException == null ? ex.Message : $"{ex.Message} | {ex.InnerException.Message}"; } private static async Task WriteParseCountKeysAsync(YunhuiKit.RedisClient redis, string bucket, string dateKey, TkDailyRepairCountRow counts) { var keys = new[] { $":parse_total:{bucket}:{dateKey}", $":parse_total:{bucket}:success:{dateKey}", $":parse_total:{bucket}:fail:{dateKey}", $":parse_total:{bucket}:放弃转链:{dateKey}" }; if (counts.total_count <= 0) { await redis.DelAsync(keys); return; } await redis.SetAsync(keys[0], counts.total_count, 90 * 86400); await redis.SetAsync(keys[1], counts.success_count, 90 * 86400); await redis.SetAsync(keys[2], counts.fail_count, 90 * 86400); await redis.SetAsync(keys[3], counts.abandon_count, 90 * 86400); } private class TkDailyRepairCountRow { public int accountId { get; set; } public string accountName { get; set; } = string.Empty; public long total_count { get; set; } public long success_count { get; set; } public long fail_count { get; set; } public long abandon_count { get; set; } } private sealed class TkDailyRepairEndpointCountRow : TkDailyRepairCountRow { public string end_point { get; set; } = string.Empty; } private static async Task SaveJdStressStartLogAsync(string runId, string logName, int t, int time, string ip, string oaid, bool wait) { try { await new LoggerLibrary("jd_api_stress", logName) .AppendLine($"status=started") .AppendLine($"runId={runId}") .AppendLine($"startedAt={DateTime.Now:O}") .AppendLine($"t={t}") .AppendLine($"timeSeconds={time}") .AppendLine($"ip={ip}") .AppendLine($"oaid={oaid}") .AppendLine($"wait={wait}") .AppendLine($"content={JdStressDeeplinkContent}") .SaveAsync(); return "started"; } catch (Exception ex) { return ex.Message; } } private static async Task RunJdStressAsync(string runId, string logName, int t, int time, string ip, string oaid, string startLogStatus, CancellationToken cancellationToken) { var startedAt = DateTime.Now; var stopwatch = Stopwatch.StartNew(); long scheduledCount = 0; long completedCount = 0; long successCount = 0; long failCount = 0; long exceptionCount = 0; long riskControlCount = 0; long totalElapsedMs = 0; int activeCount = 0; int peakActiveCount = 0; var responseLines = new ConcurrentQueue(); var messageCounts = new ConcurrentDictionary(StringComparer.Ordinal); var codeCounts = new ConcurrentDictionary(StringComparer.Ordinal); int initialTaskCapacity = (int)Math.Min((long)t * Math.Min(time, 60), 4096L); var runningTasks = new List(initialTaskCapacity); string message = "ok"; try { for (int second = 0; second < time && !cancellationToken.IsCancellationRequested; second++) { for (int i = 0; i < t; i++) { long sequence = Interlocked.Increment(ref scheduledCount); runningTasks.Add(Task.Run(async () => { int currentActive = Interlocked.Increment(ref activeCount); UpdateMax(ref peakActiveCount, currentActive); try { var item = await ExecuteJdStressRequestAsync(sequence, ip, oaid, cancellationToken); Interlocked.Increment(ref completedCount); Interlocked.Add(ref totalElapsedMs, item.ElapsedMs); if (item.Success) { Interlocked.Increment(ref successCount); } else { Interlocked.Increment(ref failCount); } if (item.IsException) { Interlocked.Increment(ref exceptionCount); } if (IsRiskControlResult(item)) { Interlocked.Increment(ref riskControlCount); } string messageKey = string.IsNullOrWhiteSpace(item.SubMessage) ? item.Message : $"{item.Message}:{item.SubMessage}"; if (string.IsNullOrWhiteSpace(messageKey)) messageKey = "empty"; messageCounts.AddOrUpdate(messageKey, 1, (_, value) => value + 1); codeCounts.AddOrUpdate(item.Code.ToString(), 1, (_, value) => value + 1); responseLines.Enqueue( $"{item.FinishedAt:O}\tseq={item.Sequence}\telapsedMs={item.ElapsedMs}\tcode={item.Code}\tsuccess={item.Success}\tmessage={NormalizeLogValue(item.Message)}\tsub_message={NormalizeLogValue(item.SubMessage)}\tresponse={NormalizeLogValue(item.Response)}"); } finally { Interlocked.Decrement(ref activeCount); } })); } await ObserveCompletedTasksAsync(runningTasks); var nextTick = TimeSpan.FromSeconds(second + 1); var delay = nextTick - stopwatch.Elapsed; if (delay > TimeSpan.Zero) { await Task.Delay(delay, cancellationToken); } } } catch (OperationCanceledException ex) { message = $"canceled:{ex.Message}"; responseLines.Enqueue($"{DateTime.Now:O}\tmessage={NormalizeLogValue(message)}"); } catch (Exception ex) { message = $"runner error:{ex.Message}"; responseLines.Enqueue($"{DateTime.Now:O}\tmessage={NormalizeLogValue(message)}\tstack={NormalizeLogValue(ex.StackTrace ?? string.Empty)}"); } try { await Task.WhenAll(runningTasks); } catch (Exception ex) { message = $"task wait error:{ex.Message}"; responseLines.Enqueue($"{DateTime.Now:O}\tmessage={NormalizeLogValue(message)}\tstack={NormalizeLogValue(ex.StackTrace ?? string.Empty)}"); } stopwatch.Stop(); string logStatus = "saved"; try { var log = new LoggerLibrary("jd_api_stress", logName); log.AppendLine($"status=finished"); log.AppendLine($"startLogStatus={startLogStatus}"); log.AppendLine($"runId={runId}"); log.AppendLine($"startedAt={startedAt:O}"); log.AppendLine($"finishedAt={DateTime.Now:O}"); log.AppendLine($"t={t}"); log.AppendLine($"timeSeconds={time}"); log.AppendLine($"ip={ip}"); log.AppendLine($"oaid={oaid}"); log.AppendLine($"content={JdStressDeeplinkContent}"); log.AppendLine($"scheduled={scheduledCount}"); log.AppendLine($"completed={completedCount}"); log.AppendLine($"success={successCount}"); log.AppendLine($"fail={failCount}"); log.AppendLine($"exception={exceptionCount}"); log.AppendLine($"riskControl={riskControlCount}"); log.AppendLine($"peakActive={peakActiveCount}"); log.AppendLine("messageCounts="); foreach (var item in messageCounts.OrderByDescending(item => item.Value)) { log.AppendLine($"{item.Key}\t{item.Value}"); } log.AppendLine("codeCounts="); foreach (var item in codeCounts.OrderByDescending(item => item.Value)) { log.AppendLine($"{item.Key}\t{item.Value}"); } log.AppendLine("responses="); while (responseLines.TryDequeue(out string? line)) { log.AppendLine(line); } await log.SaveAsync(); } catch (Exception ex) { logStatus = ex.Message; } return new JdStressRunResult { success = true, message = message, runId = runId, requestPerSecond = t, timeSeconds = time, expectedRequests = (long)t * time, scheduledCount = scheduledCount, completedCount = completedCount, successCount = successCount, failCount = failCount, exceptionCount = exceptionCount, riskControlCount = riskControlCount, peakActiveCount = peakActiveCount, averageElapsedMs = completedCount == 0 ? 0 : Math.Round(totalElapsedMs / (double)completedCount, 2), elapsedSeconds = Math.Round(stopwatch.Elapsed.TotalSeconds, 2), messageCounts = messageCounts.OrderByDescending(item => item.Value).ToDictionary(item => item.Key, item => item.Value), codeCounts = codeCounts.OrderByDescending(item => item.Value).ToDictionary(item => item.Key, item => item.Value), log = new JdStressLogInfo { type = "LoggerLibrary", dir = "jd_api_stress", name = logName, status = logStatus } }; } private static async Task ObserveCompletedTasksAsync(List runningTasks) { for (int i = runningTasks.Count - 1; i >= 0; i--) { if (!runningTasks[i].IsCompleted) continue; await runningTasks[i]; runningTasks.RemoveAt(i); } } private static async Task ExecuteJdStressRequestAsync(long sequence, string ip, string oaid, CancellationToken cancellationToken) { var sw = Stopwatch.StartNew(); try { var request = new UnionParseRequest { Content = JdStressDeeplinkContent, Channel = "jd", CommerceType = 0, Ip = ip, Oaid = oaid, RiskStrategy = string.Empty, LaunchScene = 0, AccountId = 0, SpecialText = 0, QueryText = string.Empty, ClickId = string.Empty, Type = "dp", Pic = string.Empty }; var result = await UnionParseCore.DeeplinkJdParseAsync(request, cancellationToken); sw.Stop(); string response = result.Content ?? string.Empty; var parsed = ParseJdStressResponse(response); parsed.Sequence = sequence; parsed.ElapsedMs = sw.ElapsedMilliseconds; parsed.Response = response; parsed.FinishedAt = DateTime.Now; return parsed; } catch (Exception ex) { sw.Stop(); return new JdStressRequestResult { Sequence = sequence, ElapsedMs = sw.ElapsedMilliseconds, Success = false, Message = "exception", SubMessage = ex.Message, Code = 0, Response = ex.ToString(), FinishedAt = DateTime.Now, IsException = true }; } } private static JdStressRequestResult ParseJdStressResponse(string response) { var result = new JdStressRequestResult(); if (string.IsNullOrWhiteSpace(response)) { result.Message = "empty response"; return result; } try { using var doc = JsonDocument.Parse(response); var root = doc.RootElement; result.Success = ReadBool(root, "success"); result.Message = ReadString(root, "message"); result.SubMessage = ReadString(root, "sub_message"); result.Code = ReadInt(root, "code"); } catch (Exception ex) { result.Success = false; result.Message = "parse response error"; result.SubMessage = ex.Message; result.Code = 0; } return result; } private static bool ReadBool(JsonElement root, string propertyName) { if (!root.TryGetProperty(propertyName, out var property)) return false; return property.ValueKind switch { JsonValueKind.True => true, JsonValueKind.False => false, JsonValueKind.Number => property.TryGetInt32(out int value) && value != 0, JsonValueKind.String => bool.TryParse(property.GetString(), out bool value) && value, _ => false }; } private static int ReadInt(JsonElement root, string propertyName) { if (!root.TryGetProperty(propertyName, out var property)) return 0; return property.ValueKind switch { JsonValueKind.Number => property.TryGetInt32(out int value) ? value : 0, JsonValueKind.String => int.TryParse(property.GetString(), out int value) ? value : 0, _ => 0 }; } private static string ReadString(JsonElement root, string propertyName) { if (!root.TryGetProperty(propertyName, out var property)) return string.Empty; if (property.ValueKind == JsonValueKind.Null || property.ValueKind == JsonValueKind.Undefined) return string.Empty; return property.ValueKind == JsonValueKind.String ? property.GetString() ?? string.Empty : property.ToString(); } private static bool IsRiskControlResult(JdStressRequestResult result) { if (!"放弃转链".Equals(result.Message, StringComparison.Ordinal)) return false; return result.SubMessage.Contains("控制", StringComparison.Ordinal) || result.SubMessage.Contains("风控", StringComparison.Ordinal) || result.SubMessage.Contains("限流", StringComparison.Ordinal) || result.SubMessage.Contains("频", StringComparison.Ordinal) || result.SubMessage.Contains("系统繁忙", StringComparison.Ordinal); } private static string NormalizeLogValue(string value) { return (value ?? string.Empty) .Replace("\r", "\\r") .Replace("\n", "\\n") .Replace("\t", " "); } private static void UpdateMax(ref int target, int value) { int snapshot; while (value > (snapshot = Volatile.Read(ref target)) && Interlocked.CompareExchange(ref target, value, snapshot) != snapshot) { } } private sealed class JdStressRequestResult { public long Sequence { get; set; } public bool Success { get; set; } public string Message { get; set; } = string.Empty; public string SubMessage { get; set; } = string.Empty; public int Code { get; set; } public long ElapsedMs { get; set; } public string Response { get; set; } = string.Empty; public DateTime FinishedAt { get; set; } public bool IsException { get; set; } } private sealed class JdStressRunResult { public bool success { get; set; } public string message { get; set; } = string.Empty; public string runId { get; set; } = string.Empty; public int requestPerSecond { get; set; } public int timeSeconds { get; set; } public long expectedRequests { get; set; } public long scheduledCount { get; set; } public long completedCount { get; set; } public long successCount { get; set; } public long failCount { get; set; } public long exceptionCount { get; set; } public long riskControlCount { get; set; } public int peakActiveCount { get; set; } public double averageElapsedMs { get; set; } public double elapsedSeconds { get; set; } public Dictionary messageCounts { get; set; } = []; public Dictionary codeCounts { get; set; } = []; public JdStressLogInfo log { get; set; } = new(); } private sealed class JdStressLogInfo { public string type { get; set; } = string.Empty; public string dir { get; set; } = string.Empty; public string name { get; set; } = string.Empty; public string status { get; set; } = string.Empty; } [HttpGet] public async Task xxx() { var list = await TkPoolCore.ListAsync(); if (list == null) return new APIResult(new { success = false, message = "没有有效账号", }); string message = string.Empty; foreach (var account in list) { try { var alimama = new AlimamaPlus(account); alimama.RenewCookie(); } catch (Exception ex) { message = $"【cookie续期】xxxx\n{ex.Message}\n{ex.StackTrace}"; NotifyCore.Notify(new NifyMessage { message = message, priority = NifyMessagePriority.high, tags = ["red_circle"] }); continue; } } return new APIResult(new { success = true, message = "ok" }); } } }