tt-help-cli-ycl 1.4.23 → 1.4.24
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.
- package/package.json +1 -1
- package/scripts/migrate-raw-jobs.mjs +243 -0
- package/scripts/sync-raw-jobs.mjs +281 -0
- package/src/cli/raw-sync.js +41 -0
- package/src/lib/args.js +50 -0
- package/src/lib/constants.js +14 -0
- package/src/main.js +3 -0
- package/src/watch/data-store.js +178 -258
- package/src/watch/db-schema.js +1 -0
- package/src/watch/db-stats.js +18 -10
- package/src/watch/public/app.js +1 -1
- package/src/watch/server.js +117 -19
package/src/watch/server.js
CHANGED
|
@@ -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 =
|
|
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
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
|
|
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
|
|
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().
|
|
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().
|
|
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().
|
|
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().
|
|
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().
|
|
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().
|
|
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().
|
|
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().
|
|
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().
|
|
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().
|
|
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().
|
|
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().
|
|
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().
|
|
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}`,
|