koishi-plugin-aka-ai-generator 0.7.5 → 0.7.7
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/lib/index.js +194 -36
- package/lib/providers/yunwu-video.d.ts +4 -0
- package/lib/services/UserManager.d.ts +31 -0
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -914,7 +914,7 @@ var YunwuVideoProvider = class {
|
|
|
914
914
|
} else {
|
|
915
915
|
duration = 25;
|
|
916
916
|
}
|
|
917
|
-
const
|
|
917
|
+
const buildRequestBody = /* @__PURE__ */ __name((watermark) => ({
|
|
918
918
|
images: [`data:${mimeType};base64,${imageBase64}`],
|
|
919
919
|
model: this.config.modelId,
|
|
920
920
|
orientation,
|
|
@@ -922,29 +922,42 @@ var YunwuVideoProvider = class {
|
|
|
922
922
|
size: "large",
|
|
923
923
|
// 高清1080p
|
|
924
924
|
duration,
|
|
925
|
-
watermark
|
|
926
|
-
//
|
|
925
|
+
watermark,
|
|
926
|
+
// 优先无水印;若接口不允许则降级有水印
|
|
927
927
|
private: false
|
|
928
928
|
// 默认视频会发布
|
|
929
|
-
};
|
|
929
|
+
}), "buildRequestBody");
|
|
930
930
|
logger?.info("提交视频生成任务", {
|
|
931
931
|
model: this.config.modelId,
|
|
932
932
|
promptLength: prompt.length,
|
|
933
|
-
duration
|
|
934
|
-
orientation
|
|
933
|
+
duration,
|
|
934
|
+
orientation
|
|
935
935
|
});
|
|
936
|
-
const
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
936
|
+
const doCreate = /* @__PURE__ */ __name(async (watermark) => {
|
|
937
|
+
return await ctx.http.post(
|
|
938
|
+
`${this.config.apiBase}/v1/video/create`,
|
|
939
|
+
buildRequestBody(watermark),
|
|
940
|
+
{
|
|
941
|
+
headers: {
|
|
942
|
+
"Authorization": `Bearer ${this.config.apiKey}`,
|
|
943
|
+
"Content-Type": "application/json",
|
|
944
|
+
"Accept": "application/json"
|
|
945
|
+
},
|
|
946
|
+
timeout: this.config.apiTimeout * 1e3
|
|
947
|
+
}
|
|
948
|
+
);
|
|
949
|
+
}, "doCreate");
|
|
950
|
+
let response;
|
|
951
|
+
try {
|
|
952
|
+
response = await doCreate(false);
|
|
953
|
+
} catch (e) {
|
|
954
|
+
logger?.warn("无水印创建任务失败,尝试有水印", { error: sanitizeError(e) });
|
|
955
|
+
response = await doCreate(true);
|
|
956
|
+
}
|
|
957
|
+
if (response?.error || response?.status && response.status >= 400) {
|
|
958
|
+
logger?.warn("无水印创建任务返回错误,尝试有水印", { status: response?.status, error: response?.error, response: response?.data });
|
|
959
|
+
response = await doCreate(true);
|
|
960
|
+
}
|
|
948
961
|
if (response.error) {
|
|
949
962
|
const errorMsg = response.error.message || response.error.type || "创建任务失败";
|
|
950
963
|
throw new Error(sanitizeString(errorMsg));
|
|
@@ -1067,6 +1080,12 @@ var YunwuVideoProvider = class {
|
|
|
1067
1080
|
await new Promise((resolve) => setTimeout(resolve, pollInterval * 1e3));
|
|
1068
1081
|
}
|
|
1069
1082
|
}
|
|
1083
|
+
/**
|
|
1084
|
+
* 等待指定任务完成并返回视频 URL(供外部在拿到 taskId 后自行控制扣费/提示)
|
|
1085
|
+
*/
|
|
1086
|
+
async waitForVideo(taskId, maxWaitTime = 300) {
|
|
1087
|
+
return await this.pollTaskCompletion(taskId, maxWaitTime);
|
|
1088
|
+
}
|
|
1070
1089
|
/**
|
|
1071
1090
|
* 生成视频(主入口)
|
|
1072
1091
|
*/
|
|
@@ -1077,7 +1096,7 @@ var YunwuVideoProvider = class {
|
|
|
1077
1096
|
const taskId = await this.createVideoTask(prompt, imageUrl, options);
|
|
1078
1097
|
logger?.debug("任务已创建,等待 3 秒后开始查询", { taskId });
|
|
1079
1098
|
await new Promise((resolve) => setTimeout(resolve, 3e3));
|
|
1080
|
-
const videoUrl = await this.
|
|
1099
|
+
const videoUrl = await this.waitForVideo(taskId, maxWaitTime);
|
|
1081
1100
|
logger?.info("视频生成完成", { taskId, videoUrl });
|
|
1082
1101
|
return videoUrl;
|
|
1083
1102
|
} catch (error) {
|
|
@@ -1157,11 +1176,14 @@ var UserManager = class {
|
|
|
1157
1176
|
dataFile;
|
|
1158
1177
|
backupFile;
|
|
1159
1178
|
rechargeHistoryFile;
|
|
1179
|
+
pendingVideoTasksFile;
|
|
1160
1180
|
logger;
|
|
1161
1181
|
dataLock = new AsyncLock();
|
|
1162
1182
|
historyLock = new AsyncLock();
|
|
1183
|
+
pendingLock = new AsyncLock();
|
|
1163
1184
|
// 内存缓存
|
|
1164
1185
|
usersCache = null;
|
|
1186
|
+
pendingVideoCache = null;
|
|
1165
1187
|
activeTasks = /* @__PURE__ */ new Map();
|
|
1166
1188
|
// userId -> requestId
|
|
1167
1189
|
rateLimitMap = /* @__PURE__ */ new Map();
|
|
@@ -1176,6 +1198,7 @@ var UserManager = class {
|
|
|
1176
1198
|
this.dataFile = (0, import_path.join)(this.dataDir, "users_data.json");
|
|
1177
1199
|
this.backupFile = (0, import_path.join)(this.dataDir, "users_data.json.backup");
|
|
1178
1200
|
this.rechargeHistoryFile = (0, import_path.join)(this.dataDir, "recharge_history.json");
|
|
1201
|
+
this.pendingVideoTasksFile = (0, import_path.join)(this.dataDir, "pending_video_tasks.json");
|
|
1179
1202
|
if (!(0, import_fs.existsSync)(this.dataDir)) {
|
|
1180
1203
|
(0, import_fs.mkdirSync)(this.dataDir, { recursive: true });
|
|
1181
1204
|
}
|
|
@@ -1237,6 +1260,95 @@ var UserManager = class {
|
|
|
1237
1260
|
throw error;
|
|
1238
1261
|
}
|
|
1239
1262
|
}
|
|
1263
|
+
// --- 待结算视频任务(防止超时套利) ---
|
|
1264
|
+
async loadPendingVideoTasks() {
|
|
1265
|
+
if (this.pendingVideoCache) return this.pendingVideoCache;
|
|
1266
|
+
return await this.pendingLock.acquire(async () => {
|
|
1267
|
+
if (this.pendingVideoCache) return this.pendingVideoCache;
|
|
1268
|
+
try {
|
|
1269
|
+
if ((0, import_fs.existsSync)(this.pendingVideoTasksFile)) {
|
|
1270
|
+
const data = await import_fs.promises.readFile(this.pendingVideoTasksFile, "utf-8");
|
|
1271
|
+
const parsed = JSON.parse(data);
|
|
1272
|
+
this.pendingVideoCache = {
|
|
1273
|
+
version: parsed?.version || "1.0.0",
|
|
1274
|
+
lastUpdate: parsed?.lastUpdate || (/* @__PURE__ */ new Date()).toISOString(),
|
|
1275
|
+
tasks: parsed?.tasks || {}
|
|
1276
|
+
};
|
|
1277
|
+
return this.pendingVideoCache;
|
|
1278
|
+
}
|
|
1279
|
+
} catch (error) {
|
|
1280
|
+
this.logger.error("读取待结算视频任务失败", error);
|
|
1281
|
+
}
|
|
1282
|
+
this.pendingVideoCache = {
|
|
1283
|
+
version: "1.0.0",
|
|
1284
|
+
lastUpdate: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1285
|
+
tasks: {}
|
|
1286
|
+
};
|
|
1287
|
+
return this.pendingVideoCache;
|
|
1288
|
+
});
|
|
1289
|
+
}
|
|
1290
|
+
async savePendingVideoTasksInternal() {
|
|
1291
|
+
if (!this.pendingVideoCache) return;
|
|
1292
|
+
try {
|
|
1293
|
+
this.pendingVideoCache.lastUpdate = (/* @__PURE__ */ new Date()).toISOString();
|
|
1294
|
+
await import_fs.promises.writeFile(this.pendingVideoTasksFile, JSON.stringify(this.pendingVideoCache, null, 2), "utf-8");
|
|
1295
|
+
} catch (error) {
|
|
1296
|
+
this.logger.error("保存待结算视频任务失败", error);
|
|
1297
|
+
throw error;
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
/**
|
|
1301
|
+
* 记录一个待结算的视频任务(任务创建成功后立即写入,避免“超时但最终成功”不扣费)
|
|
1302
|
+
*/
|
|
1303
|
+
async addPendingVideoTask(task) {
|
|
1304
|
+
await this.loadPendingVideoTasks();
|
|
1305
|
+
await this.pendingLock.acquire(async () => {
|
|
1306
|
+
if (!this.pendingVideoCache) {
|
|
1307
|
+
this.pendingVideoCache = { version: "1.0.0", lastUpdate: (/* @__PURE__ */ new Date()).toISOString(), tasks: {} };
|
|
1308
|
+
}
|
|
1309
|
+
if (this.pendingVideoCache.tasks[task.taskId]) return;
|
|
1310
|
+
this.pendingVideoCache.tasks[task.taskId] = task;
|
|
1311
|
+
await this.savePendingVideoTasksInternal();
|
|
1312
|
+
});
|
|
1313
|
+
}
|
|
1314
|
+
async getPendingVideoTask(taskId) {
|
|
1315
|
+
const data = await this.loadPendingVideoTasks();
|
|
1316
|
+
return data.tasks[taskId] || null;
|
|
1317
|
+
}
|
|
1318
|
+
async markPendingVideoTaskCharged(taskId) {
|
|
1319
|
+
await this.loadPendingVideoTasks();
|
|
1320
|
+
return await this.pendingLock.acquire(async () => {
|
|
1321
|
+
if (!this.pendingVideoCache) return null;
|
|
1322
|
+
const task = this.pendingVideoCache.tasks[taskId];
|
|
1323
|
+
if (!task) return null;
|
|
1324
|
+
if (task.charged) return task;
|
|
1325
|
+
task.charged = true;
|
|
1326
|
+
task.chargedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1327
|
+
await this.savePendingVideoTasksInternal();
|
|
1328
|
+
return task;
|
|
1329
|
+
});
|
|
1330
|
+
}
|
|
1331
|
+
async deletePendingVideoTask(taskId) {
|
|
1332
|
+
await this.loadPendingVideoTasks();
|
|
1333
|
+
await this.pendingLock.acquire(async () => {
|
|
1334
|
+
if (!this.pendingVideoCache) return;
|
|
1335
|
+
if (!this.pendingVideoCache.tasks[taskId]) return;
|
|
1336
|
+
delete this.pendingVideoCache.tasks[taskId];
|
|
1337
|
+
await this.savePendingVideoTasksInternal();
|
|
1338
|
+
});
|
|
1339
|
+
}
|
|
1340
|
+
/**
|
|
1341
|
+
* 获取某个用户最近一次未扣费的待结算视频任务
|
|
1342
|
+
*/
|
|
1343
|
+
async getLatestPendingVideoTaskForUser(userId) {
|
|
1344
|
+
const data = await this.loadPendingVideoTasks();
|
|
1345
|
+
const tasks = Object.values(data.tasks).filter((t) => t.userId === userId && !t.charged).sort((a, b) => {
|
|
1346
|
+
const ta = Date.parse(a.createdAt || "") || 0;
|
|
1347
|
+
const tb = Date.parse(b.createdAt || "") || 0;
|
|
1348
|
+
return tb - ta;
|
|
1349
|
+
});
|
|
1350
|
+
return tasks[0] || null;
|
|
1351
|
+
}
|
|
1240
1352
|
// 获取特定用户数据
|
|
1241
1353
|
async getUserData(userId, userName) {
|
|
1242
1354
|
await this.loadUsersData();
|
|
@@ -2230,6 +2342,7 @@ ${infoParts.join("\n")}`;
|
|
|
2230
2342
|
if (!userManager.startTask(userId)) {
|
|
2231
2343
|
return "您有一个任务正在进行中,请等待完成";
|
|
2232
2344
|
}
|
|
2345
|
+
let createdTaskId = null;
|
|
2233
2346
|
try {
|
|
2234
2347
|
const inputResult = await getInputData(session, img, "single");
|
|
2235
2348
|
if ("error" in inputResult) {
|
|
@@ -2271,24 +2384,41 @@ ${infoParts.join("\n")}`;
|
|
|
2271
2384
|
💡 提示:生成过程中可继续使用其他功能`
|
|
2272
2385
|
);
|
|
2273
2386
|
const startTime = Date.now();
|
|
2274
|
-
const
|
|
2387
|
+
const taskId = await videoProvider.createVideoTask(
|
|
2275
2388
|
prompt,
|
|
2276
2389
|
imageUrls[0],
|
|
2277
2390
|
{
|
|
2278
2391
|
duration,
|
|
2279
2392
|
aspectRatio: ratio
|
|
2280
|
-
}
|
|
2281
|
-
config.videoMaxWaitTime
|
|
2393
|
+
}
|
|
2282
2394
|
);
|
|
2283
|
-
|
|
2395
|
+
createdTaskId = taskId;
|
|
2396
|
+
await userManager.addPendingVideoTask({
|
|
2397
|
+
taskId,
|
|
2398
|
+
userId,
|
|
2399
|
+
userName,
|
|
2400
|
+
commandName: "图生视频",
|
|
2401
|
+
credits: videoCredits,
|
|
2402
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2403
|
+
charged: false
|
|
2404
|
+
});
|
|
2405
|
+
await new Promise((resolve) => setTimeout(resolve, 3e3));
|
|
2406
|
+
const videoUrl = await videoProvider.waitForVideo(taskId, config.videoMaxWaitTime);
|
|
2284
2407
|
await session.send(import_koishi2.h.video(videoUrl));
|
|
2408
|
+
await recordUserUsage(session, "图生视频", videoCredits, false);
|
|
2409
|
+
await userManager.markPendingVideoTaskCharged(taskId);
|
|
2410
|
+
await userManager.deletePendingVideoTask(taskId);
|
|
2285
2411
|
const totalTime = Math.floor((Date.now() - startTime) / 1e3);
|
|
2286
2412
|
await session.send(`✅ 视频生成完成!(耗时 ${totalTime} 秒)`);
|
|
2287
2413
|
} catch (error) {
|
|
2288
2414
|
logger.error("视频生成失败", { userId, error: sanitizeError(error) });
|
|
2289
2415
|
const errorMsg = error.message || "";
|
|
2290
2416
|
if (errorMsg.includes("任务ID:")) {
|
|
2291
|
-
return
|
|
2417
|
+
return "⏳ 视频生成超时(任务仍在后台继续生成),请稍后发送“查询视频”获取结果";
|
|
2418
|
+
}
|
|
2419
|
+
try {
|
|
2420
|
+
if (createdTaskId) await userManager.deletePendingVideoTask(createdTaskId);
|
|
2421
|
+
} catch {
|
|
2292
2422
|
}
|
|
2293
2423
|
return `视频生成失败:${sanitizeString(errorMsg)}`;
|
|
2294
2424
|
} finally {
|
|
@@ -2297,28 +2427,38 @@ ${infoParts.join("\n")}`;
|
|
|
2297
2427
|
});
|
|
2298
2428
|
}
|
|
2299
2429
|
if (config.enableVideoGeneration && videoProvider) {
|
|
2300
|
-
ctx.command("查询视频
|
|
2430
|
+
ctx.command("查询视频 [taskId:string]", "查询视频生成状态(不传任务ID则查询自己最近一次任务)").action(async ({ session }, taskId) => {
|
|
2301
2431
|
if (!session?.userId) return "会话无效";
|
|
2302
|
-
|
|
2303
|
-
|
|
2432
|
+
const trimmedTaskId = (taskId || "").trim();
|
|
2433
|
+
const resolvedTaskId = trimmedTaskId ? trimmedTaskId : (await userManager.getLatestPendingVideoTaskForUser(session.userId))?.taskId;
|
|
2434
|
+
if (!resolvedTaskId) {
|
|
2435
|
+
return "你当前没有可查询的待生成视频任务";
|
|
2304
2436
|
}
|
|
2305
2437
|
try {
|
|
2306
2438
|
await session.send("🔍 正在查询视频生成状态...");
|
|
2307
|
-
const status = await videoProvider.queryTaskStatus(
|
|
2439
|
+
const status = await videoProvider.queryTaskStatus(resolvedTaskId);
|
|
2308
2440
|
if (status.status === "completed" && status.videoUrl) {
|
|
2441
|
+
const pending = await userManager.getPendingVideoTask(resolvedTaskId);
|
|
2442
|
+
if (pending && pending.userId && pending.userId !== session.userId) {
|
|
2443
|
+
return "该任务ID不属于当前用户,无法查询";
|
|
2444
|
+
}
|
|
2309
2445
|
await session.send(import_koishi2.h.video(status.videoUrl));
|
|
2446
|
+
if (pending && !pending.charged) {
|
|
2447
|
+
await recordUserUsage(session, pending.commandName, pending.credits, false);
|
|
2448
|
+
await userManager.markPendingVideoTaskCharged(resolvedTaskId);
|
|
2449
|
+
await userManager.deletePendingVideoTask(resolvedTaskId);
|
|
2450
|
+
}
|
|
2310
2451
|
return "✅ 视频已生成完成!";
|
|
2311
2452
|
} else if (status.status === "processing" || status.status === "pending") {
|
|
2312
2453
|
const progressText = status.progress ? `(进度:${status.progress}%)` : "";
|
|
2313
|
-
return `⏳ 视频正在生成中${progressText}
|
|
2314
|
-
任务ID:${taskId}`;
|
|
2454
|
+
return `⏳ 视频正在生成中${progressText},请稍后再次查询`;
|
|
2315
2455
|
} else if (status.status === "failed") {
|
|
2316
2456
|
return `❌ 视频生成失败:${status.error || "未知错误"}`;
|
|
2317
2457
|
} else {
|
|
2318
2458
|
return `❓ 未知状态:${status.status}`;
|
|
2319
2459
|
}
|
|
2320
2460
|
} catch (error) {
|
|
2321
|
-
logger.error("查询视频任务失败", { taskId, error: sanitizeError(error) });
|
|
2461
|
+
logger.error("查询视频任务失败", { taskId: resolvedTaskId, error: sanitizeError(error) });
|
|
2322
2462
|
return `查询失败:${sanitizeString(error.message)}`;
|
|
2323
2463
|
}
|
|
2324
2464
|
});
|
|
@@ -2343,6 +2483,7 @@ ${infoParts.join("\n")}`;
|
|
|
2343
2483
|
if (!userManager.startTask(userId)) {
|
|
2344
2484
|
return "您有一个任务正在进行中,请等待完成";
|
|
2345
2485
|
}
|
|
2486
|
+
let createdTaskId = null;
|
|
2346
2487
|
try {
|
|
2347
2488
|
const inputResult = await getInputData(session, img, "single");
|
|
2348
2489
|
if ("error" in inputResult) {
|
|
@@ -2361,23 +2502,40 @@ ${infoParts.join("\n")}`;
|
|
|
2361
2502
|
📝 描述:${finalPrompt}
|
|
2362
2503
|
⏱️ 预计需要 1-3 分钟`
|
|
2363
2504
|
);
|
|
2364
|
-
const
|
|
2505
|
+
const taskId = await videoProvider.createVideoTask(
|
|
2365
2506
|
finalPrompt,
|
|
2366
2507
|
imageUrls[0],
|
|
2367
2508
|
{
|
|
2368
2509
|
duration: style.duration || 15,
|
|
2369
2510
|
aspectRatio: style.aspectRatio || "16:9"
|
|
2370
|
-
}
|
|
2371
|
-
config.videoMaxWaitTime
|
|
2511
|
+
}
|
|
2372
2512
|
);
|
|
2373
|
-
|
|
2513
|
+
createdTaskId = taskId;
|
|
2514
|
+
await userManager.addPendingVideoTask({
|
|
2515
|
+
taskId,
|
|
2516
|
+
userId,
|
|
2517
|
+
userName,
|
|
2518
|
+
commandName: style.commandName,
|
|
2519
|
+
credits: videoCredits,
|
|
2520
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2521
|
+
charged: false
|
|
2522
|
+
});
|
|
2523
|
+
await new Promise((resolve) => setTimeout(resolve, 3e3));
|
|
2524
|
+
const videoUrl = await videoProvider.waitForVideo(taskId, config.videoMaxWaitTime);
|
|
2374
2525
|
await session.send(import_koishi2.h.video(videoUrl));
|
|
2526
|
+
await recordUserUsage(session, style.commandName, videoCredits, false);
|
|
2527
|
+
await userManager.markPendingVideoTaskCharged(taskId);
|
|
2528
|
+
await userManager.deletePendingVideoTask(taskId);
|
|
2375
2529
|
await session.send(`✅ 视频生成完成!`);
|
|
2376
2530
|
} catch (error) {
|
|
2377
2531
|
logger.error("视频风格转换失败", { userId, style: style.commandName, error: sanitizeError(error) });
|
|
2378
2532
|
const errorMsg = error.message || "";
|
|
2379
2533
|
if (errorMsg.includes("任务ID:")) {
|
|
2380
|
-
return
|
|
2534
|
+
return "⏳ 视频生成超时(任务仍在后台继续生成),请稍后发送“查询视频”获取结果";
|
|
2535
|
+
}
|
|
2536
|
+
try {
|
|
2537
|
+
if (createdTaskId) await userManager.deletePendingVideoTask(createdTaskId);
|
|
2538
|
+
} catch {
|
|
2381
2539
|
}
|
|
2382
2540
|
return `视频生成失败:${sanitizeString(errorMsg)}`;
|
|
2383
2541
|
} finally {
|
|
@@ -21,6 +21,10 @@ export declare class YunwuVideoProvider implements VideoProvider {
|
|
|
21
21
|
* 轮询等待任务完成
|
|
22
22
|
*/
|
|
23
23
|
private pollTaskCompletion;
|
|
24
|
+
/**
|
|
25
|
+
* 等待指定任务完成并返回视频 URL(供外部在拿到 taskId 后自行控制扣费/提示)
|
|
26
|
+
*/
|
|
27
|
+
waitForVideo(taskId: string, maxWaitTime?: number): Promise<string>;
|
|
24
28
|
/**
|
|
25
29
|
* 生成视频(主入口)
|
|
26
30
|
*/
|
|
@@ -39,15 +39,33 @@ export interface RechargeHistory {
|
|
|
39
39
|
lastUpdate: string;
|
|
40
40
|
records: RechargeRecord[];
|
|
41
41
|
}
|
|
42
|
+
export interface PendingVideoTask {
|
|
43
|
+
taskId: string;
|
|
44
|
+
userId: string;
|
|
45
|
+
userName: string;
|
|
46
|
+
commandName: string;
|
|
47
|
+
credits: number;
|
|
48
|
+
createdAt: string;
|
|
49
|
+
charged: boolean;
|
|
50
|
+
chargedAt?: string;
|
|
51
|
+
}
|
|
52
|
+
export interface PendingVideoTasksData {
|
|
53
|
+
version: string;
|
|
54
|
+
lastUpdate: string;
|
|
55
|
+
tasks: Record<string, PendingVideoTask>;
|
|
56
|
+
}
|
|
42
57
|
export declare class UserManager {
|
|
43
58
|
private dataDir;
|
|
44
59
|
private dataFile;
|
|
45
60
|
private backupFile;
|
|
46
61
|
private rechargeHistoryFile;
|
|
62
|
+
private pendingVideoTasksFile;
|
|
47
63
|
private logger;
|
|
48
64
|
private dataLock;
|
|
49
65
|
private historyLock;
|
|
66
|
+
private pendingLock;
|
|
50
67
|
private usersCache;
|
|
68
|
+
private pendingVideoCache;
|
|
51
69
|
private activeTasks;
|
|
52
70
|
private rateLimitMap;
|
|
53
71
|
private securityBlockMap;
|
|
@@ -59,6 +77,19 @@ export declare class UserManager {
|
|
|
59
77
|
isAdmin(userId: string, config: Config): boolean;
|
|
60
78
|
private loadUsersData;
|
|
61
79
|
private saveUsersDataInternal;
|
|
80
|
+
private loadPendingVideoTasks;
|
|
81
|
+
private savePendingVideoTasksInternal;
|
|
82
|
+
/**
|
|
83
|
+
* 记录一个待结算的视频任务(任务创建成功后立即写入,避免“超时但最终成功”不扣费)
|
|
84
|
+
*/
|
|
85
|
+
addPendingVideoTask(task: PendingVideoTask): Promise<void>;
|
|
86
|
+
getPendingVideoTask(taskId: string): Promise<PendingVideoTask | null>;
|
|
87
|
+
markPendingVideoTaskCharged(taskId: string): Promise<PendingVideoTask | null>;
|
|
88
|
+
deletePendingVideoTask(taskId: string): Promise<void>;
|
|
89
|
+
/**
|
|
90
|
+
* 获取某个用户最近一次未扣费的待结算视频任务
|
|
91
|
+
*/
|
|
92
|
+
getLatestPendingVideoTaskForUser(userId: string): Promise<PendingVideoTask | null>;
|
|
62
93
|
getUserData(userId: string, userName: string): Promise<UserData>;
|
|
63
94
|
getAllUsers(): Promise<UsersData>;
|
|
64
95
|
updateUsersBatch(updates: (data: UsersData) => void): Promise<void>;
|