TestController.cs 56 KB

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