mirror of
https://github.com/foxhui/WebAI2API.git
synced 2026-06-16 21:03:59 +08:00
feat: 支持多窗口并行且支持账号数据隔离
This commit is contained in:
+24
-20
@@ -58,35 +58,38 @@ export function createRouter(context) {
|
||||
* 处理 GET /v1/cookies
|
||||
* @param {import('http').ServerResponse} res - HTTP 响应
|
||||
* @param {string} requestId - 请求 ID
|
||||
* @param {string} [workerName] - 可选,指定 Worker 名称
|
||||
* @param {string} [domain] - 可选,指定获取某个域名的 Cookies
|
||||
*/
|
||||
async function handleCookies(res, requestId, domain) {
|
||||
const browserContext = queueManager.getBrowserContext();
|
||||
async function handleCookies(res, requestId, workerName, domain) {
|
||||
const poolContext = queueManager.getPoolContext();
|
||||
|
||||
if (!browserContext?.page) {
|
||||
if (!poolContext?.poolManager) {
|
||||
sendApiError(res, { code: ERROR_CODES.BROWSER_NOT_INITIALIZED });
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const context = browserContext.page.context();
|
||||
let cookies;
|
||||
|
||||
if (domain) {
|
||||
// 指定域名时,只获取该域名的 Cookies
|
||||
cookies = await context.cookies(domain.startsWith('http') ? domain : `https://${domain}`);
|
||||
} else {
|
||||
// 默认获取所有 Cookies
|
||||
cookies = await context.cookies();
|
||||
}
|
||||
|
||||
sendJson(res, 200, { cookies });
|
||||
const result = await queueManager.getWorkerCookies(workerName, domain);
|
||||
sendJson(res, 200, {
|
||||
worker: result.worker,
|
||||
cookies: result.cookies
|
||||
});
|
||||
} catch (err) {
|
||||
logger.error('服务器', '获取 Cookies 失败', { id: requestId, error: err.message });
|
||||
sendApiError(res, {
|
||||
code: ERROR_CODES.INTERNAL_ERROR,
|
||||
error: err.message
|
||||
});
|
||||
|
||||
// 区分错误类型
|
||||
if (err.message.includes('Worker 不存在') || err.message.includes('Worker not found')) {
|
||||
sendApiError(res, {
|
||||
code: ERROR_CODES.BAD_REQUEST,
|
||||
error: err.message
|
||||
});
|
||||
} else {
|
||||
sendApiError(res, {
|
||||
code: ERROR_CODES.INTERNAL_ERROR,
|
||||
error: err.message
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -195,8 +198,9 @@ export function createRouter(context) {
|
||||
if (req.method === 'GET' && pathname === '/v1/models') {
|
||||
handleModels(res);
|
||||
} else if (req.method === 'GET' && pathname === '/v1/cookies') {
|
||||
const workerName = parsedUrl.searchParams.get('name');
|
||||
const domain = parsedUrl.searchParams.get('domain');
|
||||
await handleCookies(res, requestId, domain);
|
||||
await handleCookies(res, requestId, workerName, domain);
|
||||
} else if (req.method === 'POST' && pathname.startsWith('/v1/chat/completions')) {
|
||||
await handleChatCompletions(req, res, requestId);
|
||||
} else {
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import sharp from 'sharp';
|
||||
import { IMAGE_POLICY } from '../backend/models.js';
|
||||
import { IMAGE_POLICY } from '../backend/registry.js';
|
||||
import { ERROR_CODES, getErrorMessage } from './errors.js';
|
||||
|
||||
/**
|
||||
@@ -128,11 +128,13 @@ export async function parseRequest(data, options) {
|
||||
prompt = prompt.trim();
|
||||
|
||||
// 解析模型参数
|
||||
let modelId = null;
|
||||
let modelKey = null;
|
||||
if (data.model) {
|
||||
modelId = resolveModelId(data.model);
|
||||
if (modelId) {
|
||||
logger.info('服务器', `触发模型: ${data.model} (${modelId})`, { id: requestId });
|
||||
// 只校验模型是否支持,不解析
|
||||
const resolved = resolveModelId(data.model);
|
||||
if (resolved) {
|
||||
modelKey = data.model; // 保留原始 modelKey,由 PoolManager 自行解析
|
||||
logger.info('服务器', `触发模型: ${data.model}`, { id: requestId });
|
||||
} else {
|
||||
return parseError(ERROR_CODES.INVALID_MODEL, `模型无效/后端 ${backendName} 不支持: ${data.model}`);
|
||||
}
|
||||
@@ -157,7 +159,7 @@ export async function parseRequest(data, options) {
|
||||
data: {
|
||||
prompt,
|
||||
imagePaths,
|
||||
modelId,
|
||||
modelId: modelKey, // 返回原始 modelKey
|
||||
modelName: data.model || null,
|
||||
isStreaming
|
||||
}
|
||||
|
||||
+45
-27
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* @fileoverview 任务队列管理模块
|
||||
* @description 负责请求队列、并发控制和心跳机制
|
||||
* @description 负责请求队列、并发控制和心跳机制,适配 Pool 模式架构
|
||||
*/
|
||||
|
||||
import { logger } from '../utils/logger.js';
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
buildChatCompletion,
|
||||
buildChatCompletionChunk
|
||||
} from './http/respond.js';
|
||||
import { ERROR_CODES } from './errors.js';
|
||||
|
||||
/**
|
||||
* @typedef {object} TaskContext
|
||||
@@ -34,9 +35,8 @@ import {
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {object} BrowserContext
|
||||
* @property {object} browser - Playwright 浏览器实例
|
||||
* @property {object} page - Playwright 页面实例
|
||||
* @typedef {object} PoolContext
|
||||
* @property {import('../backend/pool.js').PoolManager} poolManager - Pool 管理器
|
||||
* @property {object} config - 配置对象
|
||||
*/
|
||||
|
||||
@@ -44,14 +44,19 @@ import {
|
||||
* 创建任务队列管理器
|
||||
* @param {QueueConfig} queueConfig - 队列配置
|
||||
* @param {object} callbacks - 回调函数
|
||||
* @param {Function} callbacks.initBrowser - 初始化浏览器函数
|
||||
* @param {Function} callbacks.initBrowser - 初始化 Pool 函数
|
||||
* @param {Function} callbacks.generateImage - 生成图片函数
|
||||
* @param {object} callbacks.config - 配置对象
|
||||
* @param {Function} [callbacks.navigateToMonitor] - 监控导航函数
|
||||
* @param {Function} [callbacks.getCookies] - 获取 Cookies 函数
|
||||
* @returns {object} 队列管理器
|
||||
*/
|
||||
export function createQueueManager(queueConfig, callbacks) {
|
||||
const { maxConcurrent, maxQueueSize, keepaliveMode } = queueConfig;
|
||||
const { initBrowser, generateImage, config, navigateToMonitor } = callbacks;
|
||||
const { maxConcurrent, queueBuffer, keepaliveMode } = queueConfig;
|
||||
const { initBrowser, generateImage, config, navigateToMonitor, getCookies } = callbacks;
|
||||
|
||||
// 计算有效队列大小:0 表示不限制,否则为 maxConcurrent + buffer
|
||||
const effectiveQueueSize = queueBuffer === 0 ? Infinity : (maxConcurrent + queueBuffer);
|
||||
|
||||
/** @type {TaskContext[]} */
|
||||
const queue = [];
|
||||
@@ -59,8 +64,8 @@ export function createQueueManager(queueConfig, callbacks) {
|
||||
/** @type {number} */
|
||||
let processingCount = 0;
|
||||
|
||||
/** @type {BrowserContext|null} */
|
||||
let browserContext = null;
|
||||
/** @type {PoolContext|null} */
|
||||
let poolContext = null;
|
||||
|
||||
/**
|
||||
* 清理任务临时文件
|
||||
@@ -97,13 +102,13 @@ export function createQueueManager(queueConfig, callbacks) {
|
||||
}
|
||||
|
||||
try {
|
||||
// 确保浏览器已初始化
|
||||
if (!browserContext) {
|
||||
browserContext = await initBrowser(config);
|
||||
// 确保 Pool 已初始化
|
||||
if (!poolContext) {
|
||||
poolContext = await initBrowser(config);
|
||||
}
|
||||
|
||||
// 调用核心生图逻辑
|
||||
const result = await generateImage(browserContext, prompt, imagePaths, modelId, { id });
|
||||
// 调用核心生图逻辑 (通过 Pool 分发)
|
||||
const result = await generateImage(poolContext, prompt, imagePaths, modelId, { id });
|
||||
|
||||
// 清除心跳
|
||||
if (heartbeatInterval) clearInterval(heartbeatInterval);
|
||||
@@ -198,36 +203,49 @@ export function createQueueManager(queueConfig, callbacks) {
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function canAcceptNonStreaming() {
|
||||
return processingCount + queue.length < maxQueueSize;
|
||||
return processingCount + queue.length < effectiveQueueSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化浏览器
|
||||
* @returns {Promise<BrowserContext>}
|
||||
* 初始化 Pool
|
||||
* @returns {Promise<PoolContext>}
|
||||
*/
|
||||
async function initializeBrowser() {
|
||||
browserContext = await initBrowser(config);
|
||||
async function initializePool() {
|
||||
poolContext = await initBrowser(config);
|
||||
// 初始化完成后,触发首次监控跳转
|
||||
if (navigateToMonitor) {
|
||||
navigateToMonitor().catch(() => { });
|
||||
}
|
||||
return browserContext;
|
||||
return poolContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取浏览器上下文
|
||||
* @returns {BrowserContext|null}
|
||||
* 获取 Pool 上下文
|
||||
* @returns {PoolContext|null}
|
||||
*/
|
||||
function getBrowserContext() {
|
||||
return browserContext;
|
||||
function getPoolContext() {
|
||||
return poolContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定 Worker 的 Cookies (代理到后端)
|
||||
* @param {string} [workerName] - Worker 名称
|
||||
* @param {string} [domain] - 域名
|
||||
* @returns {Promise<{worker: string, cookies: object[]}>}
|
||||
*/
|
||||
async function getWorkerCookies(workerName, domain) {
|
||||
if (!getCookies) {
|
||||
throw new Error('getCookies 回调未注册');
|
||||
}
|
||||
return await getCookies(workerName, domain);
|
||||
}
|
||||
|
||||
return {
|
||||
addTask,
|
||||
getStatus,
|
||||
canAcceptNonStreaming,
|
||||
initializeBrowser,
|
||||
getBrowserContext,
|
||||
maxQueueSize
|
||||
initializePool,
|
||||
getPoolContext,
|
||||
getWorkerCookies
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user