Files
2025-09-10 18:09:38 +08:00

418 lines
12 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const express = require('express');
const router = express.Router();
const multer = require('multer');
const { authenticateToken } = require('./auth');
const { auth } = require('../middleware/auth');
const minioService = require('../services/minioService');
const { initializeBuckets } = require('../config/minio');
// 初始化MinIO存储桶
// initializeBuckets().catch(console.error);
/**
* @swagger
* tags:
* name: Upload
* description: 文件上传API
*/
// 配置multer内存存储用于MinIO上传
const storage = multer.memoryStorage();
// 文件过滤器 - 支持图片和视频
const fileFilter = (req, file, cb) => {
// 允许图片和视频文件
if (file.mimetype.startsWith('image/') || file.mimetype.startsWith('video/')) {
cb(null, true);
} else {
cb(new Error('只能上传图片或视频文件'), false);
}
};
// 单文件上传配置
const upload = multer({
storage: storage,
fileFilter: fileFilter,
limits: {
fileSize: 5 * 1024 * 1024, // 5MB
files: 1 // 一次只能上传一个文件
}
});
// 多文件上传配置
const multiUpload = multer({
storage: storage,
fileFilter: fileFilter,
limits: {
fileSize: 10 * 1024 * 1024, // 10MB (视频文件更大)
files: 10 // 最多10个文件
}
});
/**
* @swagger
* /upload/image:
* post:
* summary: 上传图片
* tags: [Upload]
* security:
* - bearerAuth: []
* requestBody:
* required: true
* content:
* multipart/form-data:
* schema:
* type: object
* properties:
* file:
* type: string
* format: binary
* description: 要上传的图片文件
* type:
* type: string
* enum: [avatar, product, document]
* default: document
* description: 上传文件类型
* responses:
* 200:
* description: 图片上传成功
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* example: true
* url:
* type: string
* description: 上传后的文件URL
* filename:
* type: string
* description: 上传后的文件名
* 400:
* description: 请求参数错误
* 401:
* description: 未授权
* 500:
* description: 服务器错误
*/
router.post('/image', authenticateToken, (req, res) => {
upload.single('file')(req, res, async (err) => {
if (err instanceof multer.MulterError) {
if (err.code === 'LIMIT_FILE_SIZE') {
return res.status(400).json({
success: false,
message: '文件大小不能超过 5MB'
});
}
if (err.code === 'LIMIT_FILE_COUNT') {
return res.status(400).json({
success: false,
message: '一次只能上传一个文件'
});
}
return res.status(400).json({
success: false,
message: '文件上传失败:' + err.message
});
} else if (err) {
return res.status(400).json({
success: false,
message: err.message
});
}
if (!req.file) {
return res.status(400).json({
success: false,
message: '请选择要上传的文件'
});
}
try {
// 使用MinIO服务上传文件
const type = req.body.type || 'document';
const result = await minioService.uploadFile(
req.file.buffer,
req.file.originalname,
req.file.mimetype,
type
);
res.json({
success: true,
message: '文件上传成功',
data: result.data
});
} catch (error) {
console.error('文件上传到MinIO失败:', error);
res.status(500).json({
success: false,
message: error.message || '文件上传失败'
});
}
});
});
/**
* @swagger
* /upload:
* post:
* summary: 多文件上传接口 (支持MediaUpload组件)
* tags: [Upload]
* security:
* - bearerAuth: []
* requestBody:
* required: true
* content:
* multipart/form-data:
* schema:
* type: object
* properties:
* files:
* type: array
* items:
* type: string
* format: binary
* description: 要上传的文件列表
* type:
* type: string
* enum: [avatar, product, document]
* default: document
* description: 上传文件类型
* responses:
* 200:
* description: 文件上传成功
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* example: true
* message:
* type: string
* example: 文件上传成功
* data:
* type: array
* items:
* type: object
* properties:
* filename:
* type: string
* originalname:
* type: string
* mimetype:
* type: string
* size:
* type: integer
* path:
* type: string
* url:
* type: string
* 400:
* description: 请求参数错误
* 401:
* description: 未授权
* 500:
* description: 服务器错误
* @access Private
*/
router.post('/', authenticateToken, (req, res) => {
multiUpload.array('file', 10)(req, res, async (err) => {
if (err instanceof multer.MulterError) {
if (err.code === 'LIMIT_FILE_SIZE') {
return res.status(400).json({
success: false,
message: '文件大小不能超过 10MB'
});
}
if (err.code === 'LIMIT_FILE_COUNT') {
return res.status(400).json({
success: false,
message: '一次最多只能上传10个文件'
});
}
return res.status(400).json({
success: false,
message: '文件上传失败:' + err.message
});
} else if (err) {
return res.status(400).json({
success: false,
message: err.message
});
}
if (!req.files || req.files.length === 0) {
return res.status(400).json({
success: false,
message: '请选择要上传的文件'
});
}
try {
// 使用MinIO服务上传多个文件
const type = req.body.type || 'document';
const files = req.files.map(file => ({
buffer: file.buffer,
originalName: file.originalname,
mimeType: file.mimetype
}));
const result = await minioService.uploadMultipleFiles(files, type);
// 如果只上传了一个文件,返回单文件格式以保持兼容性
if (result.data.files.length === 1) {
result.data.files.forEach(element => {
element.path = '/' + element.path
});
res.json({
success: true,
message: '文件上传成功',
data: {
...result.data.files[0],
urls: result.data.urls // 同时提供urls数组格式
}
});
} else {
// 多文件返回数组格式
res.json({
success: true,
message: `成功上传${result.data.files.length}个文件`,
data: result.data
});
}
} catch (error) {
console.error('文件上传到MinIO失败:', error);
res.status(500).json({
success: false,
message: error.message || '文件上传失败'
});
}
});
});
/**
* @swagger
* /upload/single:
* post:
* summary: 单文件上传接口(兼容性接口)
* tags: [Upload]
* security:
* - bearerAuth: []
* requestBody:
* required: true
* content:
* multipart/form-data:
* schema:
* type: object
* properties:
* file:
* type: string
* format: binary
* description: 要上传的文件
* type:
* type: string
* enum: [avatar, product, document]
* default: document
* description: 上传文件类型
* responses:
* 200:
* description: 文件上传成功
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* example: true
* message:
* type: string
* example: 文件上传成功
* url:
* type: string
* description: 上传后的文件URL
* filename:
* type: string
* description: 上传后的文件名
* originalname:
* type: string
* description: 原始文件名
* size:
* type: integer
* description: 文件大小
* 400:
* description: 请求参数错误
* 401:
* description: 未授权
* 500:
* description: 服务器错误
*/
router.post('/single', auth, (req, res) => {
upload.single('file')(req, res, async (err) => {
if (err instanceof multer.MulterError) {
return res.status(400).json({
success: false,
message: '文件上传失败:' + err.message
});
} else if (err) {
return res.status(400).json({
success: false,
message: err.message
});
}
if (!req.file) {
return res.status(400).json({ success: false, message: '没有上传文件' });
}
try {
// 使用MinIO服务上传文件
const type = req.body.type || 'document';
const result = await minioService.uploadFile(
req.file.buffer,
req.file.originalname,
req.file.mimetype,
type
);
res.json({
success: true,
message: '文件上传成功',
url: result.data.url,
filename: result.data.filename,
originalname: result.data.originalname,
size: result.data.size
});
} catch (error) {
console.error('文件上传到MinIO失败:', error);
res.status(500).json({
success: false,
message: error.message || '文件上传失败'
});
}
});
});
// 错误处理中间件
router.use((error, req, res, next) => {
if (error instanceof multer.MulterError) {
if (error.code === 'LIMIT_FILE_SIZE') {
return res.status(400).json({ success: false, message: '文件大小不能超过10MB' });
}
if (error.code === 'LIMIT_FILE_COUNT') {
return res.status(400).json({ success: false, message: '一次最多只能上传10个文件' });
}
}
if (error.message === '只能上传图片或视频文件') {
return res.status(400).json({ success: false, message: error.message });
}
res.status(500).json({ success: false, message: '上传失败' });
});
module.exports = router;