base.cs 29 KB

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