TestController.cs 56 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434
  1. using molilian.core;
  2. using dodohold.core;
  3. using Dapper;
  4. using Microsoft.AspNetCore.Mvc;
  5. using Org.BouncyCastle.Ocsp;
  6. using System.Collections.Concurrent;
  7. using System.Diagnostics;
  8. using System.Text.Json;
  9. using System.Runtime.InteropServices;
  10. using System.Net;
  11. using System.Threading;
  12. using System.Data;
  13. using Microsoft.AspNetCore.Mvc.RazorPages;
  14. using TencentCloud.Soe.V20180724.Models;
  15. using static dodohold.core.ZTOExpress.CreateOrderArgs;
  16. using static Spire.Xls.Core.Spreadsheet.HTMLOptions;
  17. using System.Net.Http.Json;
  18. using System.Text.RegularExpressions;
  19. using System.Text;
  20. using TencentCloud.Oceanus.V20190422.Models;
  21. using YunhuiKit;
  22. using TencentCloud.Cdn.V20180606.Models;
  23. namespace molilian.api.Controllers
  24. {
  25. [ApiController]
  26. [Route("[controller]/[action]")]
  27. public class TestController : ControllerBase
  28. {
  29. 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";
  30. protected IHttpContextAccessor _accessor;
  31. public TestController(IHttpContextAccessor accessor)
  32. {
  33. _accessor = accessor;
  34. }
  35. [HttpGet]
  36. public async Task<ActionResult> 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)
  37. {
  38. t = t <= 0 ? 5 : t;
  39. time = time <= 0 ? 60 : time;
  40. string runId = DateTime.Now.ToString("yyyyMMdd_HHmmss_fff");
  41. string logName = $"run_{runId}_t{t}_time{time}";
  42. string startLogStatus = await SaveJdStressStartLogAsync(runId, logName, t, time, ip, oaid, wait);
  43. if (wait)
  44. {
  45. var result = await RunJdStressAsync(runId, logName, t, time, ip, oaid, startLogStatus, CancellationToken.None);
  46. return new APIResult(result);
  47. }
  48. _ = Task.Run(async () =>
  49. {
  50. try
  51. {
  52. await RunJdStressAsync(runId, logName, t, time, ip, oaid, startLogStatus, CancellationToken.None);
  53. }
  54. catch (Exception ex)
  55. {
  56. _ = new LoggerLibrary("jd_api_stress", $"{logName}_runner_error")
  57. .Info(ex.Message, ex.StackTrace)
  58. .SaveAsync();
  59. }
  60. });
  61. return new APIResult(new
  62. {
  63. success = true,
  64. message = "后台压测已启动",
  65. runId,
  66. requestPerSecond = t,
  67. timeSeconds = time,
  68. expectedRequests = (long)t * time,
  69. wait,
  70. log = new
  71. {
  72. type = "LoggerLibrary",
  73. dir = "jd_api_stress",
  74. name = logName,
  75. status = startLogStatus
  76. }
  77. });
  78. }
  79. [HttpGet]
  80. public async Task<ActionResult> ecs_list_test()
  81. {
  82. var list = AliyunPoolCore.EcsList();
  83. return new APIResult(new
  84. {
  85. success = true,
  86. msg = "ok",
  87. list
  88. });
  89. }
  90. [HttpGet]
  91. public async Task<ActionResult> testReconnectionRedis()
  92. {
  93. string cacheKey = "test";
  94. RedisKit.SetAsync(cacheKey, 1, 3600);
  95. string val = await RedisKit.GetAsync<string>(cacheKey);
  96. return new APIResult(new { success = "ok", val });
  97. }
  98. [HttpGet]
  99. public async Task<ActionResult> backfill_track_parse_metrics([FromQuery] string reportDate = "")
  100. {
  101. DateTime targetDate = DateTime.Now.Date;
  102. if (!string.IsNullOrWhiteSpace(reportDate) && !DateTime.TryParse(reportDate, out targetDate))
  103. {
  104. return new APIResult(new
  105. {
  106. success = false,
  107. message = "reportDate格式错误,请使用 yyyy-MM-dd"
  108. });
  109. }
  110. var result = await TracksCore.BackfillParseMetricCountersAsync(targetDate);
  111. return new APIResult(new
  112. {
  113. success = true,
  114. message = "ok",
  115. data = result
  116. });
  117. }
  118. [HttpGet]
  119. public async Task<ActionResult> ChangePublicIpByName(string nodeName)
  120. {
  121. var proxy_node = new DBContext.Table("proxy_nodes").Get<dynamic>("nodeName=@nodeName", new { nodeName });
  122. if (proxy_node == null) return new APIResult(new { success = false, msg = "没有匹配的 proxy_nodes 记录" });
  123. int aliyun_id = proxy_node.aliyun_id;
  124. string proxy_server = proxy_node.server;
  125. var account = new DBContext.Table("aliyun_pool").Get<AliyunPoolDTO>(aliyun_id);
  126. if (account == null) return new APIResult(new { success = false, msg = "没有匹配的 aliyun_pool 记录" });
  127. var uri = new Uri(proxy_server);
  128. string privateIp = uri.Host;
  129. AliyunCore core = new AliyunCore(account);
  130. var success = await core.ChangePublicIpAsync(privateIp);
  131. return new APIResult(new { success = "ok" });
  132. }
  133. [HttpGet]
  134. public async Task<ActionResult> ChangePublicIp(int id)
  135. {
  136. id = id >= 20000 ? id - 20000 : id;
  137. var proxy_node = new DBContext.Table("proxy_nodes").Get<dynamic>(id);
  138. if (proxy_node == null) return new APIResult(new { success = false, msg = "没有匹配的 proxy_nodes 记录" });
  139. int aliyun_id = proxy_node.aliyun_id;
  140. string proxy_server = proxy_node.server;
  141. var account = new DBContext.Table("aliyun_pool").Get<AliyunPoolDTO>(aliyun_id);
  142. if (account == null) return new APIResult(new { success = false, msg = "没有匹配的 aliyun_pool 记录" });
  143. var uri = new Uri(proxy_server);
  144. string privateIp = uri.Host;
  145. AliyunCore core = new AliyunCore(account);
  146. var success = await core.ChangePublicIpAsync(privateIp);
  147. return new APIResult(new { success = "ok" });
  148. }
  149. [HttpGet]
  150. //第一部 先加一个网卡,
  151. public async Task<ActionResult> CreateNetworkInterface(int id, string ecsid)
  152. {
  153. AliyunCore core = new AliyunCore(id);
  154. //创建弹性网卡并绑定公网IP
  155. var success = core.EcsCreateNetworkInterface("cn-beijing", ecsid);
  156. return new APIResult(new { success });
  157. }
  158. [HttpGet]
  159. //第二部 给网卡绑定很多个辅助ip
  160. public async Task<ActionResult> EcsAssignPrivateIpAddresses(int id, string ecsid, int count, bool isSecondary = true)
  161. {
  162. AliyunCore core = new AliyunCore(id);
  163. //创建弹性网卡并绑定公网IP
  164. var success = core.EcsAssignPrivateIpAddresses("cn-beijing", ecsid, count, !isSecondary);
  165. return new APIResult(new { success });
  166. }
  167. [HttpGet]
  168. public async Task<ActionResult> QueryAccountBalance()
  169. {
  170. AliyunPlus plus = new AliyunPlus("LTAI5tQbkTjtULQcrWGaw2VJ", "UIkkolVVEddooOKOIUByCqymZkK6ZA");
  171. var response = plus.QueryAccountBalance();
  172. var balance = response.Body.Data.AvailableAmount;
  173. return new APIResult(new { response });
  174. }
  175. //[HttpGet]
  176. //public async Task<ActionResult> test(string table = "tk_parse_logs_shop", int count = 100)
  177. //{
  178. // //table = "tk_parse_logs_shop";
  179. // //table = "tk_parse_logs_live";
  180. // //table = "tk_parse_logs_video";
  181. // string filter = "success=0 AND message='放弃转链' AND reason='非标准链接'";
  182. // for (int i = 0; i < 100; i++)
  183. // {
  184. // try
  185. // {
  186. // var list = new DBContext.Table(table)
  187. // .Where(filter, new { })
  188. // .Limit(count).Select<TkDataDTO>();
  189. // if (!list.Any())
  190. // {
  191. // return new APIResult(new
  192. // {
  193. // success = false,
  194. // message = "所有任务完成"
  195. // });
  196. // }
  197. // foreach (var item in list)
  198. // {
  199. // TkPoolDTO? account = TkPoolCore.GetOne(TkPoolCore.TkAction.parse);
  200. // if (account == null)
  201. // {
  202. // return new APIResult(new
  203. // {
  204. // success = false,
  205. // message = "没有工作账号"
  206. // });
  207. // }
  208. // var result = item.Convert2Json().Convert2Object<TkDataDTO>();
  209. // result.accountId = account.id;
  210. // result.accountName = account.company;
  211. // var alimama = new AlimamaPlus(account);
  212. // int timeout = alimama._config.rt_max;
  213. // using var cts = new CancellationTokenSource();
  214. // cts.CancelAfter(timeout);
  215. // result = await alimama.UnionParseAsync(item.content, result);
  216. // string reason = "非标准链接".Equals(result.reason) ? "非标准链接ok" : result.reason;
  217. // new DBContext.Table(table)
  218. // .Add("linkType", (int)result.link_type)
  219. // .Add("rawContent", result.rawContent)
  220. // .Add("success", result.success)
  221. // .Add("message", result.message)
  222. // .Add("reason", reason)
  223. // .Add("content", result.content)
  224. // .Add("itemId", result.itemId)
  225. // .Add("itemName", result.itemName)
  226. // .Add("pic", result.pic)
  227. // .Add("couponAmount", result.couponAmount)
  228. // .Add("promotionPrice", result.promotionPrice)
  229. // .Add("taoToken", result.taoToken)
  230. // .Add("shortLinkurl", result.shortLinkurl)
  231. // .Add("deeplink_url", result.deeplink_url)
  232. // .Add("num_iid", result.num_iid)
  233. // .Add("elapsedTime", result.elapsedTime)
  234. // .Add("elapsedTime2", result.elapsedTime2)
  235. // .Add("elapsedTime3", result.elapsedTime3)
  236. // .Add("subCode", result.subCode)
  237. // .Where("id=@id", new { item.id })
  238. // .Update();
  239. // }
  240. // }
  241. // catch
  242. // {
  243. // }
  244. // }
  245. // return new APIResult(new
  246. // {
  247. // success = true,
  248. // message = "ok"
  249. // });
  250. //}
  251. [HttpGet]
  252. public async Task<ActionResult> redu_douyin(string command)
  253. {
  254. ReduPlus plus = new ReduPlus("", "", "");
  255. var result = await plus.DouyinParse(command);
  256. return Content(result.Convert2Json());
  257. }
  258. [HttpGet]
  259. public async Task<ActionResult> redu_kuaishou(string command)
  260. {
  261. ReduPlus plus = new ReduPlus("", "", "");
  262. var result = await plus.KuaishouParse(command);
  263. return Content(result.Convert2Json());
  264. }
  265. [HttpPost]
  266. public async Task<ActionResult> test2([FromForm] string jsonContent, [FromForm] string testText)
  267. {
  268. // 用于存储输出结果的StringBuilder
  269. StringBuilder outputBuilder = new StringBuilder();
  270. // 解析JSON数据
  271. JsonDocument jsonDoc = JsonDocument.Parse(jsonContent);
  272. // 获取根元素
  273. JsonElement root = jsonDoc.RootElement;
  274. // 遍历规则
  275. foreach (JsonElement ruleSet in root.EnumerateArray())
  276. {
  277. string platformType = ruleSet.Read("platformType", string.Empty);
  278. string supplier = ruleSet.Read("supplier", string.Empty);
  279. outputBuilder.AppendLine($"平台类型: {platformType}, 供应商: {supplier}");
  280. JsonElement pwdRules = ruleSet.GetProperty("pwdRules");
  281. int idx = 0;
  282. foreach (JsonElement patternElement in pwdRules.EnumerateArray())
  283. {
  284. string pattern = patternElement.GetString();
  285. try
  286. {
  287. // 使用Regex类来编译正则表达式
  288. Regex compiledPattern = new Regex(pattern);
  289. if (compiledPattern.IsMatch(testText))
  290. {
  291. outputBuilder.AppendLine($"规则 {idx + 1}: 匹配\t{pattern}");
  292. }
  293. else
  294. {
  295. outputBuilder.AppendLine($"规则 {idx + 1}: 不匹配");
  296. }
  297. }
  298. catch (Exception e)
  299. {
  300. outputBuilder.AppendLine($"规则 {idx + 1}: 正则表达式错误 - {e.Message}\t{pattern}");
  301. }
  302. idx++;
  303. }
  304. }
  305. outputBuilder.AppendLine("测试完成");
  306. return Content(outputBuilder.ToString());
  307. }
  308. [HttpGet]
  309. public async Task<ActionResult> ip(string ip)
  310. {
  311. string result = IP2RegionPlus.Search(ip);
  312. return new APIResult(new
  313. {
  314. success = true,
  315. message = result
  316. });
  317. }
  318. [HttpPost]
  319. public async Task<ActionResult> testreg([FromBody] JsonElement form)
  320. {
  321. var content = form.Read("s", string.Empty);
  322. string shortLinkurl = AlimamaPlus.GetTaobaoLink(content);
  323. bool is_tao_token = AlimamaPlus.MatchRegexes(content, []);
  324. bool is_other = AlimamaPlus.MatchOtherInfo(content, []);
  325. return new APIResult(new
  326. {
  327. shortLinkurl,
  328. is_tao_token,
  329. is_other
  330. });
  331. }
  332. //[HttpGet]
  333. //public async Task<ActionResult> comparison_tk([FromQuery] int count = 100)
  334. //{
  335. // string cacheKey = ":lock_key:comparison_tk_logs";
  336. // int last_id = RedisHelper.Get<int>(cacheKey);
  337. // string filter = "id>@last_id";
  338. // var result = new DBContext.Table("comparison_tk_logs")
  339. // .Where(filter, new { last_id })
  340. // .Page(count, 1)
  341. // .Order("ID")
  342. // .PageList<TkDataDTO>(false);
  343. // var ip = "127.0.0.1";
  344. // var oaid = "test-comparison_tk_logs";
  345. // foreach (var item in result.List)
  346. // {
  347. // await UnionParseCore.TaobaoParseAsync(item.rawContent, ip, oaid);
  348. // RedisHelper.Set(cacheKey, item.id, 10 * 86400);
  349. // }
  350. // if (result.Count < count)
  351. // {
  352. // cacheKey = ":lock_key:start_comparison_tk";
  353. // RedisHelper.Set(cacheKey, 1, 600);
  354. // }
  355. // return new APIResult(new
  356. // {
  357. // success = true,
  358. // message = "ok"
  359. // });
  360. //}
  361. //[HttpGet]
  362. //public async Task<ActionResult> comparison_tk_raw([FromQuery] int count = 100)
  363. //{
  364. // string cacheKey = ":lock_key:comparison_tk_logs_raw";
  365. // int last_id = RedisHelper.Get<int>(cacheKey);
  366. // string filter = "id>@last_id";
  367. // var result = new DBContext.Table("comparison_tk_logs")
  368. // .Where(filter, new { last_id })
  369. // .Page(count, 1)
  370. // .Order("ID")
  371. // .PageList<TkDataDTO>(false);
  372. // var ip = "127.0.0.1";
  373. // var oaid = "test-comparison_tk_logs_raw";
  374. // foreach (var item in result.List)
  375. // {
  376. // await UnionParseCore.TaobaoParseAsync(item.rawContent, ip, oaid);
  377. // RedisHelper.Set(cacheKey, item.id, 10 * 86400);
  378. // }
  379. // if (result.Count < count)
  380. // {
  381. // cacheKey = ":lock_key:start_comparison_tk";
  382. // RedisHelper.Set(cacheKey, 1, 600);
  383. // }
  384. // return new APIResult(new
  385. // {
  386. // success = true,
  387. // message = "ok"
  388. // });
  389. //}
  390. [HttpGet]
  391. public async Task<ActionResult> testTask()
  392. {
  393. string url = "https://www.taobao.com";
  394. TkDataDTO result = new TkDataDTO();
  395. result.message = "init";
  396. result.success = true;
  397. int timeout = 1000;
  398. using var cts = new CancellationTokenSource();
  399. cts.CancelAfter(timeout);
  400. var requestTask = Task.Run(() => AlimamaPlus.testTaskAsync(result, cts.Token), cts.Token);
  401. var delayTask = Task.Delay(timeout, cts.Token);
  402. var completedTask = await Task.WhenAny(requestTask, delayTask);
  403. if (completedTask == requestTask)
  404. {
  405. result = await requestTask;
  406. }
  407. else
  408. {
  409. cts.Cancel();
  410. result.success = false;
  411. result.message = "放弃转链";
  412. result.reason = "请求超时";
  413. result.itemName = "点击打开淘宝APP";
  414. }
  415. return new APIResult(new
  416. {
  417. success = true,
  418. message = "ok",
  419. result,
  420. });
  421. }
  422. [HttpGet]
  423. public async Task<ActionResult> testThread()
  424. {
  425. // 查看默认的最小和最大线程数
  426. ThreadPool.GetMinThreads(out int defaultMinWorker, out int defaultMinIOC);
  427. ThreadPool.GetMaxThreads(out int defaultMaxWorker, out int defaultMaxIOC);
  428. string tmp = $"Default Min worker threads: {defaultMinWorker}, Min I/O completion threads: {defaultMinIOC}";
  429. string tmp2 = $"Default Max worker threads: {defaultMaxWorker}, Max I/O completion threads: {defaultMaxIOC}";
  430. // 获取当前线程池中可用的工作线程数和 I/O 完成端口线程数
  431. ThreadPool.GetAvailableThreads(out int availableWorkerThreads, out int availableIOCompletionThreads);
  432. string tmp3 = $"Current available worker threads: {availableWorkerThreads}, available I/O completion threads: {availableIOCompletionThreads}";
  433. return new APIResult(new
  434. {
  435. tmp,
  436. tmp2,
  437. tmp3,
  438. });
  439. }
  440. [HttpPost]
  441. public async Task<ActionResult> test1([FromBody] JsonElement form)
  442. {
  443. NotifyCore.Notify(new NifyMessage
  444. {
  445. message = $"【淘宝联盟:test】cookie 掉线",
  446. priority = NifyMessagePriority.high,
  447. tags = ["red_circle"]
  448. });
  449. return new APIResult(new
  450. {
  451. success = true,
  452. message = "ok"
  453. });
  454. }
  455. [HttpGet]
  456. public async Task<ActionResult> RepairTkDailyAccountStats(
  457. DateTime startDate = default,
  458. DateTime endDate = default,
  459. string accountIds = "129,140",
  460. string extraNames = "搜同款_楚颜_128众杰科技,搜同款_广哲2",
  461. bool dryRun = false,
  462. bool repairDailyLogs = true,
  463. bool repairRedis = true,
  464. bool repairRedisNameKeys = true,
  465. int commandTimeoutSeconds = 600)
  466. {
  467. if (startDate == default) startDate = new DateTime(2026, 6, 16);
  468. if (endDate == default) endDate = DateTime.Now.Date;
  469. commandTimeoutSeconds = Math.Clamp(commandTimeoutSeconds, 30, 3600);
  470. startDate = startDate.Date;
  471. endDate = endDate.Date;
  472. if (endDate < startDate)
  473. {
  474. return new APIResult(new { success = false, message = "endDate 不能早于 startDate" });
  475. }
  476. var ids = (accountIds ?? string.Empty)
  477. .Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
  478. .Select(item => int.TryParse(item, out int id) ? id : 0)
  479. .Where(id => id > 0)
  480. .Distinct()
  481. .ToArray();
  482. if (ids.Length == 0)
  483. {
  484. return new APIResult(new { success = false, message = "accountIds 不能为空" });
  485. }
  486. using var conn = CenterHub.GetOpenConnection();
  487. if (conn.State != ConnectionState.Open) conn.Open();
  488. var errors = new List<object>();
  489. var affectedNames = new HashSet<string>(StringComparer.Ordinal);
  490. var accounts = SqlMapper.Query<TkDailyRepairCountRow>(
  491. conn,
  492. "SELECT id accountId, company accountName FROM tk_pool WHERE id IN @ids",
  493. new { ids },
  494. commandTimeout: commandTimeoutSeconds).ToList();
  495. foreach (string name in accounts.Select(item => item.accountName).Where(item => !string.IsNullOrWhiteSpace(item)))
  496. {
  497. affectedNames.Add(name);
  498. }
  499. extraNames ??= string.Empty;
  500. foreach (string name in extraNames
  501. .Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
  502. .Where(item => !string.IsNullOrWhiteSpace(item)))
  503. {
  504. affectedNames.Add(name);
  505. }
  506. foreach (string name in SqlMapper.Query<string>(
  507. conn,
  508. @"
  509. SELECT DISTINCT accountName
  510. FROM center_daily_logs
  511. WHERE channel = 0
  512. AND accountId IN @ids
  513. AND log_date BETWEEN @startDate AND @endDate
  514. AND COALESCE(accountName,'')<>''",
  515. new { ids, startDate, endDate },
  516. commandTimeout: commandTimeoutSeconds))
  517. {
  518. affectedNames.Add(name);
  519. }
  520. var days = EachDay(startDate, endDate).ToList();
  521. var dailyReports = new List<object>();
  522. var endpointCountsByDate = new Dictionary<string, List<TkDailyRepairEndpointCountRow>>();
  523. foreach (var date in days)
  524. {
  525. try
  526. {
  527. string tableName = $"tk_parse_logs_{date:yyyyMMdd}";
  528. if (!TableExists(conn, tableName, commandTimeoutSeconds))
  529. {
  530. dailyReports.Add(new { date = date.ToString("yyyy-MM-dd"), tableName, skipped = true, reason = "table not exists" });
  531. continue;
  532. }
  533. var endpointCounts = SqlMapper.Query<TkDailyRepairEndpointCountRow>(
  534. conn,
  535. $@"
  536. SELECT
  537. l.accountId,
  538. COALESCE(p.company, MAX(l.accountName), '') accountName,
  539. COALESCE(l.end_point, '') end_point,
  540. COUNT(*) total_count,
  541. CAST(COALESCE(SUM(l.success = 1), 0) AS SIGNED) success_count,
  542. CAST(COALESCE(SUM(l.success = 0), 0) AS SIGNED) fail_count,
  543. CAST(COALESCE(SUM(l.message = '放弃转链' OR l.reason = '放弃转链'), 0) AS SIGNED) abandon_count
  544. FROM {tableName} l
  545. LEFT JOIN tk_pool p ON p.id = l.accountId
  546. WHERE l.accountId IN @ids
  547. GROUP BY l.accountId, p.company, l.end_point
  548. ORDER BY l.accountId, l.end_point",
  549. new { ids },
  550. commandTimeout: commandTimeoutSeconds).ToList();
  551. endpointCountsByDate[date.ToString("yyyyMMdd")] = endpointCounts;
  552. var accountCounts = endpointCounts
  553. .GroupBy(item => item.accountId)
  554. .Select(group => new TkDailyRepairCountRow
  555. {
  556. accountId = group.Key,
  557. accountName = accounts.FirstOrDefault(item => item.accountId == group.Key)?.accountName
  558. ?? group.FirstOrDefault()?.accountName
  559. ?? string.Empty,
  560. total_count = group.Sum(item => item.total_count),
  561. success_count = group.Sum(item => item.success_count),
  562. fail_count = group.Sum(item => item.fail_count),
  563. abandon_count = group.Sum(item => item.abandon_count)
  564. })
  565. .ToList();
  566. int dailyLogRows = 0;
  567. var allAccountCounts = ids
  568. .Select(accountId => accountCounts.FirstOrDefault(item => item.accountId == accountId)
  569. ?? new TkDailyRepairCountRow
  570. {
  571. accountId = accountId,
  572. accountName = accounts.FirstOrDefault(item => item.accountId == accountId)?.accountName ?? string.Empty
  573. })
  574. .ToList();
  575. foreach (var counts in allAccountCounts)
  576. {
  577. if (!dryRun && repairDailyLogs)
  578. {
  579. dailyLogRows += UpsertCenterDailyLog(conn, date, counts, commandTimeoutSeconds);
  580. }
  581. }
  582. dailyReports.Add(new
  583. {
  584. date = date.ToString("yyyy-MM-dd"),
  585. tableName,
  586. skipped = false,
  587. dailyLogRows,
  588. source = "mysql:tk_parse_logs_yyyyMMdd",
  589. counts = allAccountCounts,
  590. endpointCounts
  591. });
  592. }
  593. catch (Exception ex)
  594. {
  595. var error = new { scope = "daily_log", date = date.ToString("yyyy-MM-dd"), error = FormatRepairError(ex) };
  596. errors.Add(error);
  597. dailyReports.Add(new { date = date.ToString("yyyy-MM-dd"), skipped = true, reason = "error", error });
  598. }
  599. }
  600. var endpointReports = new List<object>();
  601. if (repairRedis || repairRedisNameKeys)
  602. {
  603. var endpoints = EndPointCore.List(true)
  604. .Where(node => node.status && node.is_public_api && !string.IsNullOrEmpty(EndPointCore.GetRedisServer(node)))
  605. .ToList();
  606. foreach (var endpoint in endpoints)
  607. {
  608. var redisServer = EndPointCore.GetRedisServer(endpoint);
  609. var endpointReport = new List<object>();
  610. try
  611. {
  612. await using var scope = await RedisClientFactory.CreateScopeAsync(redisServer);
  613. var redis = scope.Client;
  614. foreach (var date in days)
  615. {
  616. string dateKey = date.ToString("yyyyMMdd");
  617. if (!endpointCountsByDate.TryGetValue(dateKey, out var endpointCounts))
  618. {
  619. continue;
  620. }
  621. foreach (int accountId in ids)
  622. {
  623. var counts = endpointCounts.FirstOrDefault(item =>
  624. item.accountId == accountId &&
  625. string.Equals(item.end_point, endpoint.name, StringComparison.Ordinal))
  626. ?? new TkDailyRepairEndpointCountRow
  627. {
  628. accountId = accountId,
  629. accountName = accounts.FirstOrDefault(item => item.accountId == accountId)?.accountName ?? string.Empty,
  630. end_point = endpoint.name
  631. };
  632. if (!dryRun && repairRedis)
  633. {
  634. await WriteParseCountKeysAsync(redis, $"tb_{accountId}", dateKey, counts);
  635. }
  636. endpointReport.Add(new
  637. {
  638. date = date.ToString("yyyy-MM-dd"),
  639. bucket = $"tb_{accountId}",
  640. counts
  641. });
  642. }
  643. if (repairRedisNameKeys)
  644. {
  645. foreach (string name in affectedNames)
  646. {
  647. var before = await ReadParseCountKeysAsync(redis, name, dateKey);
  648. if (!dryRun)
  649. {
  650. await DeleteParseBucketKeysAsync(redis, name, dateKey);
  651. }
  652. endpointReport.Add(new
  653. {
  654. date = date.ToString("yyyy-MM-dd"),
  655. deletedBucket = name,
  656. before
  657. });
  658. }
  659. }
  660. }
  661. }
  662. catch (Exception ex)
  663. {
  664. errors.Add(new { scope = "redis_endpoint", endpoint = endpoint.name, error = FormatRepairError(ex) });
  665. }
  666. endpointReports.Add(new
  667. {
  668. endpoint = endpoint.name,
  669. endpoint.description,
  670. dryRun,
  671. deletedBuckets = endpointReport
  672. });
  673. }
  674. }
  675. return new APIResult(new
  676. {
  677. success = errors.Count == 0,
  678. dryRun,
  679. accountIds = ids,
  680. extraNames,
  681. repairDailyLogs,
  682. repairRedis,
  683. repairRedisNameKeys,
  684. commandTimeoutSeconds,
  685. startDate = startDate.ToString("yyyy-MM-dd"),
  686. endDate = endDate.ToString("yyyy-MM-dd"),
  687. affectedNames = affectedNames.OrderBy(item => item).ToList(),
  688. mysql = dailyReports,
  689. redis = endpointReports,
  690. errors
  691. });
  692. }
  693. private static IEnumerable<DateTime> EachDay(DateTime startDate, DateTime endDate)
  694. {
  695. for (var date = startDate.Date; date <= endDate.Date; date = date.AddDays(1))
  696. {
  697. yield return date;
  698. }
  699. }
  700. private static bool TableExists(IDbConnection conn, string tableName, int commandTimeoutSeconds)
  701. {
  702. const string sql = @"
  703. SELECT COUNT(*)
  704. FROM information_schema.tables
  705. WHERE table_schema = DATABASE()
  706. AND table_name = @tableName";
  707. return SqlMapper.ExecuteScalar<int>(
  708. conn,
  709. sql,
  710. new { tableName },
  711. commandTimeout: commandTimeoutSeconds) > 0;
  712. }
  713. private static int UpsertCenterDailyLog(IDbConnection conn, DateTime date, TkDailyRepairCountRow counts, int commandTimeoutSeconds)
  714. {
  715. if (string.IsNullOrWhiteSpace(counts.accountName)) return 0;
  716. string successPercentage = counts.total_count > 0 ? $"{counts.success_count / (double)counts.total_count * 100:f2}%" : string.Empty;
  717. string abandonPercentage = counts.total_count > 0 ? $"{counts.abandon_count / (double)counts.total_count * 100:f2}%" : string.Empty;
  718. var existingIds = SqlMapper.Query<int>(
  719. conn,
  720. @"
  721. SELECT id
  722. FROM center_daily_logs
  723. WHERE channel = 0
  724. AND log_date = @date
  725. AND accountId = @accountId
  726. ORDER BY id",
  727. new { date, counts.accountId },
  728. commandTimeout: commandTimeoutSeconds).ToList();
  729. if (existingIds.Count > 0)
  730. {
  731. int affectedRows = SqlMapper.Execute(
  732. conn,
  733. @"
  734. UPDATE center_daily_logs
  735. SET accountName = @accountName,
  736. parse_total_count = @totalCount,
  737. parse_success_count = @successCount,
  738. parse_abandon_count = @abandonCount,
  739. parse_success_percentage = @successPercentage,
  740. parse_abandon_percentage = @abandonPercentage,
  741. last_time = NOW()
  742. WHERE id = @id",
  743. new
  744. {
  745. id = existingIds[0],
  746. counts.accountName,
  747. totalCount = counts.total_count,
  748. successCount = counts.success_count,
  749. abandonCount = counts.abandon_count,
  750. successPercentage,
  751. abandonPercentage
  752. },
  753. commandTimeout: commandTimeoutSeconds);
  754. if (existingIds.Count > 1)
  755. {
  756. affectedRows += SqlMapper.Execute(
  757. conn,
  758. "DELETE FROM center_daily_logs WHERE id IN @ids",
  759. new { ids = existingIds.Skip(1).ToArray() },
  760. commandTimeout: commandTimeoutSeconds);
  761. }
  762. return affectedRows;
  763. }
  764. if (counts.total_count <= 0) return 0;
  765. return SqlMapper.Execute(
  766. conn,
  767. @"
  768. INSERT INTO center_daily_logs
  769. (channel, accountId, accountName, log_date, create_time, last_time,
  770. parse_total_count, parse_success_count, parse_abandon_count,
  771. parse_success_percentage, parse_abandon_percentage)
  772. VALUES
  773. (0, @accountId, @accountName, @date, NOW(), NOW(),
  774. @totalCount, @successCount, @abandonCount,
  775. @successPercentage, @abandonPercentage)",
  776. new
  777. {
  778. date,
  779. counts.accountId,
  780. counts.accountName,
  781. totalCount = counts.total_count,
  782. successCount = counts.success_count,
  783. abandonCount = counts.abandon_count,
  784. successPercentage,
  785. abandonPercentage
  786. },
  787. commandTimeout: commandTimeoutSeconds);
  788. }
  789. private static async Task<TkDailyRepairCountRow> ReadParseCountKeysAsync(YunhuiKit.RedisClient redis, string bucket, string dateKey)
  790. {
  791. return new TkDailyRepairCountRow
  792. {
  793. accountName = bucket,
  794. total_count = await redis.GetAsync<int>($":parse_total:{bucket}:{dateKey}"),
  795. success_count = await redis.GetAsync<int>($":parse_total:{bucket}:success:{dateKey}"),
  796. fail_count = await redis.GetAsync<int>($":parse_total:{bucket}:fail:{dateKey}"),
  797. abandon_count = await redis.GetAsync<int>($":parse_total:{bucket}:放弃转链:{dateKey}")
  798. };
  799. }
  800. private static async Task DeleteParseBucketKeysAsync(YunhuiKit.RedisClient redis, string bucket, string dateKey)
  801. {
  802. var keys = new List<string>
  803. {
  804. $":parse_total:{bucket}:{dateKey}",
  805. $":parse_total:{bucket}:success:{dateKey}",
  806. $":parse_total:{bucket}:fail:{dateKey}",
  807. $":parse_total:{bucket}:放弃转链:{dateKey}",
  808. $":parse_total:{bucket}:message:{dateKey}",
  809. $":parse_total:{bucket}:reason:{dateKey}"
  810. };
  811. foreach (string dpBucket in new[] { "dp_none", "dp_home", "dp_success", "dp_fail" })
  812. {
  813. keys.Add($":parse_total:{dpBucket}:{bucket}:{dateKey}");
  814. keys.Add($":parse_total:{dpBucket}:{bucket}:success:{dateKey}");
  815. keys.Add($":parse_total:{dpBucket}:{bucket}:fail:{dateKey}");
  816. keys.Add($":parse_total:{dpBucket}:{bucket}:放弃转链:{dateKey}");
  817. keys.Add($":parse_total:{dpBucket}:{bucket}:message:{dateKey}");
  818. keys.Add($":parse_total:{dpBucket}:{bucket}:reason:{dateKey}");
  819. }
  820. await redis.DelAsync(keys.ToArray());
  821. }
  822. private static string FormatRepairError(Exception ex)
  823. {
  824. return ex.InnerException == null ? ex.Message : $"{ex.Message} | {ex.InnerException.Message}";
  825. }
  826. private static async Task WriteParseCountKeysAsync(YunhuiKit.RedisClient redis, string bucket, string dateKey, TkDailyRepairCountRow counts)
  827. {
  828. var keys = new[]
  829. {
  830. $":parse_total:{bucket}:{dateKey}",
  831. $":parse_total:{bucket}:success:{dateKey}",
  832. $":parse_total:{bucket}:fail:{dateKey}",
  833. $":parse_total:{bucket}:放弃转链:{dateKey}"
  834. };
  835. if (counts.total_count <= 0)
  836. {
  837. await redis.DelAsync(keys);
  838. return;
  839. }
  840. await redis.SetAsync(keys[0], counts.total_count, 90 * 86400);
  841. await redis.SetAsync(keys[1], counts.success_count, 90 * 86400);
  842. await redis.SetAsync(keys[2], counts.fail_count, 90 * 86400);
  843. await redis.SetAsync(keys[3], counts.abandon_count, 90 * 86400);
  844. }
  845. private class TkDailyRepairCountRow
  846. {
  847. public int accountId { get; set; }
  848. public string accountName { get; set; } = string.Empty;
  849. public long total_count { get; set; }
  850. public long success_count { get; set; }
  851. public long fail_count { get; set; }
  852. public long abandon_count { get; set; }
  853. }
  854. private sealed class TkDailyRepairEndpointCountRow : TkDailyRepairCountRow
  855. {
  856. public string end_point { get; set; } = string.Empty;
  857. }
  858. private static async Task<string> SaveJdStressStartLogAsync(string runId, string logName, int t, int time, string ip, string oaid, bool wait)
  859. {
  860. try
  861. {
  862. await new LoggerLibrary("jd_api_stress", logName)
  863. .AppendLine($"status=started")
  864. .AppendLine($"runId={runId}")
  865. .AppendLine($"startedAt={DateTime.Now:O}")
  866. .AppendLine($"t={t}")
  867. .AppendLine($"timeSeconds={time}")
  868. .AppendLine($"ip={ip}")
  869. .AppendLine($"oaid={oaid}")
  870. .AppendLine($"wait={wait}")
  871. .AppendLine($"content={JdStressDeeplinkContent}")
  872. .SaveAsync();
  873. return "started";
  874. }
  875. catch (Exception ex)
  876. {
  877. return ex.Message;
  878. }
  879. }
  880. private static async Task<JdStressRunResult> RunJdStressAsync(string runId, string logName, int t, int time, string ip, string oaid, string startLogStatus, CancellationToken cancellationToken)
  881. {
  882. var startedAt = DateTime.Now;
  883. var stopwatch = Stopwatch.StartNew();
  884. long scheduledCount = 0;
  885. long completedCount = 0;
  886. long successCount = 0;
  887. long failCount = 0;
  888. long exceptionCount = 0;
  889. long riskControlCount = 0;
  890. long totalElapsedMs = 0;
  891. int activeCount = 0;
  892. int peakActiveCount = 0;
  893. var responseLines = new ConcurrentQueue<string>();
  894. var messageCounts = new ConcurrentDictionary<string, int>(StringComparer.Ordinal);
  895. var codeCounts = new ConcurrentDictionary<string, int>(StringComparer.Ordinal);
  896. int initialTaskCapacity = (int)Math.Min((long)t * Math.Min(time, 60), 4096L);
  897. var runningTasks = new List<Task>(initialTaskCapacity);
  898. string message = "ok";
  899. try
  900. {
  901. for (int second = 0; second < time && !cancellationToken.IsCancellationRequested; second++)
  902. {
  903. for (int i = 0; i < t; i++)
  904. {
  905. long sequence = Interlocked.Increment(ref scheduledCount);
  906. runningTasks.Add(Task.Run(async () =>
  907. {
  908. int currentActive = Interlocked.Increment(ref activeCount);
  909. UpdateMax(ref peakActiveCount, currentActive);
  910. try
  911. {
  912. var item = await ExecuteJdStressRequestAsync(sequence, ip, oaid, cancellationToken);
  913. Interlocked.Increment(ref completedCount);
  914. Interlocked.Add(ref totalElapsedMs, item.ElapsedMs);
  915. if (item.Success)
  916. {
  917. Interlocked.Increment(ref successCount);
  918. }
  919. else
  920. {
  921. Interlocked.Increment(ref failCount);
  922. }
  923. if (item.IsException)
  924. {
  925. Interlocked.Increment(ref exceptionCount);
  926. }
  927. if (IsRiskControlResult(item))
  928. {
  929. Interlocked.Increment(ref riskControlCount);
  930. }
  931. string messageKey = string.IsNullOrWhiteSpace(item.SubMessage)
  932. ? item.Message
  933. : $"{item.Message}:{item.SubMessage}";
  934. if (string.IsNullOrWhiteSpace(messageKey)) messageKey = "empty";
  935. messageCounts.AddOrUpdate(messageKey, 1, (_, value) => value + 1);
  936. codeCounts.AddOrUpdate(item.Code.ToString(), 1, (_, value) => value + 1);
  937. responseLines.Enqueue(
  938. $"{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)}");
  939. }
  940. finally
  941. {
  942. Interlocked.Decrement(ref activeCount);
  943. }
  944. }));
  945. }
  946. await ObserveCompletedTasksAsync(runningTasks);
  947. var nextTick = TimeSpan.FromSeconds(second + 1);
  948. var delay = nextTick - stopwatch.Elapsed;
  949. if (delay > TimeSpan.Zero)
  950. {
  951. await Task.Delay(delay, cancellationToken);
  952. }
  953. }
  954. }
  955. catch (OperationCanceledException ex)
  956. {
  957. message = $"canceled:{ex.Message}";
  958. responseLines.Enqueue($"{DateTime.Now:O}\tmessage={NormalizeLogValue(message)}");
  959. }
  960. catch (Exception ex)
  961. {
  962. message = $"runner error:{ex.Message}";
  963. responseLines.Enqueue($"{DateTime.Now:O}\tmessage={NormalizeLogValue(message)}\tstack={NormalizeLogValue(ex.StackTrace ?? string.Empty)}");
  964. }
  965. try
  966. {
  967. await Task.WhenAll(runningTasks);
  968. }
  969. catch (Exception ex)
  970. {
  971. message = $"task wait error:{ex.Message}";
  972. responseLines.Enqueue($"{DateTime.Now:O}\tmessage={NormalizeLogValue(message)}\tstack={NormalizeLogValue(ex.StackTrace ?? string.Empty)}");
  973. }
  974. stopwatch.Stop();
  975. string logStatus = "saved";
  976. try
  977. {
  978. var log = new LoggerLibrary("jd_api_stress", logName);
  979. log.AppendLine($"status=finished");
  980. log.AppendLine($"startLogStatus={startLogStatus}");
  981. log.AppendLine($"runId={runId}");
  982. log.AppendLine($"startedAt={startedAt:O}");
  983. log.AppendLine($"finishedAt={DateTime.Now:O}");
  984. log.AppendLine($"t={t}");
  985. log.AppendLine($"timeSeconds={time}");
  986. log.AppendLine($"ip={ip}");
  987. log.AppendLine($"oaid={oaid}");
  988. log.AppendLine($"content={JdStressDeeplinkContent}");
  989. log.AppendLine($"scheduled={scheduledCount}");
  990. log.AppendLine($"completed={completedCount}");
  991. log.AppendLine($"success={successCount}");
  992. log.AppendLine($"fail={failCount}");
  993. log.AppendLine($"exception={exceptionCount}");
  994. log.AppendLine($"riskControl={riskControlCount}");
  995. log.AppendLine($"peakActive={peakActiveCount}");
  996. log.AppendLine("messageCounts=");
  997. foreach (var item in messageCounts.OrderByDescending(item => item.Value))
  998. {
  999. log.AppendLine($"{item.Key}\t{item.Value}");
  1000. }
  1001. log.AppendLine("codeCounts=");
  1002. foreach (var item in codeCounts.OrderByDescending(item => item.Value))
  1003. {
  1004. log.AppendLine($"{item.Key}\t{item.Value}");
  1005. }
  1006. log.AppendLine("responses=");
  1007. while (responseLines.TryDequeue(out string? line))
  1008. {
  1009. log.AppendLine(line);
  1010. }
  1011. await log.SaveAsync();
  1012. }
  1013. catch (Exception ex)
  1014. {
  1015. logStatus = ex.Message;
  1016. }
  1017. return new JdStressRunResult
  1018. {
  1019. success = true,
  1020. message = message,
  1021. runId = runId,
  1022. requestPerSecond = t,
  1023. timeSeconds = time,
  1024. expectedRequests = (long)t * time,
  1025. scheduledCount = scheduledCount,
  1026. completedCount = completedCount,
  1027. successCount = successCount,
  1028. failCount = failCount,
  1029. exceptionCount = exceptionCount,
  1030. riskControlCount = riskControlCount,
  1031. peakActiveCount = peakActiveCount,
  1032. averageElapsedMs = completedCount == 0 ? 0 : Math.Round(totalElapsedMs / (double)completedCount, 2),
  1033. elapsedSeconds = Math.Round(stopwatch.Elapsed.TotalSeconds, 2),
  1034. messageCounts = messageCounts.OrderByDescending(item => item.Value).ToDictionary(item => item.Key, item => item.Value),
  1035. codeCounts = codeCounts.OrderByDescending(item => item.Value).ToDictionary(item => item.Key, item => item.Value),
  1036. log = new JdStressLogInfo
  1037. {
  1038. type = "LoggerLibrary",
  1039. dir = "jd_api_stress",
  1040. name = logName,
  1041. status = logStatus
  1042. }
  1043. };
  1044. }
  1045. private static async Task ObserveCompletedTasksAsync(List<Task> runningTasks)
  1046. {
  1047. for (int i = runningTasks.Count - 1; i >= 0; i--)
  1048. {
  1049. if (!runningTasks[i].IsCompleted) continue;
  1050. await runningTasks[i];
  1051. runningTasks.RemoveAt(i);
  1052. }
  1053. }
  1054. private static async Task<JdStressRequestResult> ExecuteJdStressRequestAsync(long sequence, string ip, string oaid, CancellationToken cancellationToken)
  1055. {
  1056. var sw = Stopwatch.StartNew();
  1057. try
  1058. {
  1059. var request = new UnionParseRequest
  1060. {
  1061. Content = JdStressDeeplinkContent,
  1062. Channel = "jd",
  1063. CommerceType = 0,
  1064. Ip = ip,
  1065. Oaid = oaid,
  1066. RiskStrategy = string.Empty,
  1067. LaunchScene = 0,
  1068. AccountId = 0,
  1069. SpecialText = 0,
  1070. QueryText = string.Empty,
  1071. ClickId = string.Empty,
  1072. Type = "dp",
  1073. Pic = string.Empty
  1074. };
  1075. var result = await UnionParseCore.DeeplinkJdParseAsync(request, cancellationToken);
  1076. sw.Stop();
  1077. string response = result.Content ?? string.Empty;
  1078. var parsed = ParseJdStressResponse(response);
  1079. parsed.Sequence = sequence;
  1080. parsed.ElapsedMs = sw.ElapsedMilliseconds;
  1081. parsed.Response = response;
  1082. parsed.FinishedAt = DateTime.Now;
  1083. return parsed;
  1084. }
  1085. catch (Exception ex)
  1086. {
  1087. sw.Stop();
  1088. return new JdStressRequestResult
  1089. {
  1090. Sequence = sequence,
  1091. ElapsedMs = sw.ElapsedMilliseconds,
  1092. Success = false,
  1093. Message = "exception",
  1094. SubMessage = ex.Message,
  1095. Code = 0,
  1096. Response = ex.ToString(),
  1097. FinishedAt = DateTime.Now,
  1098. IsException = true
  1099. };
  1100. }
  1101. }
  1102. private static JdStressRequestResult ParseJdStressResponse(string response)
  1103. {
  1104. var result = new JdStressRequestResult();
  1105. if (string.IsNullOrWhiteSpace(response))
  1106. {
  1107. result.Message = "empty response";
  1108. return result;
  1109. }
  1110. try
  1111. {
  1112. using var doc = JsonDocument.Parse(response);
  1113. var root = doc.RootElement;
  1114. result.Success = ReadBool(root, "success");
  1115. result.Message = ReadString(root, "message");
  1116. result.SubMessage = ReadString(root, "sub_message");
  1117. result.Code = ReadInt(root, "code");
  1118. }
  1119. catch (Exception ex)
  1120. {
  1121. result.Success = false;
  1122. result.Message = "parse response error";
  1123. result.SubMessage = ex.Message;
  1124. result.Code = 0;
  1125. }
  1126. return result;
  1127. }
  1128. private static bool ReadBool(JsonElement root, string propertyName)
  1129. {
  1130. if (!root.TryGetProperty(propertyName, out var property)) return false;
  1131. return property.ValueKind switch
  1132. {
  1133. JsonValueKind.True => true,
  1134. JsonValueKind.False => false,
  1135. JsonValueKind.Number => property.TryGetInt32(out int value) && value != 0,
  1136. JsonValueKind.String => bool.TryParse(property.GetString(), out bool value) && value,
  1137. _ => false
  1138. };
  1139. }
  1140. private static int ReadInt(JsonElement root, string propertyName)
  1141. {
  1142. if (!root.TryGetProperty(propertyName, out var property)) return 0;
  1143. return property.ValueKind switch
  1144. {
  1145. JsonValueKind.Number => property.TryGetInt32(out int value) ? value : 0,
  1146. JsonValueKind.String => int.TryParse(property.GetString(), out int value) ? value : 0,
  1147. _ => 0
  1148. };
  1149. }
  1150. private static string ReadString(JsonElement root, string propertyName)
  1151. {
  1152. if (!root.TryGetProperty(propertyName, out var property)) return string.Empty;
  1153. if (property.ValueKind == JsonValueKind.Null || property.ValueKind == JsonValueKind.Undefined) return string.Empty;
  1154. return property.ValueKind == JsonValueKind.String ? property.GetString() ?? string.Empty : property.ToString();
  1155. }
  1156. private static bool IsRiskControlResult(JdStressRequestResult result)
  1157. {
  1158. if (!"放弃转链".Equals(result.Message, StringComparison.Ordinal)) return false;
  1159. return result.SubMessage.Contains("控制", StringComparison.Ordinal)
  1160. || result.SubMessage.Contains("风控", StringComparison.Ordinal)
  1161. || result.SubMessage.Contains("限流", StringComparison.Ordinal)
  1162. || result.SubMessage.Contains("频", StringComparison.Ordinal)
  1163. || result.SubMessage.Contains("系统繁忙", StringComparison.Ordinal);
  1164. }
  1165. private static string NormalizeLogValue(string value)
  1166. {
  1167. return (value ?? string.Empty)
  1168. .Replace("\r", "\\r")
  1169. .Replace("\n", "\\n")
  1170. .Replace("\t", " ");
  1171. }
  1172. private static void UpdateMax(ref int target, int value)
  1173. {
  1174. int snapshot;
  1175. while (value > (snapshot = Volatile.Read(ref target))
  1176. && Interlocked.CompareExchange(ref target, value, snapshot) != snapshot)
  1177. {
  1178. }
  1179. }
  1180. private sealed class JdStressRequestResult
  1181. {
  1182. public long Sequence { get; set; }
  1183. public bool Success { get; set; }
  1184. public string Message { get; set; } = string.Empty;
  1185. public string SubMessage { get; set; } = string.Empty;
  1186. public int Code { get; set; }
  1187. public long ElapsedMs { get; set; }
  1188. public string Response { get; set; } = string.Empty;
  1189. public DateTime FinishedAt { get; set; }
  1190. public bool IsException { get; set; }
  1191. }
  1192. private sealed class JdStressRunResult
  1193. {
  1194. public bool success { get; set; }
  1195. public string message { get; set; } = string.Empty;
  1196. public string runId { get; set; } = string.Empty;
  1197. public int requestPerSecond { get; set; }
  1198. public int timeSeconds { get; set; }
  1199. public long expectedRequests { get; set; }
  1200. public long scheduledCount { get; set; }
  1201. public long completedCount { get; set; }
  1202. public long successCount { get; set; }
  1203. public long failCount { get; set; }
  1204. public long exceptionCount { get; set; }
  1205. public long riskControlCount { get; set; }
  1206. public int peakActiveCount { get; set; }
  1207. public double averageElapsedMs { get; set; }
  1208. public double elapsedSeconds { get; set; }
  1209. public Dictionary<string, int> messageCounts { get; set; } = [];
  1210. public Dictionary<string, int> codeCounts { get; set; } = [];
  1211. public JdStressLogInfo log { get; set; } = new();
  1212. }
  1213. private sealed class JdStressLogInfo
  1214. {
  1215. public string type { get; set; } = string.Empty;
  1216. public string dir { get; set; } = string.Empty;
  1217. public string name { get; set; } = string.Empty;
  1218. public string status { get; set; } = string.Empty;
  1219. }
  1220. [HttpGet]
  1221. public async Task<ActionResult> xxx()
  1222. {
  1223. var list = await TkPoolCore.ListAsync();
  1224. if (list == null) return new APIResult(new { success = false, message = "没有有效账号", });
  1225. string message = string.Empty;
  1226. foreach (var account in list)
  1227. {
  1228. try
  1229. {
  1230. var alimama = new AlimamaPlus(account);
  1231. alimama.RenewCookie();
  1232. }
  1233. catch (Exception ex)
  1234. {
  1235. message = $"【cookie续期】xxxx\n{ex.Message}\n{ex.StackTrace}";
  1236. NotifyCore.Notify(new NifyMessage
  1237. {
  1238. message = message,
  1239. priority = NifyMessagePriority.high,
  1240. tags = ["red_circle"]
  1241. });
  1242. continue;
  1243. }
  1244. }
  1245. return new APIResult(new { success = true, message = "ok" });
  1246. }
  1247. }
  1248. }