102 lines
3.2 KiB
TypeScript
102 lines
3.2 KiB
TypeScript
import { runtimeConfig } from '../../config/runtime.js'
|
|
|
|
const DEFAULT_TIMEOUT_MS = 60_000
|
|
|
|
type OcrAction = 'recognize' | 'batch'
|
|
|
|
type OcrRequestPayload = Record<string, unknown>
|
|
|
|
type OcrHttpResult = Record<string, any> & {
|
|
code: number
|
|
msg?: string
|
|
}
|
|
|
|
export async function recognizeTencentCaptcha(payload: OcrRequestPayload): Promise<OcrHttpResult> {
|
|
return callOcrViaHttp('recognize', payload)
|
|
}
|
|
|
|
export async function recognizeImageCaptcha(payload: OcrRequestPayload): Promise<OcrHttpResult> {
|
|
return callOcrViaHttp('recognize', payload)
|
|
}
|
|
|
|
export async function batchRecognizeTencentCaptcha(payload: OcrRequestPayload): Promise<OcrHttpResult> {
|
|
return callOcrViaHttp('batch', payload, { timeoutMs: 180_000 })
|
|
}
|
|
|
|
export async function warmupOcrService(): Promise<void> {
|
|
await warmupOcrViaHttp()
|
|
}
|
|
|
|
export async function closeOcrService(): Promise<void> {
|
|
// HTTP 模式无持久连接,无需关闭。
|
|
}
|
|
|
|
// ── HTTP Call ───────────────────────────────────────────────────────────────
|
|
|
|
async function callOcrViaHttp(
|
|
action: OcrAction,
|
|
payload: OcrRequestPayload,
|
|
{ timeoutMs = DEFAULT_TIMEOUT_MS }: { timeoutMs?: number } = {},
|
|
): Promise<OcrHttpResult> {
|
|
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<void> {
|
|
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}`)
|
|
}
|
|
}
|