import { runtimeConfig } from '../../config/runtime.js' const DEFAULT_TIMEOUT_MS = 60_000 type OcrAction = 'recognize' | 'batch' type OcrRequestPayload = Record type OcrHttpResult = Record & { code: number msg?: string } export async function recognizeTencentCaptcha(payload: OcrRequestPayload): Promise { return callOcrViaHttp('recognize', payload) } export async function recognizeImageCaptcha(payload: OcrRequestPayload): Promise { return callOcrViaHttp('recognize', payload) } export async function batchRecognizeTencentCaptcha(payload: OcrRequestPayload): Promise { return callOcrViaHttp('batch', payload, { timeoutMs: 180_000 }) } export async function warmupOcrService(): Promise { await warmupOcrViaHttp() } export async function closeOcrService(): Promise { // HTTP 模式无持久连接,无需关闭。 } // ── HTTP Call ─────────────────────────────────────────────────────────────── async function callOcrViaHttp( action: OcrAction, payload: OcrRequestPayload, { timeoutMs = DEFAULT_TIMEOUT_MS }: { timeoutMs?: number } = {}, ): Promise { const baseUrl = String(runtimeConfig.ocr.baseUrl).trim().replace(/\/+$/, '') if (!baseUrl) { throw new Error('OCR_BASE_URL 未配置,无法调用 OCR 服务') } const url = `${baseUrl}/ocr/${action}` let response try { response = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), signal: AbortSignal.timeout(timeoutMs), }) } catch (err) { if (err instanceof Error && err.name === 'TimeoutError') { throw new Error(`OCR 请求超时(${timeoutMs}ms),请检查 OCR 服务状态`) } throw new Error(`OCR 服务不可用(${baseUrl}),请检查 OCR_BASE_URL 或外部 OCR 服务状态`) } if (!response.ok) { throw new Error(`OCR 服务返回错误:HTTP ${response.status}`) } const result = await response.json() if (!result || typeof result !== 'object' || Array.isArray(result)) { throw new Error('OCR 服务返回了无效响应') } const ocrResult = result as OcrHttpResult if (ocrResult.code !== 0) { throw new Error(`OCR 服务返回错误:${ocrResult.msg || '未知错误'}`) } return ocrResult } async function warmupOcrViaHttp(): Promise { const baseUrl = String(runtimeConfig.ocr.baseUrl).trim().replace(/\/+$/, '') if (!baseUrl) { throw new Error('OCR_BASE_URL 未配置,无法启动 OCR 健康检查') } let response try { response = await fetch(`${baseUrl}/health`, { signal: AbortSignal.timeout(30_000), }) } catch (err) { if (err instanceof Error && err.name === 'TimeoutError') { throw new Error(`OCR 服务健康检查超时,请检查 ${baseUrl} 是否可访问`) } throw new Error(`OCR 服务不可用(${baseUrl}),请检查 OCR_BASE_URL 或外部 OCR 服务状态`) } if (!response.ok) { throw new Error(`OCR 服务健康检查失败:HTTP ${response.status}`) } }