|
|
/**
|
|
|
* 文件上传中间件
|
|
|
*
|
|
|
* 功能:处理图片上传(如菜品图片、用户头像)
|
|
|
* 使用multer库实现文件上传
|
|
|
*/
|
|
|
|
|
|
const multer = require('multer');
|
|
|
const path = require('path');
|
|
|
const fs = require('fs');
|
|
|
|
|
|
// 确保上传目录存在
|
|
|
const uploadDir = 'uploads/images';
|
|
|
if (!fs.existsSync(uploadDir)) {
|
|
|
fs.mkdirSync(uploadDir, { recursive: true });
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 配置文件存储
|
|
|
* destination: 文件保存位置
|
|
|
* filename: 文件名生成规则
|
|
|
*/
|
|
|
const storage = multer.diskStorage({
|
|
|
// 设置上传文件的存储目录
|
|
|
destination: function (req, file, cb) {
|
|
|
cb(null, uploadDir); // null表示没有错误
|
|
|
},
|
|
|
|
|
|
// 设置上传文件的文件名
|
|
|
filename: function (req, file, cb) {
|
|
|
// 生成唯一文件名:时间戳-随机数.原扩展名
|
|
|
// 例如:1704873600000-123456.jpg
|
|
|
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
|
|
|
const ext = path.extname(file.originalname); // 获取文件扩展名
|
|
|
cb(null, uniqueSuffix + ext);
|
|
|
}
|
|
|
});
|
|
|
|
|
|
/**
|
|
|
* 文件过滤器
|
|
|
* 只允许上传图片文件
|
|
|
*/
|
|
|
const fileFilter = (req, file, cb) => {
|
|
|
// 允许的图片MIME类型
|
|
|
const allowedTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/gif', 'image/webp'];
|
|
|
|
|
|
if (allowedTypes.includes(file.mimetype)) {
|
|
|
cb(null, true); // 接受文件
|
|
|
} else {
|
|
|
cb(new Error('只能上传图片文件(jpg, jpeg, png, gif, webp)'), false);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* 创建multer上传实例
|
|
|
*/
|
|
|
const upload = multer({
|
|
|
storage: storage,
|
|
|
fileFilter: fileFilter,
|
|
|
limits: {
|
|
|
fileSize: 5 * 1024 * 1024 // 限制文件大小为5MB
|
|
|
}
|
|
|
});
|
|
|
|
|
|
/**
|
|
|
* 单个图片上传中间件
|
|
|
*
|
|
|
* 使用示例:
|
|
|
* router.post('/dishes', uploadSingle, createDish);
|
|
|
*
|
|
|
* 前端上传示例(FormData):
|
|
|
* const formData = new FormData();
|
|
|
* formData.append('image', file);
|
|
|
* axios.post('/dishes', formData);
|
|
|
*/
|
|
|
const uploadSingle = upload.single('image'); // 'image'是表单字段名
|
|
|
|
|
|
/**
|
|
|
* 多个图片上传中间件
|
|
|
*
|
|
|
* 使用示例:
|
|
|
* router.post('/gallery', uploadMultiple, uploadGallery);
|
|
|
*
|
|
|
* 前端上传示例:
|
|
|
* const formData = new FormData();
|
|
|
* files.forEach(file => {
|
|
|
* formData.append('images', file);
|
|
|
* });
|
|
|
*/
|
|
|
const uploadMultiple = upload.array('images', 10); // 最多10张图片
|
|
|
|
|
|
/**
|
|
|
* 删除上传的文件
|
|
|
* @param {string} filename - 文件名
|
|
|
*
|
|
|
* 说明:当删除数据库记录时,也要删除对应的图片文件
|
|
|
*/
|
|
|
const deleteUploadedFile = (filename) => {
|
|
|
try {
|
|
|
if (!filename) return;
|
|
|
|
|
|
const filePath = path.join(uploadDir, filename);
|
|
|
|
|
|
// 检查文件是否存在
|
|
|
if (fs.existsSync(filePath)) {
|
|
|
fs.unlinkSync(filePath); // 同步删除文件
|
|
|
console.log(`✅ 已删除文件: ${filename}`);
|
|
|
}
|
|
|
} catch (error) {
|
|
|
console.error('❌ 删除文件失败:', error.message);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* 获取文件的访问URL
|
|
|
* @param {string} filename - 文件名
|
|
|
* @returns {string} 文件URL
|
|
|
*
|
|
|
* 例如:/uploads/images/1704873600000-123456.jpg
|
|
|
*/
|
|
|
const getFileUrl = (filename) => {
|
|
|
if (!filename) return null;
|
|
|
return `/uploads/images/${filename}`;
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* 从URL中提取文件名
|
|
|
* @param {string} url - 文件URL
|
|
|
* @returns {string} 文件名
|
|
|
*/
|
|
|
const getFilenameFromUrl = (url) => {
|
|
|
if (!url) return null;
|
|
|
return path.basename(url);
|
|
|
};
|
|
|
|
|
|
// 导出所有函数
|
|
|
module.exports = {
|
|
|
uploadSingle,
|
|
|
uploadMultiple,
|
|
|
deleteUploadedFile,
|
|
|
getFileUrl,
|
|
|
getFilenameFromUrl
|
|
|
};
|