feat(API): 添加文件上传接口
- 实现 POST /api/files/upload 端点 - 支持图片、PDF、Word、Excel、文本、代码等格式 - 限制最大文件大小为 20MB - 自动创建 public/uploads 目录存储上传文件 - 使用 nanoid 生成唯一文件名 - 返回文件信息包含 URL、大小、类型等 - 提供 GET 端点获取上传配置
This commit is contained in:
parent
cb86380e7f
commit
66a58a2d3d
131
src/app/api/files/upload/route.ts
Normal file
131
src/app/api/files/upload/route.ts
Normal 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,
|
||||||
|
});
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user