feat(API): 添加文件上传接口

- 实现 POST /api/files/upload 端点
- 支持图片、PDF、Word、Excel、文本、代码等格式
- 限制最大文件大小为 20MB
- 自动创建 public/uploads 目录存储上传文件
- 使用 nanoid 生成唯一文件名
- 返回文件信息包含 URL、大小、类型等
- 提供 GET 端点获取上传配置
This commit is contained in:
gaoziman 2025-12-20 12:15:49 +08:00
parent cb86380e7f
commit 66a58a2d3d

View File

@ -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,
});
}