TestController.cs 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961
  1. using molilian.core;
  2. using dodohold.core;
  3. using Dapper;
  4. using Microsoft.AspNetCore.Mvc;
  5. using Org.BouncyCastle.Ocsp;
  6. using System.Text.Json;
  7. using System.Runtime.InteropServices;
  8. using System.Net;
  9. using System.Threading;
  10. using System.Data;
  11. using Microsoft.AspNetCore.Mvc.RazorPages;
  12. using TencentCloud.Soe.V20180724.Models;
  13. using static dodohold.core.ZTOExpress.CreateOrderArgs;
  14. using static Spire.Xls.Core.Spreadsheet.HTMLOptions;
  15. using System.Net.Http.Json;
  16. using System.Text.RegularExpressions;
  17. using System.Text;
  18. using TencentCloud.Oceanus.V20190422.Models;
  19. using YunhuiKit;
  20. using TencentCloud.Cdn.V20180606.Models;
  21. namespace molilian.api.Controllers
  22. {
  23. [ApiController]
  24. [Route("[controller]/[action]")]
  25. public class TestController : ControllerBase
  26. {
  27. protected IHttpContextAccessor _accessor;
  28. public TestController(IHttpContextAccessor accessor)
  29. {
  30. _accessor = accessor;
  31. }
  32. [HttpGet]
  33. public async Task<ActionResult> ecs_list_test()
  34. {
  35. var list = AliyunPoolCore.EcsList();
  36. return new APIResult(new
  37. {
  38. success = true,
  39. msg = "ok",
  40. list
  41. });
  42. }
  43. [HttpGet]
  44. public async Task<ActionResult> testReconnectionRedis()
  45. {
  46. string cacheKey = "test";
  47. RedisKit.SetAsync(cacheKey, 1, 3600);
  48. string val = await RedisKit.GetAsync<string>(cacheKey);
  49. return new APIResult(new { success = "ok", val });
  50. }
  51. [HttpGet]
  52. public async Task<ActionResult> ChangePublicIpByName(string nodeName)
  53. {
  54. var proxy_node = new DBContext.Table("proxy_nodes").Get<dynamic>("nodeName=@nodeName", new { nodeName });
  55. if (proxy_node == null) return new APIResult(new { success = false, msg = "没有匹配的 proxy_nodes 记录" });
  56. int aliyun_id = proxy_node.aliyun_id;
  57. string proxy_server = proxy_node.server;
  58. var account = new DBContext.Table("aliyun_pool").Get<AliyunPoolDTO>(aliyun_id);
  59. if (account == null) return new APIResult(new { success = false, msg = "没有匹配的 aliyun_pool 记录" });
  60. var uri = new Uri(proxy_server);
  61. string privateIp = uri.Host;
  62. AliyunCore core = new AliyunCore(account);
  63. var success = await core.ChangePublicIpAsync(privateIp);
  64. return new APIResult(new { success = "ok" });
  65. }
  66. [HttpGet]
  67. public async Task<ActionResult> ChangePublicIp(int id)
  68. {
  69. id = id >= 20000 ? id - 20000 : id;
  70. var proxy_node = new DBContext.Table("proxy_nodes").Get<dynamic>(id);
  71. if (proxy_node == null) return new APIResult(new { success = false, msg = "没有匹配的 proxy_nodes 记录" });
  72. int aliyun_id = proxy_node.aliyun_id;
  73. string proxy_server = proxy_node.server;
  74. var account = new DBContext.Table("aliyun_pool").Get<AliyunPoolDTO>(aliyun_id);
  75. if (account == null) return new APIResult(new { success = false, msg = "没有匹配的 aliyun_pool 记录" });
  76. var uri = new Uri(proxy_server);
  77. string privateIp = uri.Host;
  78. AliyunCore core = new AliyunCore(account);
  79. var success = await core.ChangePublicIpAsync(privateIp);
  80. return new APIResult(new { success = "ok" });
  81. }
  82. [HttpGet]
  83. //第一部 先加一个网卡,
  84. public async Task<ActionResult> CreateNetworkInterface(int id, string ecsid)
  85. {
  86. AliyunCore core = new AliyunCore(id);
  87. //创建弹性网卡并绑定公网IP
  88. var success = core.EcsCreateNetworkInterface("cn-beijing", ecsid);
  89. return new APIResult(new { success });
  90. }
  91. [HttpGet]
  92. //第二部 给网卡绑定很多个辅助ip
  93. public async Task<ActionResult> EcsAssignPrivateIpAddresses(int id, string ecsid, int count, bool isSecondary = true)
  94. {
  95. AliyunCore core = new AliyunCore(id);
  96. //创建弹性网卡并绑定公网IP
  97. var success = core.EcsAssignPrivateIpAddresses("cn-beijing", ecsid, count, !isSecondary);
  98. return new APIResult(new { success });
  99. }
  100. [HttpGet]
  101. public async Task<ActionResult> QueryAccountBalance()
  102. {
  103. AliyunPlus plus = new AliyunPlus("LTAI5tQbkTjtULQcrWGaw2VJ", "UIkkolVVEddooOKOIUByCqymZkK6ZA");
  104. var response = plus.QueryAccountBalance();
  105. var balance = response.Body.Data.AvailableAmount;
  106. return new APIResult(new { response });
  107. }
  108. //[HttpGet]
  109. //public async Task<ActionResult> test(string table = "tk_parse_logs_shop", int count = 100)
  110. //{
  111. // //table = "tk_parse_logs_shop";
  112. // //table = "tk_parse_logs_live";
  113. // //table = "tk_parse_logs_video";
  114. // string filter = "success=0 AND message='放弃转链' AND reason='非标准链接'";
  115. // for (int i = 0; i < 100; i++)
  116. // {
  117. // try
  118. // {
  119. // var list = new DBContext.Table(table)
  120. // .Where(filter, new { })
  121. // .Limit(count).Select<TkDataDTO>();
  122. // if (!list.Any())
  123. // {
  124. // return new APIResult(new
  125. // {
  126. // success = false,
  127. // message = "所有任务完成"
  128. // });
  129. // }
  130. // foreach (var item in list)
  131. // {
  132. // TkPoolDTO? account = TkPoolCore.GetOne(TkPoolCore.TkAction.parse);
  133. // if (account == null)
  134. // {
  135. // return new APIResult(new
  136. // {
  137. // success = false,
  138. // message = "没有工作账号"
  139. // });
  140. // }
  141. // var result = item.Convert2Json().Convert2Object<TkDataDTO>();
  142. // result.accountId = account.id;
  143. // result.accountName = account.company;
  144. // var alimama = new AlimamaPlus(account);
  145. // int timeout = alimama._config.rt_max;
  146. // using var cts = new CancellationTokenSource();
  147. // cts.CancelAfter(timeout);
  148. // result = await alimama.UnionParseAsync(item.content, result);
  149. // string reason = "非标准链接".Equals(result.reason) ? "非标准链接ok" : result.reason;
  150. // new DBContext.Table(table)
  151. // .Add("linkType", (int)result.link_type)
  152. // .Add("rawContent", result.rawContent)
  153. // .Add("success", result.success)
  154. // .Add("message", result.message)
  155. // .Add("reason", reason)
  156. // .Add("content", result.content)
  157. // .Add("itemId", result.itemId)
  158. // .Add("itemName", result.itemName)
  159. // .Add("pic", result.pic)
  160. // .Add("couponAmount", result.couponAmount)
  161. // .Add("promotionPrice", result.promotionPrice)
  162. // .Add("taoToken", result.taoToken)
  163. // .Add("shortLinkurl", result.shortLinkurl)
  164. // .Add("deeplink_url", result.deeplink_url)
  165. // .Add("num_iid", result.num_iid)
  166. // .Add("elapsedTime", result.elapsedTime)
  167. // .Add("elapsedTime2", result.elapsedTime2)
  168. // .Add("elapsedTime3", result.elapsedTime3)
  169. // .Add("subCode", result.subCode)
  170. // .Where("id=@id", new { item.id })
  171. // .Update();
  172. // }
  173. // }
  174. // catch
  175. // {
  176. // }
  177. // }
  178. // return new APIResult(new
  179. // {
  180. // success = true,
  181. // message = "ok"
  182. // });
  183. //}
  184. [HttpGet]
  185. public async Task<ActionResult> redu_douyin(string command)
  186. {
  187. ReduPlus plus = new ReduPlus("", "", "");
  188. var result = await plus.DouyinParse(command);
  189. return Content(result.Convert2Json());
  190. }
  191. [HttpGet]
  192. public async Task<ActionResult> redu_kuaishou(string command)
  193. {
  194. ReduPlus plus = new ReduPlus("", "", "");
  195. var result = await plus.KuaishouParse(command);
  196. return Content(result.Convert2Json());
  197. }
  198. [HttpPost]
  199. public async Task<ActionResult> test2([FromForm] string jsonContent, [FromForm] string testText)
  200. {
  201. // 用于存储输出结果的StringBuilder
  202. StringBuilder outputBuilder = new StringBuilder();
  203. // 解析JSON数据
  204. JsonDocument jsonDoc = JsonDocument.Parse(jsonContent);
  205. // 获取根元素
  206. JsonElement root = jsonDoc.RootElement;
  207. // 遍历规则
  208. foreach (JsonElement ruleSet in root.EnumerateArray())
  209. {
  210. string platformType = ruleSet.Read("platformType", string.Empty);
  211. string supplier = ruleSet.Read("supplier", string.Empty);
  212. outputBuilder.AppendLine($"平台类型: {platformType}, 供应商: {supplier}");
  213. JsonElement pwdRules = ruleSet.GetProperty("pwdRules");
  214. int idx = 0;
  215. foreach (JsonElement patternElement in pwdRules.EnumerateArray())
  216. {
  217. string pattern = patternElement.GetString();
  218. try
  219. {
  220. // 使用Regex类来编译正则表达式
  221. Regex compiledPattern = new Regex(pattern);
  222. if (compiledPattern.IsMatch(testText))
  223. {
  224. outputBuilder.AppendLine($"规则 {idx + 1}: 匹配\t{pattern}");
  225. }
  226. else
  227. {
  228. outputBuilder.AppendLine($"规则 {idx + 1}: 不匹配");
  229. }
  230. }
  231. catch (Exception e)
  232. {
  233. outputBuilder.AppendLine($"规则 {idx + 1}: 正则表达式错误 - {e.Message}\t{pattern}");
  234. }
  235. idx++;
  236. }
  237. }
  238. outputBuilder.AppendLine("测试完成");
  239. return Content(outputBuilder.ToString());
  240. }
  241. [HttpGet]
  242. public async Task<ActionResult> ip(string ip)
  243. {
  244. string result = IP2RegionPlus.Search(ip);
  245. return new APIResult(new
  246. {
  247. success = true,
  248. message = result
  249. });
  250. }
  251. [HttpPost]
  252. public async Task<ActionResult> testreg([FromBody] JsonElement form)
  253. {
  254. var content = form.Read("s", string.Empty);
  255. string shortLinkurl = AlimamaPlus.GetTaobaoLink(content);
  256. bool is_tao_token = AlimamaPlus.MatchRegexes(content, []);
  257. bool is_other = AlimamaPlus.MatchOtherInfo(content, []);
  258. return new APIResult(new
  259. {
  260. shortLinkurl,
  261. is_tao_token,
  262. is_other
  263. });
  264. }
  265. //[HttpGet]
  266. //public async Task<ActionResult> comparison_tk([FromQuery] int count = 100)
  267. //{
  268. // string cacheKey = ":lock_key:comparison_tk_logs";
  269. // int last_id = RedisHelper.Get<int>(cacheKey);
  270. // string filter = "id>@last_id";
  271. // var result = new DBContext.Table("comparison_tk_logs")
  272. // .Where(filter, new { last_id })
  273. // .Page(count, 1)
  274. // .Order("ID")
  275. // .PageList<TkDataDTO>(false);
  276. // var ip = "127.0.0.1";
  277. // var oaid = "test-comparison_tk_logs";
  278. // foreach (var item in result.List)
  279. // {
  280. // await UnionParseCore.TaobaoParseAsync(item.rawContent, ip, oaid);
  281. // RedisHelper.Set(cacheKey, item.id, 10 * 86400);
  282. // }
  283. // if (result.Count < count)
  284. // {
  285. // cacheKey = ":lock_key:start_comparison_tk";
  286. // RedisHelper.Set(cacheKey, 1, 600);
  287. // }
  288. // return new APIResult(new
  289. // {
  290. // success = true,
  291. // message = "ok"
  292. // });
  293. //}
  294. //[HttpGet]
  295. //public async Task<ActionResult> comparison_tk_raw([FromQuery] int count = 100)
  296. //{
  297. // string cacheKey = ":lock_key:comparison_tk_logs_raw";
  298. // int last_id = RedisHelper.Get<int>(cacheKey);
  299. // string filter = "id>@last_id";
  300. // var result = new DBContext.Table("comparison_tk_logs")
  301. // .Where(filter, new { last_id })
  302. // .Page(count, 1)
  303. // .Order("ID")
  304. // .PageList<TkDataDTO>(false);
  305. // var ip = "127.0.0.1";
  306. // var oaid = "test-comparison_tk_logs_raw";
  307. // foreach (var item in result.List)
  308. // {
  309. // await UnionParseCore.TaobaoParseAsync(item.rawContent, ip, oaid);
  310. // RedisHelper.Set(cacheKey, item.id, 10 * 86400);
  311. // }
  312. // if (result.Count < count)
  313. // {
  314. // cacheKey = ":lock_key:start_comparison_tk";
  315. // RedisHelper.Set(cacheKey, 1, 600);
  316. // }
  317. // return new APIResult(new
  318. // {
  319. // success = true,
  320. // message = "ok"
  321. // });
  322. //}
  323. [HttpGet]
  324. public async Task<ActionResult> testTask()
  325. {
  326. string url = "https://www.taobao.com";
  327. TkDataDTO result = new TkDataDTO();
  328. result.message = "init";
  329. result.success = true;
  330. int timeout = 1000;
  331. using var cts = new CancellationTokenSource();
  332. cts.CancelAfter(timeout);
  333. var requestTask = Task.Run(() => AlimamaPlus.testTaskAsync(result, cts.Token), cts.Token);
  334. var delayTask = Task.Delay(timeout, cts.Token);
  335. var completedTask = await Task.WhenAny(requestTask, delayTask);
  336. if (completedTask == requestTask)
  337. {
  338. result = await requestTask;
  339. }
  340. else
  341. {
  342. cts.Cancel();
  343. result.success = false;
  344. result.message = "放弃转链";
  345. result.reason = "请求超时";
  346. result.itemName = "点击打开淘宝APP";
  347. }
  348. return new APIResult(new
  349. {
  350. success = true,
  351. message = "ok",
  352. result,
  353. });
  354. }
  355. [HttpGet]
  356. public async Task<ActionResult> testThread()
  357. {
  358. // 查看默认的最小和最大线程数
  359. ThreadPool.GetMinThreads(out int defaultMinWorker, out int defaultMinIOC);
  360. ThreadPool.GetMaxThreads(out int defaultMaxWorker, out int defaultMaxIOC);
  361. string tmp = $"Default Min worker threads: {defaultMinWorker}, Min I/O completion threads: {defaultMinIOC}";
  362. string tmp2 = $"Default Max worker threads: {defaultMaxWorker}, Max I/O completion threads: {defaultMaxIOC}";
  363. // 获取当前线程池中可用的工作线程数和 I/O 完成端口线程数
  364. ThreadPool.GetAvailableThreads(out int availableWorkerThreads, out int availableIOCompletionThreads);
  365. string tmp3 = $"Current available worker threads: {availableWorkerThreads}, available I/O completion threads: {availableIOCompletionThreads}";
  366. return new APIResult(new
  367. {
  368. tmp,
  369. tmp2,
  370. tmp3,
  371. });
  372. }
  373. [HttpPost]
  374. public async Task<ActionResult> test1([FromBody] JsonElement form)
  375. {
  376. NotifyCore.Notify(new NifyMessage
  377. {
  378. message = $"【淘宝联盟:test】cookie 掉线",
  379. priority = NifyMessagePriority.high,
  380. tags = ["red_circle"]
  381. });
  382. return new APIResult(new
  383. {
  384. success = true,
  385. message = "ok"
  386. });
  387. }
  388. [HttpGet]
  389. public async Task<ActionResult> RepairTkDailyAccountStats(
  390. DateTime startDate = default,
  391. DateTime endDate = default,
  392. string accountIds = "129,140",
  393. string extraNames = "搜同款_楚颜_128众杰科技,搜同款_广哲2",
  394. bool dryRun = false,
  395. bool repairDailyLogs = true,
  396. bool repairRedis = true,
  397. bool repairRedisNameKeys = true,
  398. int commandTimeoutSeconds = 600)
  399. {
  400. if (startDate == default) startDate = new DateTime(2026, 6, 16);
  401. if (endDate == default) endDate = DateTime.Now.Date;
  402. commandTimeoutSeconds = Math.Clamp(commandTimeoutSeconds, 30, 3600);
  403. startDate = startDate.Date;
  404. endDate = endDate.Date;
  405. if (endDate < startDate)
  406. {
  407. return new APIResult(new { success = false, message = "endDate 不能早于 startDate" });
  408. }
  409. var ids = (accountIds ?? string.Empty)
  410. .Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
  411. .Select(item => int.TryParse(item, out int id) ? id : 0)
  412. .Where(id => id > 0)
  413. .Distinct()
  414. .ToArray();
  415. if (ids.Length == 0)
  416. {
  417. return new APIResult(new { success = false, message = "accountIds 不能为空" });
  418. }
  419. using var conn = CenterHub.GetOpenConnection();
  420. if (conn.State != ConnectionState.Open) conn.Open();
  421. var errors = new List<object>();
  422. var affectedNames = new HashSet<string>(StringComparer.Ordinal);
  423. var accounts = SqlMapper.Query<TkDailyRepairCountRow>(
  424. conn,
  425. "SELECT id accountId, company accountName FROM tk_pool WHERE id IN @ids",
  426. new { ids },
  427. commandTimeout: commandTimeoutSeconds).ToList();
  428. foreach (string name in accounts.Select(item => item.accountName).Where(item => !string.IsNullOrWhiteSpace(item)))
  429. {
  430. affectedNames.Add(name);
  431. }
  432. extraNames ??= string.Empty;
  433. foreach (string name in extraNames
  434. .Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
  435. .Where(item => !string.IsNullOrWhiteSpace(item)))
  436. {
  437. affectedNames.Add(name);
  438. }
  439. foreach (string name in SqlMapper.Query<string>(
  440. conn,
  441. @"
  442. SELECT DISTINCT accountName
  443. FROM center_daily_logs
  444. WHERE channel = 0
  445. AND accountId IN @ids
  446. AND log_date BETWEEN @startDate AND @endDate
  447. AND COALESCE(accountName,'')<>''",
  448. new { ids, startDate, endDate },
  449. commandTimeout: commandTimeoutSeconds))
  450. {
  451. affectedNames.Add(name);
  452. }
  453. var days = EachDay(startDate, endDate).ToList();
  454. var dailyReports = new List<object>();
  455. var endpointCountsByDate = new Dictionary<string, List<TkDailyRepairEndpointCountRow>>();
  456. foreach (var date in days)
  457. {
  458. try
  459. {
  460. string tableName = $"tk_parse_logs_{date:yyyyMMdd}";
  461. if (!TableExists(conn, tableName, commandTimeoutSeconds))
  462. {
  463. dailyReports.Add(new { date = date.ToString("yyyy-MM-dd"), tableName, skipped = true, reason = "table not exists" });
  464. continue;
  465. }
  466. var endpointCounts = SqlMapper.Query<TkDailyRepairEndpointCountRow>(
  467. conn,
  468. $@"
  469. SELECT
  470. l.accountId,
  471. COALESCE(p.company, MAX(l.accountName), '') accountName,
  472. COALESCE(l.end_point, '') end_point,
  473. COUNT(*) total_count,
  474. CAST(COALESCE(SUM(l.success = 1), 0) AS SIGNED) success_count,
  475. CAST(COALESCE(SUM(l.success = 0), 0) AS SIGNED) fail_count,
  476. CAST(COALESCE(SUM(l.message = '放弃转链' OR l.reason = '放弃转链'), 0) AS SIGNED) abandon_count
  477. FROM {tableName} l
  478. LEFT JOIN tk_pool p ON p.id = l.accountId
  479. WHERE l.accountId IN @ids
  480. GROUP BY l.accountId, p.company, l.end_point
  481. ORDER BY l.accountId, l.end_point",
  482. new { ids },
  483. commandTimeout: commandTimeoutSeconds).ToList();
  484. endpointCountsByDate[date.ToString("yyyyMMdd")] = endpointCounts;
  485. var accountCounts = endpointCounts
  486. .GroupBy(item => item.accountId)
  487. .Select(group => new TkDailyRepairCountRow
  488. {
  489. accountId = group.Key,
  490. accountName = accounts.FirstOrDefault(item => item.accountId == group.Key)?.accountName
  491. ?? group.FirstOrDefault()?.accountName
  492. ?? string.Empty,
  493. total_count = group.Sum(item => item.total_count),
  494. success_count = group.Sum(item => item.success_count),
  495. fail_count = group.Sum(item => item.fail_count),
  496. abandon_count = group.Sum(item => item.abandon_count)
  497. })
  498. .ToList();
  499. int dailyLogRows = 0;
  500. var allAccountCounts = ids
  501. .Select(accountId => accountCounts.FirstOrDefault(item => item.accountId == accountId)
  502. ?? new TkDailyRepairCountRow
  503. {
  504. accountId = accountId,
  505. accountName = accounts.FirstOrDefault(item => item.accountId == accountId)?.accountName ?? string.Empty
  506. })
  507. .ToList();
  508. foreach (var counts in allAccountCounts)
  509. {
  510. if (!dryRun && repairDailyLogs)
  511. {
  512. dailyLogRows += UpsertCenterDailyLog(conn, date, counts, commandTimeoutSeconds);
  513. }
  514. }
  515. dailyReports.Add(new
  516. {
  517. date = date.ToString("yyyy-MM-dd"),
  518. tableName,
  519. skipped = false,
  520. dailyLogRows,
  521. source = "mysql:tk_parse_logs_yyyyMMdd",
  522. counts = allAccountCounts,
  523. endpointCounts
  524. });
  525. }
  526. catch (Exception ex)
  527. {
  528. var error = new { scope = "daily_log", date = date.ToString("yyyy-MM-dd"), error = FormatRepairError(ex) };
  529. errors.Add(error);
  530. dailyReports.Add(new { date = date.ToString("yyyy-MM-dd"), skipped = true, reason = "error", error });
  531. }
  532. }
  533. var endpointReports = new List<object>();
  534. if (repairRedis || repairRedisNameKeys)
  535. {
  536. var endpoints = EndPointCore.List(true)
  537. .Where(node => node.status && node.is_public_api && !string.IsNullOrEmpty(EndPointCore.GetRedisServer(node)))
  538. .ToList();
  539. foreach (var endpoint in endpoints)
  540. {
  541. var redisServer = EndPointCore.GetRedisServer(endpoint);
  542. var endpointReport = new List<object>();
  543. try
  544. {
  545. await using var scope = await RedisClientFactory.CreateScopeAsync(redisServer);
  546. var redis = scope.Client;
  547. foreach (var date in days)
  548. {
  549. string dateKey = date.ToString("yyyyMMdd");
  550. if (!endpointCountsByDate.TryGetValue(dateKey, out var endpointCounts))
  551. {
  552. continue;
  553. }
  554. foreach (int accountId in ids)
  555. {
  556. var counts = endpointCounts.FirstOrDefault(item =>
  557. item.accountId == accountId &&
  558. string.Equals(item.end_point, endpoint.name, StringComparison.Ordinal))
  559. ?? new TkDailyRepairEndpointCountRow
  560. {
  561. accountId = accountId,
  562. accountName = accounts.FirstOrDefault(item => item.accountId == accountId)?.accountName ?? string.Empty,
  563. end_point = endpoint.name
  564. };
  565. if (!dryRun && repairRedis)
  566. {
  567. await WriteParseCountKeysAsync(redis, $"tb_{accountId}", dateKey, counts);
  568. }
  569. endpointReport.Add(new
  570. {
  571. date = date.ToString("yyyy-MM-dd"),
  572. bucket = $"tb_{accountId}",
  573. counts
  574. });
  575. }
  576. if (repairRedisNameKeys)
  577. {
  578. foreach (string name in affectedNames)
  579. {
  580. var before = await ReadParseCountKeysAsync(redis, name, dateKey);
  581. if (!dryRun)
  582. {
  583. await DeleteParseBucketKeysAsync(redis, name, dateKey);
  584. }
  585. endpointReport.Add(new
  586. {
  587. date = date.ToString("yyyy-MM-dd"),
  588. deletedBucket = name,
  589. before
  590. });
  591. }
  592. }
  593. }
  594. }
  595. catch (Exception ex)
  596. {
  597. errors.Add(new { scope = "redis_endpoint", endpoint = endpoint.name, error = FormatRepairError(ex) });
  598. }
  599. endpointReports.Add(new
  600. {
  601. endpoint = endpoint.name,
  602. endpoint.description,
  603. dryRun,
  604. deletedBuckets = endpointReport
  605. });
  606. }
  607. }
  608. return new APIResult(new
  609. {
  610. success = errors.Count == 0,
  611. dryRun,
  612. accountIds = ids,
  613. extraNames,
  614. repairDailyLogs,
  615. repairRedis,
  616. repairRedisNameKeys,
  617. commandTimeoutSeconds,
  618. startDate = startDate.ToString("yyyy-MM-dd"),
  619. endDate = endDate.ToString("yyyy-MM-dd"),
  620. affectedNames = affectedNames.OrderBy(item => item).ToList(),
  621. mysql = dailyReports,
  622. redis = endpointReports,
  623. errors
  624. });
  625. }
  626. private static IEnumerable<DateTime> EachDay(DateTime startDate, DateTime endDate)
  627. {
  628. for (var date = startDate.Date; date <= endDate.Date; date = date.AddDays(1))
  629. {
  630. yield return date;
  631. }
  632. }
  633. private static bool TableExists(IDbConnection conn, string tableName, int commandTimeoutSeconds)
  634. {
  635. const string sql = @"
  636. SELECT COUNT(*)
  637. FROM information_schema.tables
  638. WHERE table_schema = DATABASE()
  639. AND table_name = @tableName";
  640. return SqlMapper.ExecuteScalar<int>(
  641. conn,
  642. sql,
  643. new { tableName },
  644. commandTimeout: commandTimeoutSeconds) > 0;
  645. }
  646. private static int UpsertCenterDailyLog(IDbConnection conn, DateTime date, TkDailyRepairCountRow counts, int commandTimeoutSeconds)
  647. {
  648. if (string.IsNullOrWhiteSpace(counts.accountName)) return 0;
  649. string successPercentage = counts.total_count > 0 ? $"{counts.success_count / (double)counts.total_count * 100:f2}%" : string.Empty;
  650. string abandonPercentage = counts.total_count > 0 ? $"{counts.abandon_count / (double)counts.total_count * 100:f2}%" : string.Empty;
  651. var existingIds = SqlMapper.Query<int>(
  652. conn,
  653. @"
  654. SELECT id
  655. FROM center_daily_logs
  656. WHERE channel = 0
  657. AND log_date = @date
  658. AND accountId = @accountId
  659. ORDER BY id",
  660. new { date, counts.accountId },
  661. commandTimeout: commandTimeoutSeconds).ToList();
  662. if (existingIds.Count > 0)
  663. {
  664. int affectedRows = SqlMapper.Execute(
  665. conn,
  666. @"
  667. UPDATE center_daily_logs
  668. SET accountName = @accountName,
  669. parse_total_count = @totalCount,
  670. parse_success_count = @successCount,
  671. parse_abandon_count = @abandonCount,
  672. parse_success_percentage = @successPercentage,
  673. parse_abandon_percentage = @abandonPercentage,
  674. last_time = NOW()
  675. WHERE id = @id",
  676. new
  677. {
  678. id = existingIds[0],
  679. counts.accountName,
  680. totalCount = counts.total_count,
  681. successCount = counts.success_count,
  682. abandonCount = counts.abandon_count,
  683. successPercentage,
  684. abandonPercentage
  685. },
  686. commandTimeout: commandTimeoutSeconds);
  687. if (existingIds.Count > 1)
  688. {
  689. affectedRows += SqlMapper.Execute(
  690. conn,
  691. "DELETE FROM center_daily_logs WHERE id IN @ids",
  692. new { ids = existingIds.Skip(1).ToArray() },
  693. commandTimeout: commandTimeoutSeconds);
  694. }
  695. return affectedRows;
  696. }
  697. if (counts.total_count <= 0) return 0;
  698. return SqlMapper.Execute(
  699. conn,
  700. @"
  701. INSERT INTO center_daily_logs
  702. (channel, accountId, accountName, log_date, create_time, last_time,
  703. parse_total_count, parse_success_count, parse_abandon_count,
  704. parse_success_percentage, parse_abandon_percentage)
  705. VALUES
  706. (0, @accountId, @accountName, @date, NOW(), NOW(),
  707. @totalCount, @successCount, @abandonCount,
  708. @successPercentage, @abandonPercentage)",
  709. new
  710. {
  711. date,
  712. counts.accountId,
  713. counts.accountName,
  714. totalCount = counts.total_count,
  715. successCount = counts.success_count,
  716. abandonCount = counts.abandon_count,
  717. successPercentage,
  718. abandonPercentage
  719. },
  720. commandTimeout: commandTimeoutSeconds);
  721. }
  722. private static async Task<TkDailyRepairCountRow> ReadParseCountKeysAsync(YunhuiKit.RedisClient redis, string bucket, string dateKey)
  723. {
  724. return new TkDailyRepairCountRow
  725. {
  726. accountName = bucket,
  727. total_count = await redis.GetAsync<int>($":parse_total:{bucket}:{dateKey}"),
  728. success_count = await redis.GetAsync<int>($":parse_total:{bucket}:success:{dateKey}"),
  729. fail_count = await redis.GetAsync<int>($":parse_total:{bucket}:fail:{dateKey}"),
  730. abandon_count = await redis.GetAsync<int>($":parse_total:{bucket}:放弃转链:{dateKey}")
  731. };
  732. }
  733. private static async Task DeleteParseBucketKeysAsync(YunhuiKit.RedisClient redis, string bucket, string dateKey)
  734. {
  735. var keys = new List<string>
  736. {
  737. $":parse_total:{bucket}:{dateKey}",
  738. $":parse_total:{bucket}:success:{dateKey}",
  739. $":parse_total:{bucket}:fail:{dateKey}",
  740. $":parse_total:{bucket}:放弃转链:{dateKey}",
  741. $":parse_total:{bucket}:message:{dateKey}",
  742. $":parse_total:{bucket}:reason:{dateKey}"
  743. };
  744. foreach (string dpBucket in new[] { "dp_none", "dp_home", "dp_success", "dp_fail" })
  745. {
  746. keys.Add($":parse_total:{dpBucket}:{bucket}:{dateKey}");
  747. keys.Add($":parse_total:{dpBucket}:{bucket}:success:{dateKey}");
  748. keys.Add($":parse_total:{dpBucket}:{bucket}:fail:{dateKey}");
  749. keys.Add($":parse_total:{dpBucket}:{bucket}:放弃转链:{dateKey}");
  750. keys.Add($":parse_total:{dpBucket}:{bucket}:message:{dateKey}");
  751. keys.Add($":parse_total:{dpBucket}:{bucket}:reason:{dateKey}");
  752. }
  753. await redis.DelAsync(keys.ToArray());
  754. }
  755. private static string FormatRepairError(Exception ex)
  756. {
  757. return ex.InnerException == null ? ex.Message : $"{ex.Message} | {ex.InnerException.Message}";
  758. }
  759. private static async Task WriteParseCountKeysAsync(YunhuiKit.RedisClient redis, string bucket, string dateKey, TkDailyRepairCountRow counts)
  760. {
  761. var keys = new[]
  762. {
  763. $":parse_total:{bucket}:{dateKey}",
  764. $":parse_total:{bucket}:success:{dateKey}",
  765. $":parse_total:{bucket}:fail:{dateKey}",
  766. $":parse_total:{bucket}:放弃转链:{dateKey}"
  767. };
  768. if (counts.total_count <= 0)
  769. {
  770. await redis.DelAsync(keys);
  771. return;
  772. }
  773. await redis.SetAsync(keys[0], counts.total_count, 90 * 86400);
  774. await redis.SetAsync(keys[1], counts.success_count, 90 * 86400);
  775. await redis.SetAsync(keys[2], counts.fail_count, 90 * 86400);
  776. await redis.SetAsync(keys[3], counts.abandon_count, 90 * 86400);
  777. }
  778. private class TkDailyRepairCountRow
  779. {
  780. public int accountId { get; set; }
  781. public string accountName { get; set; } = string.Empty;
  782. public long total_count { get; set; }
  783. public long success_count { get; set; }
  784. public long fail_count { get; set; }
  785. public long abandon_count { get; set; }
  786. }
  787. private sealed class TkDailyRepairEndpointCountRow : TkDailyRepairCountRow
  788. {
  789. public string end_point { get; set; } = string.Empty;
  790. }
  791. [HttpGet]
  792. public async Task<ActionResult> xxx()
  793. {
  794. var list = await TkPoolCore.ListAsync();
  795. if (list == null) return new APIResult(new { success = false, message = "没有有效账号", });
  796. string message = string.Empty;
  797. foreach (var account in list)
  798. {
  799. try
  800. {
  801. var alimama = new AlimamaPlus(account);
  802. alimama.RenewCookie();
  803. }
  804. catch (Exception ex)
  805. {
  806. message = $"【cookie续期】xxxx\n{ex.Message}\n{ex.StackTrace}";
  807. NotifyCore.Notify(new NifyMessage
  808. {
  809. message = message,
  810. priority = NifyMessagePriority.high,
  811. tags = ["red_circle"]
  812. });
  813. continue;
  814. }
  815. }
  816. return new APIResult(new { success = true, message = "ok" });
  817. }
  818. }
  819. }