tt-help-cli-ycl 1.4.26 → 1.4.27
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/sync-raw-jobs.mjs +45 -72
- package/src/cli/raw-sync.js +2 -2
- package/src/lib/args.js +4 -4
- package/src/lib/constants.js +4 -4
package/package.json
CHANGED
|
@@ -13,22 +13,6 @@
|
|
|
13
13
|
|
|
14
14
|
import Database from "better-sqlite3";
|
|
15
15
|
|
|
16
|
-
// ===== 目标国家 =====
|
|
17
|
-
const TARGET_LOCATIONS = [
|
|
18
|
-
"CZ",
|
|
19
|
-
"GR",
|
|
20
|
-
"HU",
|
|
21
|
-
"PT",
|
|
22
|
-
"ES",
|
|
23
|
-
"PL",
|
|
24
|
-
"NL",
|
|
25
|
-
"BE",
|
|
26
|
-
"DE",
|
|
27
|
-
"FR",
|
|
28
|
-
"IT",
|
|
29
|
-
"IE",
|
|
30
|
-
"AT",
|
|
31
|
-
];
|
|
32
16
|
|
|
33
17
|
// ===== 命令行参数 =====
|
|
34
18
|
function parseArgs() {
|
|
@@ -37,7 +21,7 @@ function parseArgs() {
|
|
|
37
21
|
db: null,
|
|
38
22
|
archive: null,
|
|
39
23
|
interval: 30,
|
|
40
|
-
|
|
24
|
+
max: 10000,
|
|
41
25
|
batch: 500,
|
|
42
26
|
once: false,
|
|
43
27
|
};
|
|
@@ -52,8 +36,8 @@ function parseArgs() {
|
|
|
52
36
|
case "--interval":
|
|
53
37
|
opts.interval = parseInt(args[++i]) || 30;
|
|
54
38
|
break;
|
|
55
|
-
case "--
|
|
56
|
-
opts.
|
|
39
|
+
case "--max":
|
|
40
|
+
opts.max = parseInt(args[++i]) || 10000;
|
|
57
41
|
break;
|
|
58
42
|
case "--batch":
|
|
59
43
|
opts.batch = parseInt(args[++i]) || 500;
|
|
@@ -77,24 +61,25 @@ if (!opts.db || !opts.archive) {
|
|
|
77
61
|
console.error(" --db <path> 主库路径(必填)");
|
|
78
62
|
console.error(" --archive <path> 归档库路径(必填)");
|
|
79
63
|
console.error(" --interval <秒> 检测间隔,默认 30");
|
|
80
|
-
console.error(" --
|
|
64
|
+
console.error(" --max <条> 主库 raw_jobs 保有量上限,默认 10000");
|
|
81
65
|
console.error(" --batch <条> 每次搬移上限,默认 500");
|
|
82
66
|
console.error(" --once 只执行一次后退出");
|
|
83
67
|
process.exit(1);
|
|
84
68
|
}
|
|
85
69
|
|
|
86
70
|
// ===== 过滤与排序 =====
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
"
|
|
90
|
-
"
|
|
71
|
+
// 注意: 索引依赖的 WHERE 用原始列名(不用 COALESCE),与 idx_raw_archive_sync 匹配
|
|
72
|
+
const FILTER_INDEXED = [
|
|
73
|
+
"video_count > 6",
|
|
74
|
+
"follower_count > 0",
|
|
75
|
+
"following_count > 0",
|
|
91
76
|
];
|
|
92
77
|
|
|
93
78
|
const ORDER_CLAUSE = `
|
|
94
79
|
ORDER BY
|
|
95
80
|
CASE WHEN sources LIKE '%tag%' THEN 0 ELSE 1 END,
|
|
96
81
|
CASE WHEN sources LIKE '%comment%' THEN 0 ELSE 1 END,
|
|
97
|
-
|
|
82
|
+
video_count DESC,
|
|
98
83
|
created_at DESC
|
|
99
84
|
`;
|
|
100
85
|
|
|
@@ -136,23 +121,17 @@ const RAW_JOB_COLUMNS = [
|
|
|
136
121
|
"user_create_time",
|
|
137
122
|
];
|
|
138
123
|
|
|
139
|
-
// =====
|
|
140
|
-
function moveBatch(mainDb, archiveDb,
|
|
141
|
-
const whereSql =
|
|
142
|
-
...FILTER_CONDITIONS,
|
|
143
|
-
`(UPPER(COALESCE(guessed_location, '')) = ? OR COALESCE(guessed_location, '') = '')`,
|
|
144
|
-
].join(" AND ");
|
|
145
|
-
|
|
124
|
+
// ===== 核心:从归档库搬移一批到主库(全局排序,不区分国家)=====
|
|
125
|
+
function moveBatch(mainDb, archiveDb, limit) {
|
|
126
|
+
const whereSql = FILTER_INDEXED.join(" AND ");
|
|
146
127
|
const cols = RAW_JOB_COLUMNS.join(", ");
|
|
147
128
|
|
|
148
|
-
// 1. INSERT OR IGNORE 到主库
|
|
149
129
|
const insertSql = `
|
|
150
130
|
INSERT OR IGNORE INTO raw_jobs (${cols})
|
|
151
131
|
SELECT ${cols}
|
|
152
132
|
FROM (SELECT * FROM raw_jobs WHERE ${whereSql} ${ORDER_CLAUSE} LIMIT ?)
|
|
153
133
|
`;
|
|
154
134
|
|
|
155
|
-
// 2. DELETE 归档库中已搬走的数据(匹配同一批次的 unique_id)
|
|
156
135
|
const deleteSql = `
|
|
157
136
|
DELETE FROM raw_jobs
|
|
158
137
|
WHERE unique_id IN (
|
|
@@ -162,10 +141,8 @@ function moveBatch(mainDb, archiveDb, country, limit) {
|
|
|
162
141
|
`;
|
|
163
142
|
|
|
164
143
|
const moved = archiveDb.transaction(() => {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
mainDb.prepare(insertSql).run(country, limit);
|
|
168
|
-
const del = archiveDb.prepare(deleteSql).run(country, limit);
|
|
144
|
+
mainDb.prepare(insertSql).run(limit);
|
|
145
|
+
const del = archiveDb.prepare(deleteSql).run(limit);
|
|
169
146
|
return del.changes;
|
|
170
147
|
})();
|
|
171
148
|
|
|
@@ -173,52 +150,35 @@ function moveBatch(mainDb, archiveDb, country, limit) {
|
|
|
173
150
|
}
|
|
174
151
|
|
|
175
152
|
// ===== 单次同步 =====
|
|
176
|
-
function syncOnce(mainDb, archiveDb) {
|
|
153
|
+
function syncOnce(mainDb, archiveDb, maxTotal, batch) {
|
|
177
154
|
const start = Date.now();
|
|
178
|
-
const results = {};
|
|
179
|
-
|
|
180
|
-
for (const country of TARGET_LOCATIONS) {
|
|
181
|
-
// 检查主库中该国存量
|
|
182
|
-
const countRow = mainDb
|
|
183
|
-
.prepare(
|
|
184
|
-
`SELECT COUNT(*) as c FROM raw_jobs
|
|
185
|
-
WHERE UPPER(COALESCE(guessed_location, '')) = ?`,
|
|
186
|
-
)
|
|
187
|
-
.get(country);
|
|
188
|
-
|
|
189
|
-
const current = countRow?.c || 0;
|
|
190
|
-
|
|
191
|
-
if (current >= opts.threshold) {
|
|
192
|
-
continue;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
const need = opts.threshold - current;
|
|
196
|
-
const limit = Math.min(need, opts.batch);
|
|
197
155
|
|
|
198
|
-
|
|
156
|
+
// 主库 raw_jobs 总数够了就不搬
|
|
157
|
+
const currentTotal =
|
|
158
|
+
mainDb.prepare("SELECT COUNT(*) as c FROM raw_jobs").get()?.c || 0;
|
|
199
159
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
console.error(
|
|
203
|
-
`[sync] ${country}: ${current} → ${current + moved} (搬入 ${moved} 条)`,
|
|
204
|
-
);
|
|
160
|
+
if (currentTotal >= maxTotal) {
|
|
161
|
+
return { moved: 0, total: currentTotal };
|
|
205
162
|
}
|
|
206
163
|
|
|
207
|
-
const
|
|
164
|
+
const need = maxTotal - currentTotal;
|
|
165
|
+
const limit = Math.min(need, batch);
|
|
166
|
+
|
|
167
|
+
const moved = moveBatch(mainDb, archiveDb, limit);
|
|
208
168
|
const elapsed = ((Date.now() - start) / 1000).toFixed(1);
|
|
209
169
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
170
|
+
console.error(
|
|
171
|
+
`[sync] ${currentTotal} → ${currentTotal + moved} (搬入 ${moved} 条),耗时 ${elapsed}s`,
|
|
172
|
+
);
|
|
213
173
|
|
|
214
|
-
return
|
|
174
|
+
return { moved, total: currentTotal + moved };
|
|
215
175
|
}
|
|
216
176
|
|
|
217
177
|
// ===== 主流程 =====
|
|
218
178
|
console.error("[sync] raw_jobs 同步程序启动");
|
|
219
179
|
console.error(`[sync] 主库: ${opts.db}`);
|
|
220
180
|
console.error(`[sync] 归档库: ${opts.archive}`);
|
|
221
|
-
console.error(`[sync]
|
|
181
|
+
console.error(`[sync] 主库上限: ${opts.max} 条,每次搬 ${opts.batch} 条`);
|
|
222
182
|
console.error(`[sync] 检测间隔: ${opts.once ? "仅一次" : opts.interval + "s"}`);
|
|
223
183
|
console.error("");
|
|
224
184
|
|
|
@@ -255,10 +215,23 @@ const archiveTotal =
|
|
|
255
215
|
console.error(
|
|
256
216
|
`[sync] 归档库 raw_jobs 总量: ${archiveTotal.toLocaleString()} 条`,
|
|
257
217
|
);
|
|
218
|
+
|
|
219
|
+
// 确保归档库有加速索引(首次运行自动创建)
|
|
220
|
+
const startIdx = Date.now();
|
|
221
|
+
archiveDb.exec(`
|
|
222
|
+
CREATE INDEX IF NOT EXISTS idx_raw_archive_sync
|
|
223
|
+
ON raw_jobs(video_count DESC, created_at DESC)
|
|
224
|
+
WHERE video_count > 6 AND follower_count > 0 AND following_count > 0;
|
|
225
|
+
`);
|
|
226
|
+
const idxElapsed = ((Date.now() - startIdx) / 1000).toFixed(1);
|
|
227
|
+
if (idxElapsed > 0.1) {
|
|
228
|
+
console.error(`[sync] 归档库索引就绪,耗时 ${idxElapsed}s`);
|
|
229
|
+
}
|
|
258
230
|
console.error("");
|
|
259
231
|
|
|
260
232
|
// 执行同步
|
|
261
|
-
|
|
233
|
+
const MAX_TOTAL = opts.max ?? 10000;
|
|
234
|
+
syncOnce(mainDb, archiveDb, MAX_TOTAL, opts.batch);
|
|
262
235
|
|
|
263
236
|
if (opts.once) {
|
|
264
237
|
mainDb.close();
|
|
@@ -269,7 +242,7 @@ if (opts.once) {
|
|
|
269
242
|
|
|
270
243
|
// 定时循环
|
|
271
244
|
setInterval(() => {
|
|
272
|
-
syncOnce(mainDb, archiveDb);
|
|
245
|
+
syncOnce(mainDb, archiveDb, MAX_TOTAL, opts.batch);
|
|
273
246
|
}, opts.interval * 1000);
|
|
274
247
|
|
|
275
248
|
// 优雅退出
|
package/src/cli/raw-sync.js
CHANGED
|
@@ -16,8 +16,8 @@ export function handleRawSync(parsed) {
|
|
|
16
16
|
|
|
17
17
|
if (parsed.rawSyncInterval != null)
|
|
18
18
|
args.push("--interval", String(parsed.rawSyncInterval));
|
|
19
|
-
if (parsed.
|
|
20
|
-
args.push("--
|
|
19
|
+
if (parsed.rawSyncMax != null)
|
|
20
|
+
args.push("--max", String(parsed.rawSyncMax));
|
|
21
21
|
if (parsed.rawSyncBatch != null)
|
|
22
22
|
args.push("--batch", String(parsed.rawSyncBatch));
|
|
23
23
|
if (parsed.rawSyncOnce) args.push("--once");
|
package/src/lib/args.js
CHANGED
|
@@ -690,7 +690,7 @@ function parseRawSyncArgs(args) {
|
|
|
690
690
|
let rawSyncDb = null;
|
|
691
691
|
let rawSyncArchive = null;
|
|
692
692
|
let rawSyncInterval = null;
|
|
693
|
-
let
|
|
693
|
+
let rawSyncMax = null;
|
|
694
694
|
let rawSyncBatch = null;
|
|
695
695
|
let rawSyncOnce = false;
|
|
696
696
|
|
|
@@ -702,8 +702,8 @@ function parseRawSyncArgs(args) {
|
|
|
702
702
|
rawSyncArchive = args[++i];
|
|
703
703
|
} else if (arg === "--interval" && i + 1 < args.length) {
|
|
704
704
|
rawSyncInterval = parseInt(args[++i]) || null;
|
|
705
|
-
} else if (arg === "--
|
|
706
|
-
|
|
705
|
+
} else if (arg === "--max" && i + 1 < args.length) {
|
|
706
|
+
rawSyncMax = parseInt(args[++i]) || null;
|
|
707
707
|
} else if (arg === "--batch" && i + 1 < args.length) {
|
|
708
708
|
rawSyncBatch = parseInt(args[++i]) || null;
|
|
709
709
|
} else if (arg === "--once") {
|
|
@@ -716,7 +716,7 @@ function parseRawSyncArgs(args) {
|
|
|
716
716
|
rawSyncDb,
|
|
717
717
|
rawSyncArchive,
|
|
718
718
|
rawSyncInterval,
|
|
719
|
-
|
|
719
|
+
rawSyncMax,
|
|
720
720
|
rawSyncBatch,
|
|
721
721
|
rawSyncOnce,
|
|
722
722
|
urls: [],
|
package/src/lib/constants.js
CHANGED
|
@@ -315,17 +315,17 @@ const HELP_TEXT = [
|
|
|
315
315
|
" tt-help tag refresh --port 8222 -s http://127.0.0.1:3001",
|
|
316
316
|
"",
|
|
317
317
|
" raw-sync --db <主库路径> --archive <归档库路径> [选项]",
|
|
318
|
-
" 从归档库向主库 raw_jobs
|
|
318
|
+
" 从归档库向主库 raw_jobs 同步数据,保持主库总量不超上限",
|
|
319
319
|
" 选项:",
|
|
320
320
|
" --db <路径> 主库路径(必填)",
|
|
321
321
|
" --archive <路径> 归档库路径(必填)",
|
|
322
322
|
" --interval <秒> 检测间隔,默认 30",
|
|
323
|
-
" --
|
|
324
|
-
" --batch <条>
|
|
323
|
+
" --max <条> 主库 raw_jobs 保有量上限,默认 10000",
|
|
324
|
+
" --batch <条> 每次搬移条数,默认 500",
|
|
325
325
|
" --once 只执行一次后退出",
|
|
326
326
|
" 示例: tt-help raw-sync --db data/result.db --archive data/result-archive.db",
|
|
327
327
|
" tt-help raw-sync --db data/result.db --archive data/result-archive.db --once",
|
|
328
|
-
" tt-help raw-sync --db data/result.db --archive data/result-archive.db --interval 60 --
|
|
328
|
+
" tt-help raw-sync --db data/result.db --archive data/result-archive.db --interval 60 --max 20000",
|
|
329
329
|
"",
|
|
330
330
|
" config [show|set|unset|reset]",
|
|
331
331
|
" config 查看当前配置",
|