tt-help-cli-ycl 1.4.23 → 1.4.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -27,21 +27,87 @@ const publicDir = join(__dirname, "public");
27
27
  // Stats 缓存:避免每次请求都扫描 100 万+ 条 jobs 数据
28
28
  let statsCache = null;
29
29
  let statsCacheTime = 0;
30
- const STATS_CACHE_TTL = 10000; // 10 秒缓存
30
+ const STATS_CACHE_TTL = 60000; // 60 秒缓存
31
+
32
+ // 慢计数缓存:每张大表独立缓存,错峰刷新,避免集中阻塞
33
+ const SLOW_CACHE_TTL = 600000; // 10 分钟
34
+ const slowCaches = {
35
+ rawJobs: { value: 0, time: 0, sql: "SELECT COUNT(*) as c FROM raw_jobs" },
36
+ commentTaskCount: {
37
+ value: 0,
38
+ time: 0,
39
+ sql: "SELECT COUNT(*) as c FROM videos WHERE user_update_count IS NULL OR user_update_count <= 0",
40
+ },
41
+ dbTotalUsers: { value: 0, time: 0, sql: "SELECT COUNT(*) as c FROM users" },
42
+ tagCount: { value: 0, time: 0, sql: "SELECT COUNT(*) as c FROM tags" },
43
+ };
44
+
45
+ function refreshOneSlowCount(st, key) {
46
+ const entry = slowCaches[key];
47
+ if (!entry) return;
48
+ try {
49
+ const result = st.rawQuery(entry.sql);
50
+ entry.value = result?.rows?.[0]?.c || 0;
51
+ entry.time = Date.now();
52
+ } catch (e) {
53
+ console.error(`[stats] 慢计数 ${key} 更新失败: ${e.message}`);
54
+ }
55
+ }
31
56
 
32
- function invalidateStatsCache() {
33
- statsCache = null;
34
- statsCacheTime = 0;
57
+ function getSlowCounts(st) {
58
+ const result = {};
59
+ for (const [key, entry] of Object.entries(slowCaches)) {
60
+ if (Date.now() - entry.time > SLOW_CACHE_TTL) {
61
+ refreshOneSlowCount(st, key);
62
+ }
63
+ result[key] = entry.value;
64
+ }
65
+ return result;
35
66
  }
36
67
 
68
+ let statsRefreshing = false; // 防止并发刷新
69
+
37
70
  function computeStatsIncremental(st) {
38
71
  const now = Date.now();
72
+ // 缓存有效 → 直接返回
39
73
  if (statsCache && now - statsCacheTime < STATS_CACHE_TTL) {
40
74
  return statsCache;
41
75
  }
42
- statsCache = st.getDashboardStats(DEFAULT_TARGET_LOCATIONS);
76
+ // 缓存过期但存在 → 先返回旧数据,后台异步刷新
77
+ if (statsCache && !statsRefreshing) {
78
+ statsRefreshing = true;
79
+ setImmediate(async () => {
80
+ try {
81
+ const slowCounts = getSlowCounts(st);
82
+ const stats = st.getDashboardStats(DEFAULT_TARGET_LOCATIONS, {
83
+ skipSlowCounts: true,
84
+ });
85
+ stats.rawJobs = slowCounts.rawJobs;
86
+ stats.commentTaskCount = slowCounts.commentTaskCount;
87
+ stats.dbTotalUsers = slowCounts.dbTotalUsers;
88
+ stats.tagCount = slowCounts.tagCount;
89
+ statsCache = stats;
90
+ statsCacheTime = Date.now();
91
+ } catch (e) {
92
+ console.error(`[stats] 后台刷新失败: ${e.message}`);
93
+ } finally {
94
+ statsRefreshing = false;
95
+ }
96
+ });
97
+ return statsCache;
98
+ }
99
+ // 首次加载 → 同步计算
100
+ const slowCounts = getSlowCounts(st);
101
+ const stats = st.getDashboardStats(DEFAULT_TARGET_LOCATIONS, {
102
+ skipSlowCounts: true,
103
+ });
104
+ stats.rawJobs = slowCounts.rawJobs;
105
+ stats.commentTaskCount = slowCounts.commentTaskCount;
106
+ stats.dbTotalUsers = slowCounts.dbTotalUsers;
107
+ stats.tagCount = slowCounts.tagCount;
108
+ statsCache = stats;
43
109
  statsCacheTime = now;
44
- return statsCache;
110
+ return stats;
45
111
  }
46
112
 
47
113
  function readBody(req) {
@@ -329,8 +395,24 @@ export function startWatchServer(
329
395
  }
330
396
 
331
397
  const server = http.createServer(async (req, res) => {
398
+ const startTime = Date.now();
332
399
  const { path: routePath, params } = parseQuery(req.url);
333
400
 
401
+ // 请求耗时日志:仅慢请求打印(>1s)
402
+ res.on("finish", () => {
403
+ const elapsed = Date.now() - startTime;
404
+ if (elapsed <= 1000) return;
405
+ const method = req.method;
406
+ const shortPath =
407
+ routePath.length > 80
408
+ ? routePath.substring(0, 80) + "..."
409
+ : routePath;
410
+ const suffix = elapsed > 5000 ? "[SLOW]" : "[WARN]";
411
+ console.error(
412
+ `${method} ${shortPath} → ${res.statusCode} (${elapsed}ms)${suffix}`,
413
+ );
414
+ });
415
+
334
416
  // 客户端追踪:从 header 提取 clientId 和元数据,根据路由推断类型
335
417
  const clientId = req.headers["x-client-id"] || "";
336
418
  if (clientId) {
@@ -500,7 +582,7 @@ export function startWatchServer(
500
582
  ) {
501
583
  try {
502
584
  const result = store.moveNoCountryJobsToBase();
503
- const ts = new Date().toISOString().slice(11, 19);
585
+ const ts = new Date().toLocaleTimeString("zh-CN", { hour12: false });
504
586
  if (result.ok) {
505
587
  console.error(
506
588
  `[JOB ${ts}] MOVE-NO-COUNTRY-TO-BASE: moved=${result.moved}, total=${result.total}`,
@@ -524,7 +606,7 @@ export function startWatchServer(
524
606
  ) {
525
607
  try {
526
608
  const result = store.moveSellerJobsToBase();
527
- const ts = new Date().toISOString().slice(11, 19);
609
+ const ts = new Date().toLocaleTimeString("zh-CN", { hour12: false });
528
610
  if (result.ok) {
529
611
  console.error(
530
612
  `[JOB ${ts}] MOVE-SELLER-JOBS: jobs=${result.fromJobs}, raw_jobs=${result.fromRawJobs}, reset=${result.resetCount}, available=${result.availableInBase}`,
@@ -734,7 +816,7 @@ export function startWatchServer(
734
816
  .filter(Boolean)
735
817
  : [];
736
818
  const tasks = store.getPendingUserUpdateTasks(limit, countries);
737
- const ts = new Date().toISOString().slice(11, 19);
819
+ const ts = new Date().toLocaleTimeString("zh-CN", { hour12: false });
738
820
  console.error(
739
821
  `[JOB ${ts}] USER-UPDATE-TASKS: ${tasks.length} tasks${countries.length ? ` (countries: ${countries.join(",")})` : ""}`,
740
822
  );
@@ -749,7 +831,7 @@ export function startWatchServer(
749
831
  const results = store.batchUpdateUserInfo(updates);
750
832
  const okCount = results.filter((r) => r.ok).length;
751
833
  const errCount = results.filter((r) => r.error).length;
752
- const ts = new Date().toISOString().slice(11, 19);
834
+ const ts = new Date().toLocaleTimeString("zh-CN", { hour12: false });
753
835
  console.error(
754
836
  `[JOB ${ts}] USER-INFO-BATCH: ${okCount} ok, ${errCount} error (total=${updates.length})`,
755
837
  );
@@ -775,7 +857,7 @@ export function startWatchServer(
775
857
  .filter(Boolean)
776
858
  : [];
777
859
  const tasks = store.getRecheckJobs(limit, countries);
778
- const ts = new Date().toISOString().slice(11, 19);
860
+ const ts = new Date().toLocaleTimeString("zh-CN", { hour12: false });
779
861
  console.error(
780
862
  `[JOB ${ts}] RECHECK-JOBS: ${tasks.length} tasks${countries.length ? ` (countries: ${countries.join(",")})` : ""}`,
781
863
  );
@@ -790,7 +872,7 @@ export function startWatchServer(
790
872
  const { results } = store.batchUpdateRecheckJobs(updates);
791
873
  const okCount = results.filter((r) => r.ok).length;
792
874
  const errCount = results.filter((r) => r.error).length;
793
- const ts = new Date().toISOString().slice(11, 19);
875
+ const ts = new Date().toLocaleTimeString("zh-CN", { hour12: false });
794
876
  console.error(
795
877
  `[JOB ${ts}] RECHECK-JOBS-BATCH: ${okCount} ok, ${errCount} error (total=${updates.length})`,
796
878
  );
@@ -818,7 +900,7 @@ export function startWatchServer(
818
900
  sendJSON(res, 404, { error: ret.error });
819
901
  return;
820
902
  }
821
- const ts = new Date().toISOString().slice(11, 19);
903
+ const ts = new Date().toLocaleTimeString("zh-CN", { hour12: false });
822
904
  console.error(
823
905
  `[JOB ${ts}] USER-INFO-UPDATE: ${uniqueId} (userUpdateCount=${ret.userUpdateCount})`,
824
906
  );
@@ -849,7 +931,7 @@ export function startWatchServer(
849
931
  sendJSON(res, 404, { error: ret.error });
850
932
  return;
851
933
  }
852
- const ts = new Date().toISOString().slice(11, 19);
934
+ const ts = new Date().toLocaleTimeString("zh-CN", { hour12: false });
853
935
  console.error(
854
936
  `[JOB ${ts}] USER-LOCATION: ${uniqueId} → ${body.location} (modifiedAt=${ret.modifiedAt})`,
855
937
  );
@@ -871,7 +953,7 @@ export function startWatchServer(
871
953
  sendJSON(res, 404, { error: ret.error });
872
954
  return;
873
955
  }
874
- const ts = new Date().toISOString().slice(11, 19);
956
+ const ts = new Date().toLocaleTimeString("zh-CN", { hour12: false });
875
957
  console.error(`[JOB ${ts}] NON-SELLER: ${uniqueId} → ttSeller=false`);
876
958
  sendJSON(res, 200, ret);
877
959
  } catch (e) {
@@ -892,7 +974,7 @@ export function startWatchServer(
892
974
  sendJSON(res, 404, { error: ret.error });
893
975
  return;
894
976
  }
895
- const ts = new Date().toISOString().slice(11, 19);
977
+ const ts = new Date().toLocaleTimeString("zh-CN", { hour12: false });
896
978
  console.error(`[JOB ${ts}] MOVE-TO-BASE: ${uniqueId} → jobs_base`);
897
979
  sendJSON(res, 200, ret);
898
980
  } catch (e) {
@@ -911,7 +993,7 @@ export function startWatchServer(
911
993
  .filter(Boolean)
912
994
  : null;
913
995
  const tasks = store.getPendingCommentTasks(limit, locations);
914
- const ts = new Date().toISOString().slice(11, 19);
996
+ const ts = new Date().toLocaleTimeString("zh-CN", { hour12: false });
915
997
  console.error(
916
998
  `[JOB ${ts}] COMMENT-TASKS: ${tasks.length} tasks${locations ? " (locations: " + locations.join(",") + ")" : ""}`,
917
999
  );
@@ -930,7 +1012,7 @@ export function startWatchServer(
930
1012
  sendJSON(res, 404, { error: ret.error });
931
1013
  return;
932
1014
  }
933
- const ts = new Date().toISOString().slice(11, 19);
1015
+ const ts = new Date().toLocaleTimeString("zh-CN", { hour12: false });
934
1016
  console.error(
935
1017
  `[JOB ${ts}] COMMENT-TASK-COMMIT: ${videoId} (userUpdateCount=${ret.userUpdateCount})`,
936
1018
  );
@@ -2365,6 +2447,22 @@ export function startWatchServer(
2365
2447
  console.error(` 本地访问: http://127.0.0.1:${port}`);
2366
2448
  console.error(` 局域网访问: http://${localIP}:${port}`);
2367
2449
 
2450
+ // 预热 stats 缓存 + 错峰后台更新慢计数(每张表独立,间隔 2.5 分钟)
2451
+ setTimeout(() => {
2452
+ for (const key of Object.keys(slowCaches))
2453
+ refreshOneSlowCount(store, key);
2454
+ computeStatsIncremental(store);
2455
+ }, 3000);
2456
+ const slowKeys = Object.keys(slowCaches);
2457
+ slowKeys.forEach((key, i) => {
2458
+ setInterval(() => refreshOneSlowCount(store, key), SLOW_CACHE_TTL);
2459
+ // 首次错峰:各表间隔 2.5 分钟启动
2460
+ setTimeout(
2461
+ () => refreshOneSlowCount(store, key),
2462
+ 3000 + (i + 1) * 150000,
2463
+ );
2464
+ });
2465
+
2368
2466
  // 启动时清理超时的 scoring 标签
2369
2467
  try {
2370
2468
  const { resetStaleScoringTags } = store;
@@ -2401,7 +2499,7 @@ export function startWatchServer(
2401
2499
  setInterval(() => {
2402
2500
  try {
2403
2501
  const result = store.moveNoCountryJobsToBase();
2404
- const ts = new Date().toISOString().slice(11, 19);
2502
+ const ts = new Date().toLocaleTimeString("zh-CN", { hour12: false });
2405
2503
  if (result.moved > 0) {
2406
2504
  console.error(
2407
2505
  `[JOB ${ts}] AUTO-MOVE-NO-COUNTRY: moved=${result.moved}, total=${result.total}`,