import { execFileSync } from 'node:child_process' import { existsSync } from 'node:fs' import { join, resolve } from 'node:path' import { requireAdmin } from '../../utils/auth.js' /** * 对 public/uploads 下的图片跑 tesseract chi_sim OCR。 * Body: { image: '/uploads/reviews/xxx.jpg' } * 仅允许 /uploads/ 前缀,防止任意文件读取。 */ export default defineEventHandler(async (event) => { requireAdmin(event) const { image } = await readBody(event) if (!image || typeof image !== 'string' || !image.startsWith('/uploads/')) { throw createError({ statusCode: 422, message: '仅支持 /uploads/ 下的图片' }) } const rel = image.replace(/^\/+/, '') const abs = resolve(join(process.cwd(), 'public', rel)) const allowedRoot = resolve(join(process.cwd(), 'public/uploads')) if (!abs.startsWith(allowedRoot + '/') && abs !== allowedRoot) { throw createError({ statusCode: 400, message: '非法路径' }) } if (!existsSync(abs)) { throw createError({ statusCode: 404, message: '图片不存在' }) } try { const text = execFileSync('tesseract', [abs, 'stdout', '-l', 'chi_sim'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'], maxBuffer: 10 * 1024 * 1024, }) return { text: text || '' } } catch (e) { throw createError({ statusCode: 500, message: 'OCR 执行失败(请确认已安装 tesseract + chi_sim 语言包)' }) } })