diff --git a/src/app/api/files/upload/route.ts b/src/app/api/files/upload/route.ts new file mode 100644 index 0000000..71adee4 --- /dev/null +++ b/src/app/api/files/upload/route.ts @@ -0,0 +1,131 @@ +import { NextResponse } from 'next/server'; +import { writeFile, mkdir } from 'fs/promises'; +import { existsSync } from 'fs'; +import path from 'path'; +import { nanoid } from 'nanoid'; + +// 允许的 MIME 类型 +const ALLOWED_MIME_TYPES = [ + // 图片 + 'image/jpeg', + 'image/png', + 'image/gif', + 'image/webp', + 'image/svg+xml', + // PDF + 'application/pdf', + // Word + 'application/msword', + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + // Excel + 'application/vnd.ms-excel', + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + // 文本 + 'text/plain', + 'text/markdown', + 'text/csv', + // 代码 + 'text/javascript', + 'text/typescript', + 'text/html', + 'text/css', + 'application/json', + 'application/xml', +]; + +// 最大文件大小 20MB +const MAX_FILE_SIZE = 20 * 1024 * 1024; + +// 上传目录 +const UPLOAD_DIR = path.join(process.cwd(), 'public', 'uploads'); + +// 确保上传目录存在 +async function ensureUploadDir() { + if (!existsSync(UPLOAD_DIR)) { + await mkdir(UPLOAD_DIR, { recursive: true }); + } +} + +// 获取文件扩展名 +function getExtension(filename: string): string { + const ext = path.extname(filename).toLowerCase(); + return ext || '.bin'; +} + +// POST /api/files/upload - 上传文件 +export async function POST(request: Request) { + try { + const formData = await request.formData(); + const file = formData.get('file') as File | null; + + if (!file) { + return NextResponse.json( + { error: '没有提供文件' }, + { status: 400 } + ); + } + + // 检查文件大小 + if (file.size > MAX_FILE_SIZE) { + return NextResponse.json( + { error: `文件大小超过限制(最大 ${MAX_FILE_SIZE / 1024 / 1024}MB)` }, + { status: 400 } + ); + } + + // 检查 MIME 类型(允许一些额外的代码文件类型) + const isAllowedType = ALLOWED_MIME_TYPES.includes(file.type) || + file.type.startsWith('text/') || + file.type === 'application/octet-stream'; // 允许未知类型(通过扩展名判断) + + if (!isAllowedType) { + return NextResponse.json( + { error: `不支持的文件类型: ${file.type}` }, + { status: 400 } + ); + } + + // 确保上传目录存在 + await ensureUploadDir(); + + // 生成唯一文件名 + const fileId = nanoid(); + const ext = getExtension(file.name); + const filename = `${fileId}${ext}`; + const filepath = path.join(UPLOAD_DIR, filename); + + // 读取文件内容 + const bytes = await file.arrayBuffer(); + const buffer = Buffer.from(bytes); + + // 写入文件 + await writeFile(filepath, buffer); + + // 返回文件信息 + const fileUrl = `/uploads/${filename}`; + + return NextResponse.json({ + success: true, + fileId, + filename, + originalName: file.name, + size: file.size, + mimeType: file.type, + url: fileUrl, + }); + } catch (error) { + console.error('File upload error:', error); + return NextResponse.json( + { error: '文件上传失败' }, + { status: 500 } + ); + } +} + +// GET /api/files/upload - 获取上传配置 +export async function GET() { + return NextResponse.json({ + maxFileSize: MAX_FILE_SIZE, + allowedMimeTypes: ALLOWED_MIME_TYPES, + }); +}