You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

92 lines
2.9 KiB

import multer from 'multer';
import fs from 'node:fs';
import path from 'node:path';
import { callNodeListener } from 'h3';
import { R } from '#server/utils/response';
// 类型定义
interface IFile {
name: string;
url: string; // 前端可直接访问的 URL
path: string; // 服务器存储路径
mimeType: string;
size: number;
}
export default defineWrappedResponseHandler(async (event) => {
try {
// 存储目录
const uploadDir = path.join(process.cwd(), 'public/assets');
// 自动创建目录
if (!fs.existsSync(uploadDir)) {
fs.mkdirSync(uploadDir, { recursive: true });
}
// 配置存储
const storage = multer.diskStorage({
destination: uploadDir,
filename: (req, file, cb) => {
// 生成唯一文件名:时间戳 + 原始文件名(安全处理)
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1e9);
// 获取文件后缀
const ext = path.extname(file.originalname).toLowerCase();
// 干净的文件名(只保留字母数字)
const baseName = path.basename(file.originalname, ext).replace(/[^a-z0-9]/gi, '-');
// 最终文件名(带后缀)
const filename = `${uniqueSuffix}-${baseName}${ext}`;
cb(null, filename);
},
});
// 上传配置
const upload = multer({
storage,
limits: {
fileSize: 5 * 1024 * 1024, // 5MB 限制
},
fileFilter: (req, file, cb) => {
const allowedTypes = ['image/png', 'image/jpeg', 'image/jpg', 'image/webp'];
if (allowedTypes.includes(file.mimetype)) {
cb(null, true);
} else {
cb(new Error('只支持 PNG/JPG/WebP 格式图片'));
}
},
});
// 执行上传(最多 10 个文件)
await callNodeListener(
// @ts-expect-error Nuxt 类型兼容
upload.array('file', 10),
event.node.req,
event.node.res
);
// 获取上传后的文件
// @ts-expect-error
const uploadedFiles = event.node.req.files || [];
if (!Array.isArray(uploadedFiles) || uploadedFiles.length === 0) {
throw createError({ statusCode: 400, statusMessage: '请选择要上传的图片' });
}
// 格式化返回数据(与 `public` 静态中间件路径一致:`/public/assets/...`)
const result: IFile[] = uploadedFiles.map((file: { originalname: string; filename: string; path: string; mimetype: string; size: number }) => ({
name: file.originalname,
url: `/public/assets/${file.filename}`,
path: file.path,
mimeType: file.mimetype,
size: file.size,
}));
return R.success({ files: result });
} catch (err: unknown) {
console.error('上传失败:', err);
if (err && typeof err === 'object' && 'statusCode' in err) {
throw err;
}
const msg = err instanceof Error ? err.message : '上传失败';
throw createError({ statusCode: 400, statusMessage: msg });
}
});