"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.queueService = void 0; const models_1 = require("../../models"); const TtsService = __importStar(require("../tts/tts.service")); const MAX_CONCURRENCY = 2; const POLL_INTERVAL_MS = 1000; class QueueService { constructor() { this.isRunning = false; this.activeCount = 0; this.timer = null; } isRunning = false; activeCount = 0; timer = null; /** * 添加任务到队列 */ async addTask(userId, text, voiceId, voiceParams) { if (!text || !text.trim()) { throw new Error('请输入要转换的文本'); } if (!voiceId) { throw new Error('请选择音色'); } const existing = await models_1.prisma.audioRecord.findFirst({ where: { userId: userId || null, status: 'pending', }, }); if (existing) { throw new Error('已有待处理任务,请等待完成后再添加'); } const params = { speed: voiceParams?.speed || 1.0, pitch: voiceParams?.pitch || 0, volume: voiceParams?.volume || 50, }; const record = await models_1.prisma.audioRecord.create({ data: { userId: userId || null, audioId: `queue_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`, title: text.slice(0, 50), text, wordCount: text.length, voiceId, voiceParams: JSON.stringify(params), status: 'pending', }, }); if (this.isRunning) { this._poll(); } return { id: record.id, audioId: record.audioId, status: record.status }; } /** * 启动队列 */ start() { if (this.isRunning) { return { running: true, message: '队列已在运行中' }; } this.isRunning = true; console.log('[QueueService] 队列已启动 (并发=' + MAX_CONCURRENCY + ')'); this._poll(); return { running: true, message: '队列已启动' }; } /** * 停止队列 */ stop() { if (!this.isRunning) { return { running: false, message: '队列已停止' }; } this.isRunning = false; if (this.timer) { clearTimeout(this.timer); this.timer = null; } console.log('[QueueService] 队列已停止 (活跃任务=' + this.activeCount + ')'); return { running: false, message: '队列已停止,正在处理的任务会继续完成' }; } /** * 获取队列状态 */ async getStatus() { const [pending, processing, completed, failed] = await Promise.all([ models_1.prisma.audioRecord.count({ where: { status: 'pending' } }), models_1.prisma.audioRecord.count({ where: { status: 'processing' } }), models_1.prisma.audioRecord.count({ where: { status: 'completed' } }), models_1.prisma.audioRecord.count({ where: { status: 'failed' } }), ]); return { running: this.isRunning, activeCount: this.activeCount, maxConcurrency: MAX_CONCURRENCY, pending, processing, completed, failed, }; } /** * 获取任务列表 */ async getTasks(status, page = 1, pageSize = 20) { const where = {}; if (status) { where.status = status; } const [tasks, total] = await Promise.all([ models_1.prisma.audioRecord.findMany({ where, orderBy: { createdAt: 'desc' }, skip: (page - 1) * pageSize, take: pageSize, select: { id: true, audioId: true, title: true, wordCount: true, voiceId: true, voiceParams: true, audioUrl: true, audioDuration: true, audioSize: true, status: true, errorMsg: true, createdAt: true, updatedAt: true, }, }), models_1.prisma.audioRecord.count({ where }), ]); return { list: tasks, total, page, pageSize }; } /** * 删除任务 */ async deleteTask(audioId) { const record = await models_1.prisma.audioRecord.findUnique({ where: { audioId }, }); if (!record) { throw new Error('任务不存在'); } if (record.status === 'processing') { throw new Error('任务正在处理中,无法删除'); } await models_1.prisma.audioRecord.delete({ where: { audioId }, }); return { deleted: true, audioId }; } /** * 清空所有待处理任务 */ async clearPendingTasks() { const result = await models_1.prisma.audioRecord.deleteMany({ where: { status: 'pending' }, }); return { deleted: result.count }; } /** * 内部:轮询处理队列 */ _poll() { if (this.timer) { clearTimeout(this.timer); } this.timer = setTimeout(() => this._pollTick(), POLL_INTERVAL_MS); } async _pollTick() { if (!this.isRunning) return; try { while (this.isRunning && this.activeCount < MAX_CONCURRENCY) { const task = await this._claimNextTask(); if (!task) break; this.activeCount++; this._processTask(task).finally(() => { this.activeCount--; }); } } catch (err) { console.error('[QueueService] 轮询异常:', err.message); } if (this.isRunning) { this._poll(); } } async _claimNextTask() { return models_1.prisma.$transaction(async (tx) => { const task = await tx.audioRecord.findFirst({ where: { status: 'pending' }, orderBy: { createdAt: 'asc' }, }); if (!task) return null; await tx.audioRecord.update({ where: { id: task.id }, data: { status: 'processing' }, }); return task; }); } async _processTask(task) { const logPrefix = '[QueueService] 任务 #' + task.id; console.log(logPrefix + ' 开始处理: ' + task.audioId + ' 文本长度=' + task.wordCount); try { const voiceParams = task.voiceParams ? JSON.parse(task.voiceParams) : {}; await TtsService.generateAudio( task.userId, task.text, task.voiceId, voiceParams, async (audioUrl, duration) => { try { await models_1.prisma.audioRecord.update({ where: { id: task.id }, data: { status: 'completed', audioUrl, audioDuration: duration || 0, }, }); console.log(logPrefix + ' 完成 → ' + audioUrl); } catch (err) { console.error(logPrefix + ' 更新完成状态失败:', err.message); } } ); await new Promise((resolve) => { const checkComplete = async () => { const record = await models_1.prisma.audioRecord.findUnique({ where: { id: task.id }, }); if (record && (record.status === 'completed' || record.status === 'failed')) { resolve(); return; } setTimeout(checkComplete, 2000); }; setTimeout(checkComplete, 5000); }); } catch (err) { console.error(logPrefix + ' 处理异常:', err.message); await models_1.prisma.audioRecord.update({ where: { id: task.id }, data: { status: 'failed', errorMsg: err.message || '未知错误', }, }).catch(() => {}); } } } exports.queueService = new QueueService();