Files
2026-11-05 01:04:53 +08:00

25 lines
3.3 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import {getSchema} from '../../../../lib/schemas';
import type {NextApiRequest,NextApiResponse} from 'next';
import formidable from 'formidable';
import {readFile,unlink} from 'node:fs/promises';
import {randomUUID} from 'node:crypto';
import {buildSchema,InputError} from '../../../../lib/schema';
import {authorize,saveTask,links,Task} from '../../../../lib/store';
export const config={api:{bodyParser:false}};
export default async function handler(req:NextApiRequest,res:NextApiResponse){
if(!authorize(req,res))return;
if(req.method!=='POST'){res.setHeader('Allow','POST');return res.status(405).json({error:{code:'METHOD_NOT_ALLOWED',message:'Use POST'}});}
const cleanup:string[]=[];
try{let schemaId:any,fields:any,text='',title='提取任务',file:{name:string;mime:string;data:Buffer}|undefined;
if(req.headers['content-type']?.startsWith('multipart/form-data')){const form=formidable({maxFileSize:12*1024*1024,maxTotalFileSize:12*1024*1024,maxFiles:1,maxFields:5,maxFieldsSize:300000,filter:p=>p.name==='file'});form.on('fileBegin',(_,f)=>cleanup.push(f.filepath));const [v,fs]=await form.parse(req);try{fields=JSON.parse(v.fields?.[0]||'null');}catch{throw new InputError('fields 必须是 JSON');}schemaId=v.schemaId?.[0];text=v.text?.[0]||'';title=v.title?.[0]||title;const f=fs.file?.[0];if(f)file={name:f.originalFilename||'document',mime:f.mimetype||'',data:await readFile(f.filepath)};
}else if(req.headers['content-type']?.startsWith('application/json')){const chunks:Buffer[]=[];let size=0;for await(const c of req){size+=c.length;if(size>300000)throw new InputError('请求最多 300 KB');chunks.push(Buffer.from(c));}let b;try{b=JSON.parse(Buffer.concat(chunks).toString());}catch{throw new InputError('无效 JSON');}fields=b?.fields;schemaId=b?.schemaId;text=b?.text;title=b?.title||title;
}else throw new InputError('使用 JSON 或 multipart/form-data');
if(schemaId){if(fields!=null)throw new InputError('fields 和 schemaId 只能提供一个');try{fields=(await getSchema(String(schemaId))).fields;}catch(e:any){if(e.code==='ENOENT'||e.message==='NOT_FOUND')throw new InputError('schemaId 对应的 schema 不存在');throw e;}}const schema=buildSchema(fields);if(typeof title!=='string')throw new InputError('标题必须是文本');title=title.slice(0,120);
if(!file){if(typeof text!=='string'||!text.trim()||text.length>100000)throw new InputError('请提供文件或 1–100000 字符文本');file={name:'document.txt',mime:'text/plain; charset=utf-8',data:Buffer.from(text)};}
if(!/\.(pdf|png|jpe?g|webp|txt|md|csv|json)$/i.test(file.name))throw new InputError('支持 PDF、图片、TXT、MD、CSV、JSON');
if(/\.pdf$/i.test(file.name)){if(file.data.subarray(0,5).toString()!=='%PDF-')throw new InputError('PDF 文件内容无效');file.mime='application/pdf';}
const task:Task={id:randomUUID(),title,fields,schema,createdAt:new Date().toISOString(),document:{name:file.name,mime:file.mime,size:file.data.length},status:'ready'};
await saveTask(task,file.data);res.status(201).json({...task,links:links(task.id)});
}catch(e:any){const invalid=e instanceof InputError||e.httpCode===413;res.status(invalid?400:500).json({error:{code:invalid?'INVALID_REQUEST':'CREATE_FAILED',message:invalid?e.message:'任务创建失败'}});}finally{await Promise.all(cleanup.map(p=>unlink(p).catch(()=>{})));}
}