base.cs 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678
  1. using dodohold.core;
  2. using CSRedis;
  3. using System.Data;
  4. using System.Diagnostics;
  5. using YunhuiKit;
  6. using static ICSharpCode.SharpZipLib.Zip.ExtendedUnixData;
  7. namespace molilian.core
  8. {
  9. public partial class TkLogCore
  10. {
  11. public static bool save_dailys_log = false;
  12. static TkLogCore()
  13. {
  14. int flag = RedisHelper.Get<int>("turn:save_dailys_log");
  15. if (flag == 1) save_dailys_log = true;
  16. }
  17. private static readonly SemaphoreSlim semaphore = new SemaphoreSlim(10, 10);
  18. private const int StatsHourlyExpireSeconds = 7 * 86400;
  19. private const int StatsDailyExpireSeconds = 90 * 86400;
  20. private const int StatsMonthlyExpireSeconds = 366 * 86400;
  21. public static async Task<int> BatchInsertLogDBAsync(int limit)
  22. {
  23. await semaphore.WaitAsync().ConfigureAwait(false);
  24. try
  25. {
  26. var tasks = EndPointCore.List()
  27. .Where(node => node.is_public_api && !string.IsNullOrEmpty(node.redis_server))
  28. .Where(node => CenterHub.IsCenter ? !node.is_coupon_api : node.is_coupon_api)
  29. .Select(async node =>
  30. {
  31. try
  32. {
  33. var redisServer = EndPointCore.GetRedisServer(node);
  34. if (string.IsNullOrEmpty(redisServer)) return 0;
  35. await using var scope = await RedisClientFactory.CreateScopeAsync(redisServer);
  36. return BatchInsertLogDB(limit, scope.Client);
  37. }
  38. catch (Exception ex)
  39. {
  40. // TODO: 添加日志记录
  41. return 0;
  42. }
  43. });
  44. var results = await Task.WhenAll(tasks).ConfigureAwait(false);
  45. return results.Sum();
  46. }
  47. catch (Exception)
  48. {
  49. return 0;
  50. }
  51. finally
  52. {
  53. semaphore.Release();
  54. }
  55. }
  56. public static int BatchInsertLogDB(int limit, YunhuiKit.RedisClient redis)
  57. {
  58. int total = 0;
  59. //using var connection = DBContext.GetOpenConnection();
  60. //connection.Open();
  61. //using var transaction = connection.BeginTransaction();
  62. using IDbTransaction transaction = null;
  63. using IDbConnection connection = null;
  64. try
  65. {
  66. Stopwatch stopwatch = new Stopwatch(); // 创建一个计时器
  67. var tasks = new List<Task<int>>
  68. {
  69. RunTaskWithLoggingAsync(() => InsertPromotionImgAsync(limit, redis), "task_insert_promotion_img_logs"),
  70. RunTaskWithLoggingAsync(() => InsertParseTkLogAsync(limit, redis), "task_insert_parse_tb_logs"),
  71. RunTaskWithLoggingAsync(() => InsertParseJDLogAsync(limit, redis), "task_insert_parse_jd_logs"),
  72. RunTaskWithLoggingAsync(() => InsertParsePddLogAsync(limit, redis), "task_insert_parse_pdd_logs"),
  73. RunTaskWithLoggingAsync(() => InsertParseDyLogAsync(limit, redis), "task_insert_parse_dy_logs"),
  74. RunTaskWithLoggingAsync(() => InsertParseKsLogAsync(limit, redis), "task_insert_parse_ks_logs"),
  75. RunTaskWithLoggingAsync(() => InsertToolLogAsync(limit, redis), "task_insert_parse_tool_logs"),
  76. RunTaskWithLoggingAsync(() => InsertDeeplinkLogAsync(limit, redis), "task_insert_parse_deeplink_logs"),
  77. RunTaskWithLoggingAsync(() => InsertCouponLogAsync(limit, redis), "task_insert_parse_coupon_logs"),
  78. RunTaskWithLoggingAsync(() => InsertActivityLogAsync(limit, redis), "task_insert_activity_logs"),
  79. RunTaskWithLoggingAsync(() => InsertCpsLogAsync(limit, redis), "task_insert_parse_cps_logs"),
  80. RunTaskWithLoggingAsync(() => TracksCore.InsertTrackRequestLogAsync(limit, redis), "task_insert_track_request_logs"),
  81. //Task.Run(() => RunTaskWithLogging(() => task_insert_tk_logs(limit, redis), "task_insert_tk_logs")),
  82. //Task.Run(() => RunTaskWithLogging(() => task_insert_parse_tb_logs(limit, redis), "task_insert_parse_tb_logs")),
  83. //Task.Run(() => RunTaskWithLogging(() => task_insert_parse_jd_logs(limit, redis), "task_insert_parse_jd_logs")),
  84. //Task.Run(() => RunTaskWithLogging(() => task_insert_parse_pdd_logs(limit, redis), "task_insert_parse_pdd_logs")),
  85. //Task.Run(() => RunTaskWithLogging(() => task_insert_parse_dy_logs(limit, redis), "task_insert_parse_dy_logs")),
  86. //Task.Run(() => RunTaskWithLogging(() => task_insert_parse_tool_logs(limit, redis), "task_insert_parse_tool_logs")),
  87. //Task.Run(() => RunTaskWithLogging(() => task_insert_parse_deeplink_logs(limit, redis), "task_insert_parse_deeplink_logs")),
  88. //Task.Run(() => RunTaskWithLogging(() => task_insert_parse_coupon_logs(limit, redis), "task_insert_parse_coupon_logs")),
  89. //Task.Run(() => RunTaskWithLogging(() => task_insert_parse_cps_logs(limit, redis), "task_insert_parse_cps_logs")),
  90. //Task.Run(() => RunTaskWithLogging(() => task_insert_promotion_img_logs(limit, redis), "task_insert_promotion_img_logs")),
  91. //Task.Run(() => RunTaskWithLogging(() => task_insert_parse_ks_logs(limit, redis), "task_insert_parse_ks_logs"))
  92. };
  93. // 等待所有任务完成
  94. Task.WhenAll(tasks).Wait();//252行
  95. // 计算所有任务的结果总和
  96. total = tasks.Select(t => t.Result).Sum();
  97. //transaction.Commit();
  98. }
  99. catch (Exception ex)
  100. {
  101. //transaction.Rollback();
  102. // 构建异常详细信息字符串
  103. string detailedError = $"{ex.Message}\n" +
  104. $"堆栈跟踪: {ex.StackTrace}\n";
  105. if (ex.InnerException != null)
  106. {
  107. detailedError += $"内部异常: {ex.InnerException.Message}\n" +
  108. $"内部堆栈跟踪: {ex.InnerException.StackTrace}\n";
  109. }
  110. // 如果异常包含其他数据,也可以记录下来
  111. if (ex.Data != null && ex.Data.Count > 0)
  112. {
  113. detailedError += "附加数据:\n";
  114. foreach (var key in ex.Data.Keys)
  115. {
  116. detailedError += $"{key}: {ex.Data[key]}\n";
  117. }
  118. }
  119. _ = new LoggerLibrary("database_error", "parse_log")
  120. .Info(detailedError)
  121. .SaveAsync();
  122. NotifyCore.Notify(new NifyMessage
  123. {
  124. message = $"【Exception】\n{detailedError}",
  125. priority = NifyMessagePriority.high,
  126. tags = ["red_circle"]
  127. });
  128. }
  129. finally
  130. {
  131. //connection.Close();
  132. }
  133. return total;
  134. }
  135. private static async Task<int> RunTaskWithLoggingAsync(Func<Task<int>> taskFunc, string taskName)
  136. {
  137. LoggerLibrary log = new LoggerLibrary("debug", "BatchInsertLogDB"); // 创建日志对象
  138. Stopwatch stopwatch = new Stopwatch();
  139. stopwatch.Start();
  140. int taskTotal = await taskFunc();
  141. stopwatch.Stop();
  142. log.Info($"{taskName} 耗时: {stopwatch.ElapsedMilliseconds} ms,\t插入记录数: {taskTotal}");
  143. log.SaveAsync();
  144. return taskTotal;
  145. }
  146. /// <summary>
  147. /// 过时方法 随时删除
  148. /// </summary>
  149. /// <param name="channel"></param>
  150. /// <param name="accountId"></param>
  151. /// <param name="accountName"></param>
  152. /// <param name="success"></param>
  153. /// <param name="message"></param>
  154. /// <param name="reason"></param>
  155. private static void saveCache(string channel, int accountId, string accountName, bool success, string message, string reason)
  156. {
  157. saveAccountCache("all", success, message, reason);
  158. saveAccountCache($"{channel}", success, message, reason);
  159. saveAccountCache($"{accountName}", success, message, reason);
  160. if (accountId != 0)
  161. {
  162. //todo 放着跑两天,要将读取的地方改成读取accountid
  163. saveAccountCache($"{channel}_{accountId}", success, message, reason);
  164. }
  165. }
  166. private static void saveAccountCache(string accountName, bool success, string message, string reason)
  167. {
  168. SaveStatsAccountCache("total", accountName, success, message, reason);
  169. }
  170. private static async Task saveUnionCouponParseCacheAsync(TkDataDTO data)
  171. {
  172. string cacheKey = $":cache:parse:{data.ip}_{data.oaid}_{data.itemId}";
  173. await EndPointCore.ProcessEndPointNodesAsync(node =>
  174. {
  175. if (!node.is_coupon_api) return Task.CompletedTask;
  176. if (string.IsNullOrEmpty(node.redis_server)) return Task.CompletedTask;
  177. var redis = RedisClientManager.GetRedisClient(node.redis_server);
  178. redis.Set(cacheKey, 1, 2 * 86400);
  179. return Task.CompletedTask;
  180. });
  181. }
  182. private async static Task saveClientRequestTotalAsync(TkChannelEnum channel, string ip, string oaid)
  183. {
  184. await saveClientRequestTotalAsync(channel.ToString(), ip, oaid);
  185. }
  186. private static async Task saveClientRequestTotalAsync(string channel, string ip, string oaid)
  187. {
  188. await EndPointCore.ProcessEndPointNodesAsync(node =>
  189. {
  190. if (string.IsNullOrEmpty(node.redis_server)) return Task.CompletedTask;
  191. if (!node.is_public_api) return Task.CompletedTask;
  192. #if DEBUG
  193. switch (node.name)
  194. {
  195. case "bj":
  196. node.redis_server = "101.200.152.61:6379,password=pKBiS4ka2IpXayIdcx00,defaultDatabase=0,idleTimeout=20000,preheat=3,tryit=2,ssl=false,prefix=webhook";
  197. break;
  198. case "gz":
  199. node.redis_server = "8.138.110.158:6379,password=pKBiS4ka2IpXayIdcx00,defaultDatabase=0,idleTimeout=20000,preheat=3,tryit=2,ssl=false,prefix=webhook";
  200. break;
  201. case "coupon1":
  202. node.redis_server = "123.56.185.166:6379,password=pKBiS4ka2IpXayIdcx00,defaultDatabase=0,idleTimeout=20000,preheat=3,tryit=2,ssl=false,prefix=coupon";
  203. break;
  204. default: return Task.CompletedTask;
  205. }
  206. #endif
  207. var redis = RedisClientManager.GetRedisClient(node.redis_server);
  208. string cacheKey = $":cache:{channel}:ip:{DateTime.Now:yyyyMMdd}:{ip}";
  209. redis.IncrBy(cacheKey);
  210. redis.Expire(cacheKey, 86400);
  211. if (!string.IsNullOrEmpty(oaid))
  212. {
  213. cacheKey = $":cache:{channel}:oaid:{DateTime.Now:yyyyMMdd}:{oaid}";
  214. redis.IncrBy(cacheKey);
  215. redis.Expire(cacheKey, 86400);
  216. }
  217. return Task.CompletedTask;
  218. });
  219. }
  220. public static bool InBlacklist(string blacklist, string oaid)
  221. {
  222. if (string.IsNullOrEmpty(blacklist)) return false;
  223. var arr = blacklist.Split(new[] { "\r\n" }, StringSplitOptions.None)
  224. .Select(s => s.Trim()).ToArray();
  225. return arr.Contains(oaid);
  226. }
  227. public static int getClientRequestTotalByOAID(TkChannelEnum channel, string oaid)
  228. {
  229. if (string.IsNullOrEmpty(oaid)) return 0;
  230. string cacheKey = $":cache:{channel}:oaid:{DateTime.Now:yyyyMMdd}:{oaid}";
  231. return RedisHelper.Get<int>(cacheKey);
  232. }
  233. public static int getClientRequestTotalByIp(TkChannelEnum channel, string ip)
  234. {
  235. if (string.IsNullOrEmpty(ip)) return 0;
  236. string cacheKey = $":cache:{channel}:ip:{DateTime.Now:yyyyMMdd}:{ip}";
  237. return RedisHelper.Get<int>(cacheKey);
  238. }
  239. public static int getClientRequestTotalByOAID(string channel, string oaid)
  240. {
  241. if (string.IsNullOrEmpty(oaid)) return 0;
  242. string cacheKey = $":cache:{channel}:oaid:{DateTime.Now:yyyyMMdd}:{oaid}";
  243. return RedisHelper.Get<int>(cacheKey);
  244. }
  245. public static int getClientRequestTotalByIp(string channel, string ip)
  246. {
  247. if (string.IsNullOrEmpty(ip)) return 0;
  248. string cacheKey = $":cache:{channel}:ip:{DateTime.Now:yyyyMMdd}:{ip}";
  249. return RedisHelper.Get<int>(cacheKey);
  250. }
  251. private static void saveClientRequestTotal(CpsChannelEnum channel, string ip, string oaid)
  252. {
  253. string cacheKey = $":cache:cps_{channel}:ip:{DateTime.Now:yyyyMMdd}:{ip}";
  254. RedisHelper.IncrBy(cacheKey);
  255. RedisHelper.Expire(cacheKey, 86400);
  256. if (!string.IsNullOrEmpty(oaid))
  257. {
  258. cacheKey = $":cache:cps_{channel}:oaid:{DateTime.Now:yyyyMMdd}:{oaid}";
  259. RedisHelper.IncrBy(cacheKey);
  260. RedisHelper.Expire(cacheKey, 86400);
  261. }
  262. }
  263. public static int getClientRequestTotalByOAID(CpsChannelEnum channel, string oaid)
  264. {
  265. if (string.IsNullOrEmpty(oaid)) return 0;
  266. string cacheKey = $":cache:cps_{channel}:oaid:{DateTime.Now:yyyyMMdd}:{oaid}";
  267. return RedisHelper.Get<int>(cacheKey);
  268. }
  269. public static int getClientRequestTotalByIp(CpsChannelEnum channel, string ip)
  270. {
  271. if (string.IsNullOrEmpty(ip)) return 0;
  272. string cacheKey = $":cache:cps_{channel}:ip:{DateTime.Now:yyyyMMdd}:{ip}";
  273. return RedisHelper.Get<int>(cacheKey);
  274. }
  275. private static async Task SaveParseCacheAsync(string channel, int accountId, string accountName,
  276. bool success, string message, string reason, string deeplink)
  277. {
  278. // Account reports read this dimension directly. Record it first so failures in
  279. // secondary aggregate dimensions cannot leave the account row incomplete.
  280. if (accountId != 0)
  281. {
  282. await SaveParseAccountCacheAsync($"{channel}_{accountId}", success, message, reason);
  283. }
  284. string dp_flag = deeplink switch
  285. {
  286. "" => "none",
  287. "tbopen://m.taobao.com/tbopen/index.html" or
  288. "pinduoduo://com.xunmeng.pinduoduo/" or
  289. "snssdk1128://feed?refer=web" or
  290. "bdnetdisk://n/action.EXTERNAL_ACTIVITY" or
  291. "openapp.jdmobile://virtual?params=" or "openapp.jdmobile://" => "home",
  292. _ => success ? "success" : "fail",
  293. };
  294. //关于dp的缓存
  295. await SaveParseAccountCacheAsync($"dp_{dp_flag}:all", success, message, reason);
  296. await SaveParseAccountCacheAsync($"dp_{dp_flag}:{channel}", success, message, reason);
  297. if (accountId == 0 && !string.IsNullOrEmpty(accountName))
  298. {
  299. await SaveParseAccountCacheAsync($"dp_{dp_flag}:{accountName}", success, message, reason);
  300. }
  301. if (accountId != 0)
  302. {
  303. await SaveParseAccountCacheAsync($"dp_{dp_flag}:{channel}_{accountId}", success, message, reason);
  304. }
  305. await SaveParseAccountCacheAsync("all", success, message, reason);
  306. await SaveParseAccountCacheAsync($"dp_channel:{channel}", success, message, reason);
  307. await SaveParseAccountCacheAsync($"{channel}", success, message, reason);
  308. if (accountId == 0 && !string.IsNullOrEmpty(accountName))
  309. {
  310. await SaveParseAccountCacheAsync($"{accountName}", success, message, reason);
  311. }
  312. }
  313. private static async Task RecordParseMetricsAsync(
  314. string channel,
  315. int accountId,
  316. string accountName,
  317. bool success,
  318. string message,
  319. string reason,
  320. string deeplink,
  321. string riskStrategy,
  322. int launchScene)
  323. {
  324. // The new parse_metric counters must not depend on the legacy Redis statistics
  325. // completing successfully. The MySQL log queue is also independent of both.
  326. await TracksCore.RecordParseResultAsync(
  327. channel,
  328. riskStrategy,
  329. launchScene,
  330. accountId,
  331. success);
  332. try
  333. {
  334. await SaveParseCacheAsync(
  335. channel,
  336. accountId,
  337. accountName,
  338. success,
  339. message,
  340. reason,
  341. deeplink);
  342. }
  343. catch (Exception ex)
  344. {
  345. _ = new LoggerLibrary("TkLogCore", "RecordParseMetrics")
  346. .Info($"channel={channel}, accountId={accountId}, riskStrategy={riskStrategy}, launchScene={launchScene}")
  347. .Info(ex.Message, ex.StackTrace)
  348. .SaveAsync();
  349. }
  350. }
  351. private static async Task SaveParseAccountCacheAsync(string accountName, bool success,
  352. string message, string reason)
  353. {
  354. await SaveStatsAccountCacheAsync("parse_total", accountName, success, message, reason);
  355. }
  356. private static void saveParseCache(string channel, int accountId, string accountName,
  357. bool success, string message, string reason, string deeplink)
  358. {
  359. string dp_flag = deeplink switch
  360. {
  361. "" => "none",
  362. "tbopen://m.taobao.com/tbopen/index.html" or
  363. "pinduoduo://com.xunmeng.pinduoduo/" or
  364. "snssdk1128://feed?refer=web" or
  365. "bdnetdisk://n/action.EXTERNAL_ACTIVITY" or
  366. "openapp.jdmobile://virtual?params=" or "openapp.jdmobile://" => "home",
  367. _ => success ? "success" : "fail",
  368. };
  369. //关于dp的缓存
  370. saveParseAccountCache($"dp_{dp_flag}:all", success, message, reason);
  371. saveParseAccountCache($"dp_{dp_flag}:{channel}", success, message, reason);
  372. if (accountId == 0 && !string.IsNullOrEmpty(accountName))
  373. {
  374. saveParseAccountCache($"dp_{dp_flag}:{accountName}", success, message, reason);
  375. }
  376. if (accountId != 0)
  377. {
  378. saveParseAccountCache($"dp_{dp_flag}:{channel}_{accountId}", success, message, reason);
  379. }
  380. saveParseAccountCache("all", success, message, reason);
  381. saveParseAccountCache($"{channel}", success, message, reason);
  382. if (accountId == 0 && !string.IsNullOrEmpty(accountName))
  383. {
  384. saveParseAccountCache($"{accountName}", success, message, reason);
  385. }
  386. if (accountId != 0)
  387. {
  388. saveParseAccountCache($"{channel}_{accountId}", success, message, reason);
  389. }
  390. }
  391. private static void saveParseAccountCache(string accountName, bool success,
  392. string message, string reason)
  393. {
  394. SaveStatsAccountCache("parse_total", accountName, success, message, reason);
  395. }
  396. private static void SaveStatsAccountCache(string prefix, string accountName, bool success,
  397. string message, string reason)
  398. {
  399. var now = DateTime.Now;
  400. string month = now.ToString("yyyyMM");
  401. string day = now.ToString("yyyyMMdd");
  402. string hour = now.ToString("yyyyMMddHH");
  403. SaveStatsCount(prefix, accountName, month);
  404. SaveStatsCount(prefix, accountName, day);
  405. SaveStatsCount(prefix, accountName, hour);
  406. string result = success ? "success" : "fail";
  407. SaveStatsCount(prefix, accountName, month, result);
  408. SaveStatsCount(prefix, accountName, day, result);
  409. SaveStatsCount(prefix, accountName, hour, result);
  410. SaveStatsDimension(prefix, accountName, "message", message, month);
  411. SaveStatsDimension(prefix, accountName, "message", message, day);
  412. SaveStatsDimension(prefix, accountName, "message", message, hour);
  413. SaveStatsDimension(prefix, accountName, "reason", reason, month);
  414. SaveStatsDimension(prefix, accountName, "reason", reason, day);
  415. SaveStatsDimension(prefix, accountName, "reason", reason, hour);
  416. }
  417. private static async Task SaveStatsAccountCacheAsync(string prefix, string accountName, bool success,
  418. string message, string reason)
  419. {
  420. var now = DateTime.Now;
  421. string result = success ? "success" : "fail";
  422. string[] timeKeys =
  423. [
  424. now.ToString("yyyyMM"),
  425. now.ToString("yyyyMMdd"),
  426. now.ToString("yyyyMMddHH")
  427. ];
  428. Exception? firstError = null;
  429. async Task TrySaveAsync(Func<Task> action)
  430. {
  431. try
  432. {
  433. await action();
  434. }
  435. catch (Exception ex)
  436. {
  437. firstError ??= ex;
  438. }
  439. }
  440. foreach (string timeKey in timeKeys)
  441. {
  442. // Keep total and result writes adjacent and isolate every dimension. A single
  443. // transient Redis error must not suppress all success/fail counters that follow.
  444. await TrySaveAsync(() => SaveStatsCountAsync(prefix, accountName, timeKey));
  445. await TrySaveAsync(() => SaveStatsCountAsync(prefix, accountName, timeKey, result));
  446. await TrySaveAsync(() => SaveStatsDimensionAsync(prefix, accountName, "message", message, timeKey));
  447. await TrySaveAsync(() => SaveStatsDimensionAsync(prefix, accountName, "reason", reason, timeKey));
  448. }
  449. if (firstError != null)
  450. {
  451. _ = new LoggerLibrary("TkLogCore", "SaveStatsAccountCache")
  452. .Info($"prefix={prefix}, accountName={accountName}")
  453. .Info(firstError.Message, firstError.StackTrace)
  454. .SaveAsync();
  455. }
  456. }
  457. private static void SaveStatsCount(string prefix, string accountName, string timeKey, string? dimension = null)
  458. {
  459. string key = string.IsNullOrEmpty(dimension)
  460. ? $":{prefix}:{accountName}:{timeKey}"
  461. : $":{prefix}:{accountName}:{dimension}:{timeKey}";
  462. RedisHelper.IncrBy(key);
  463. RedisHelper.Expire(key, GetStatsExpireSeconds(timeKey));
  464. }
  465. private static async Task SaveStatsCountAsync(string prefix, string accountName, string timeKey, string? dimension = null)
  466. {
  467. string key = string.IsNullOrEmpty(dimension)
  468. ? $":{prefix}:{accountName}:{timeKey}"
  469. : $":{prefix}:{accountName}:{dimension}:{timeKey}";
  470. await RedisKit.IncrByAsync(key);
  471. await RedisKit.ExpireAsync(key, GetStatsExpireSeconds(timeKey));
  472. }
  473. private static void SaveStatsDimension(string prefix, string accountName, string name, string value, string timeKey)
  474. {
  475. if (string.IsNullOrEmpty(value)) return;
  476. int expireSeconds = GetStatsExpireSeconds(timeKey);
  477. string setKey = $":{prefix}:{accountName}:{name}:{timeKey}";
  478. RedisHelper.SAdd(setKey, value);
  479. RedisHelper.Expire(setKey, expireSeconds);
  480. string valueKey = $":{prefix}:{accountName}:{value}:{timeKey}";
  481. RedisHelper.IncrBy(valueKey);
  482. RedisHelper.Expire(valueKey, expireSeconds);
  483. string namedValueKey = $":{prefix}:{accountName}:{name}:{value}:{timeKey}";
  484. RedisHelper.IncrBy(namedValueKey);
  485. RedisHelper.Expire(namedValueKey, expireSeconds);
  486. }
  487. private static async Task SaveStatsDimensionAsync(string prefix, string accountName, string name, string value, string timeKey)
  488. {
  489. if (string.IsNullOrEmpty(value)) return;
  490. int expireSeconds = GetStatsExpireSeconds(timeKey);
  491. string setKey = $":{prefix}:{accountName}:{name}:{timeKey}";
  492. await RedisKit.SAddAsync(setKey, value);
  493. await RedisKit.ExpireAsync(setKey, expireSeconds);
  494. string valueKey = $":{prefix}:{accountName}:{value}:{timeKey}";
  495. await RedisKit.IncrByAsync(valueKey);
  496. await RedisKit.ExpireAsync(valueKey, expireSeconds);
  497. string namedValueKey = $":{prefix}:{accountName}:{name}:{value}:{timeKey}";
  498. await RedisKit.IncrByAsync(namedValueKey);
  499. await RedisKit.ExpireAsync(namedValueKey, expireSeconds);
  500. }
  501. private static int GetStatsExpireSeconds(string timeKey)
  502. {
  503. return timeKey.Length switch
  504. {
  505. 10 => StatsHourlyExpireSeconds,
  506. 8 => StatsDailyExpireSeconds,
  507. 6 => StatsMonthlyExpireSeconds,
  508. _ => StatsDailyExpireSeconds,
  509. };
  510. }
  511. public static async Task<int> GetTotalAsync(string keyname, bool all_node = true)
  512. {
  513. try
  514. {
  515. var tasks = EndPointCore.List()
  516. .Where(node => node.is_public_api && !string.IsNullOrEmpty(node.redis_server))
  517. .Where(node => all_node || (CenterHub.IsCenter ? !node.is_coupon_api : node.is_coupon_api))
  518. .Select(async node =>
  519. {
  520. try
  521. {
  522. #if DEBUG
  523. switch (node.name)
  524. {
  525. case "bj":
  526. node.redis_server = "101.200.152.61:6379,password=pKBiS4ka2IpXayIdcx00,defaultDatabase=0,idleTimeout=20000,preheat=3,tryit=2,ssl=false,prefix=webhook";
  527. break;
  528. case "gz":
  529. node.redis_server = "8.138.110.158:6379,password=pKBiS4ka2IpXayIdcx00,defaultDatabase=0,idleTimeout=20000,preheat=3,tryit=2,ssl=false,prefix=webhook";
  530. break;
  531. case "coupon1":
  532. node.redis_server = "123.56.185.166:6379,password=pKBiS4ka2IpXayIdcx00,defaultDatabase=0,idleTimeout=20000,preheat=3,tryit=2,ssl=false,prefix=coupon";
  533. break;
  534. }
  535. #endif
  536. var redisServer = EndPointCore.GetRedisServer(node);
  537. if (string.IsNullOrEmpty(redisServer))
  538. return 0;
  539. await using var scope = await RedisClientFactory.CreateScopeAsync(redisServer);
  540. return await scope.Client.GetAsync<int>(keyname);
  541. }
  542. catch (Exception)
  543. {
  544. return 0;
  545. }
  546. });
  547. var results = await Task.WhenAll(tasks);
  548. return results.Sum();
  549. }
  550. catch (Exception)
  551. {
  552. return 0;
  553. }
  554. }
  555. public static async Task<string[]> GetTotalKeysAsync(string keyname, bool all_node = true)
  556. {
  557. var tasks = EndPointCore.List()
  558. .Where(node => node.is_public_api && !string.IsNullOrEmpty(node.redis_server))
  559. .Where(node => all_node || (CenterHub.IsCenter ? !node.is_coupon_api : node.is_coupon_api))
  560. .Select(async node =>
  561. {
  562. try
  563. {
  564. try
  565. {
  566. var redisServer = EndPointCore.GetRedisServer(node);
  567. if (string.IsNullOrEmpty(redisServer)) return [];
  568. using var scope = await RedisClientFactory.CreateScopeAsync(redisServer);
  569. return await scope.Client.SMembersAsync<string>(keyname);
  570. }
  571. catch (Exception ex) { }
  572. return [];
  573. }
  574. catch (Exception ex)
  575. {
  576. return [];
  577. }
  578. });
  579. var results = await Task.WhenAll(tasks);
  580. return results.SelectMany(x => x).Distinct().ToArray();
  581. }
  582. }
  583. }