1
This commit is contained in:
@@ -0,0 +1,127 @@
|
||||
export const dynamic = "force-dynamic";
|
||||
import getDb from '@/lib/db';
|
||||
import { getUserFromRequest, unauthorizedResponse } from '@/lib/auth';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
|
||||
// POST /api/upload - 上传文件附件
|
||||
export async function POST(request) {
|
||||
try {
|
||||
const user = getUserFromRequest(request);
|
||||
if (!user) return unauthorizedResponse();
|
||||
|
||||
const formData = await request.formData();
|
||||
const file = formData.get('file');
|
||||
const contract_id = formData.get('contract_id');
|
||||
|
||||
if (!file) {
|
||||
return Response.json(
|
||||
{ error: '请选择要上传的文件' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
if (!contract_id) {
|
||||
return Response.json(
|
||||
{ error: '请指定合同ID' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
// 确保上传目录存在
|
||||
const uploadDir = path.join(process.cwd(), 'public', 'uploads', 'attachments');
|
||||
if (!fs.existsSync(uploadDir)) {
|
||||
fs.mkdirSync(uploadDir, { recursive: true });
|
||||
}
|
||||
|
||||
// 生成唯一文件名
|
||||
const ext = path.extname(file.name) || '';
|
||||
const fileName = `${uuidv4()}${ext}`;
|
||||
const filePath = path.join(uploadDir, fileName);
|
||||
|
||||
// 保存文件
|
||||
const buffer = Buffer.from(await file.arrayBuffer());
|
||||
fs.writeFileSync(filePath, buffer);
|
||||
|
||||
// 相对于 public 的路径
|
||||
const publicPath = `/api/upload?file=${fileName}`;
|
||||
|
||||
// 插入附件记录
|
||||
const db = await getDb();
|
||||
const result = db.prepare(
|
||||
'INSERT INTO contract_attachments (contract_id, file_name, file_path, file_size) VALUES (?, ?, ?, ?)'
|
||||
).run(contract_id, file.name, publicPath, buffer.length);
|
||||
|
||||
const attachment = db.prepare('SELECT * FROM contract_attachments WHERE id = ?').get(result.lastInsertRowid);
|
||||
|
||||
return Response.json(attachment, { status: 201 });
|
||||
} catch (error) {
|
||||
console.error('上传文件失败:', error);
|
||||
return Response.json(
|
||||
{ error: '服务器内部错误' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// GET /api/upload - 动态读取并服务已上传的文件(规避 Next.js 静态文件缓存导致的 404)
|
||||
export async function GET(request) {
|
||||
try {
|
||||
const user = getUserFromRequest(request);
|
||||
if (!user) return unauthorizedResponse();
|
||||
|
||||
const { searchParams } = new URL(request.url);
|
||||
const file = searchParams.get('file');
|
||||
if (!file) {
|
||||
return Response.json(
|
||||
{ error: '缺少文件名' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
// 过滤路径,防止路径穿越安全漏洞
|
||||
const cleanFile = path.basename(file);
|
||||
const filePath = path.join(process.cwd(), 'public', 'uploads', 'attachments', cleanFile);
|
||||
|
||||
if (!fs.existsSync(filePath)) {
|
||||
return Response.json(
|
||||
{ error: '文件不存在' },
|
||||
{ status: 404 }
|
||||
);
|
||||
}
|
||||
|
||||
const buffer = fs.readFileSync(filePath);
|
||||
const ext = path.extname(cleanFile).toLowerCase();
|
||||
|
||||
// 映射 Content-Type
|
||||
let contentType = 'application/octet-stream';
|
||||
if (ext === '.png') contentType = 'image/png';
|
||||
else if (ext === '.jpg' || ext === '.jpeg') contentType = 'image/jpeg';
|
||||
else if (ext === '.gif') contentType = 'image/gif';
|
||||
else if (ext === '.webp') contentType = 'image/webp';
|
||||
else if (ext === '.bmp') contentType = 'image/bmp';
|
||||
else if (ext === '.pdf') contentType = 'application/pdf';
|
||||
else if (ext === '.xlsx') contentType = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
|
||||
else if (ext === '.xls') contentType = 'application/vnd.ms-excel';
|
||||
else if (ext === '.docx') contentType = 'application/vnd.openxmlformats-officedocument.wordprocessingml.document';
|
||||
else if (ext === '.txt') contentType = 'text/plain; charset=utf-8';
|
||||
else if (ext === '.csv') contentType = 'text/csv; charset=utf-8';
|
||||
else if (ext === '.json') contentType = 'application/json; charset=utf-8';
|
||||
|
||||
return new Response(buffer, {
|
||||
headers: {
|
||||
'Content-Type': contentType,
|
||||
'Content-Length': buffer.length.toString(),
|
||||
'Content-Disposition': `inline; filename="${encodeURIComponent(cleanFile)}"`,
|
||||
'Cache-Control': 'public, max-age=31536000, immutable'
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取文件失败:', error);
|
||||
return Response.json(
|
||||
{ error: '服务器内部错误' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user