const express = require('express'); const router = express.Router(); const multer = require('multer'); const path = require('path'); const fs = require('fs'); const { agentAuth } = require('../middleware/agentAuth'); const { logger } = require('../config/logger'); // 确保上传目录存在 const uploadDir = path.join(__dirname, '../uploads'); if (!fs.existsSync(uploadDir)) { fs.mkdirSync(uploadDir, { recursive: true }); } // 配置multer存储 const storage = multer.diskStorage({ destination: function (req, file, cb) { // 根据文件类型创建不同的子目录 let subDir = 'others'; if (file.fieldname === 'avatar') { subDir = 'avatars'; } else if (file.fieldname === 'qr_code') { subDir = 'qrcodes'; } else if (file.fieldname === 'id_card_front' || file.fieldname === 'id_card_back') { subDir = 'idcards'; } else if (file.fieldname === 'business_license') { subDir = 'licenses'; } const targetDir = path.join(uploadDir, subDir); if (!fs.existsSync(targetDir)) { fs.mkdirSync(targetDir, { recursive: true }); } cb(null, targetDir); }, filename: function (req, file, cb) { // 生成唯一文件名 const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9); const ext = path.extname(file.originalname); cb(null, file.fieldname + '-' + uniqueSuffix + ext); } }); // 文件过滤器 const fileFilter = (req, file, cb) => { // 允许的图片类型 const allowedImageTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/gif', 'image/webp']; if (allowedImageTypes.includes(file.mimetype)) { cb(null, true); } else { cb(new Error('只允许上传图片文件 (JPEG, PNG, GIF, WebP)'), false); } }; // 配置multer const upload = multer({ storage: storage, fileFilter: fileFilter, limits: { fileSize: 5 * 1024 * 1024, // 5MB限制 files: 10 // 最多10个文件 } }); /** * 单文件上传 * POST /api/upload/single */ router.post('/single', agentAuth, upload.single('file'), async (req, res) => { try { if (!req.file) { return res.status(400).json({ success: false, message: '请选择要上传的文件' }); } const file = req.file; const relativePath = path.relative(path.join(__dirname, '../'), file.path).replace(/\\/g, '/'); const fileUrl = `/uploads/${relativePath.split('/').slice(1).join('/')}`; logger.info('文件上传成功', { agentId: req.agent.id, filename: file.filename, originalname: file.originalname, size: file.size, path: fileUrl }); res.json({ success: true, message: '文件上传成功', data: { filename: file.filename, originalname: file.originalname, size: file.size, mimetype: file.mimetype, url: fileUrl, path: fileUrl // 兼容前端可能使用的字段名 } }); } catch (error) { logger.error('文件上传失败', { error: error.message, stack: error.stack, agentId: req.agent?.id }); res.status(500).json({ success: false, message: error.message || '文件上传失败' }); } }); /** * 多文件上传 * POST /api/upload/multiple */ router.post('/multiple', agentAuth, upload.array('files', 10), async (req, res) => { try { if (!req.files || req.files.length === 0) { return res.status(400).json({ success: false, message: '请选择要上传的文件' }); } const files = req.files.map(file => { const relativePath = path.relative(path.join(__dirname, '../'), file.path).replace(/\\/g, '/'); const fileUrl = `/uploads/${relativePath.split('/').slice(1).join('/')}`; return { filename: file.filename, originalname: file.originalname, size: file.size, mimetype: file.mimetype, url: fileUrl, path: fileUrl }; }); logger.info('多文件上传成功', { agentId: req.agent.id, count: files.length, totalSize: req.files.reduce((sum, file) => sum + file.size, 0) }); res.json({ success: true, message: `成功上传${files.length}个文件`, data: files }); } catch (error) { logger.error('多文件上传失败', { error: error.message, stack: error.stack, agentId: req.agent?.id }); res.status(500).json({ success: false, message: error.message || '文件上传失败' }); } }); /** * 头像上传 * POST /api/upload/avatar */ router.post('/avatar', agentAuth, upload.single('avatar'), async (req, res) => { try { if (!req.file) { return res.status(400).json({ success: false, message: '请选择头像文件' }); } const file = req.file; const relativePath = path.relative(path.join(__dirname, '../'), file.path).replace(/\\/g, '/'); const fileUrl = `/uploads/${relativePath.split('/').slice(1).join('/')}`; logger.info('头像上传成功', { agentId: req.agent.id, filename: file.filename, size: file.size }); res.json({ success: true, message: '头像上传成功', data: { filename: file.filename, originalname: file.originalname, size: file.size, url: fileUrl, path: fileUrl } }); } catch (error) { logger.error('头像上传失败', { error: error.message, stack: error.stack, agentId: req.agent?.id }); res.status(500).json({ success: false, message: error.message || '头像上传失败' }); } }); /** * 收款码上传 * POST /api/upload/qrcode */ router.post('/qrcode', agentAuth, upload.single('qr_code'), async (req, res) => { try { if (!req.file) { return res.status(400).json({ success: false, message: '请选择收款码文件' }); } const file = req.file; const relativePath = path.relative(path.join(__dirname, '../'), file.path).replace(/\\/g, '/'); const fileUrl = `/uploads/${relativePath.split('/').slice(1).join('/')}`; logger.info('收款码上传成功', { agentId: req.agent.id, filename: file.filename, size: file.size }); res.json({ success: true, message: '收款码上传成功', data: { filename: file.filename, originalname: file.originalname, size: file.size, url: fileUrl, path: fileUrl } }); } catch (error) { logger.error('收款码上传失败', { error: error.message, stack: error.stack, agentId: req.agent?.id }); res.status(500).json({ success: false, message: error.message || '收款码上传失败' }); } }); /** * 身份证上传 * POST /api/upload/idcard */ router.post('/idcard', agentAuth, upload.fields([ { name: 'id_card_front', maxCount: 1 }, { name: 'id_card_back', maxCount: 1 } ]), async (req, res) => { try { if (!req.files || (!req.files.id_card_front && !req.files.id_card_back)) { return res.status(400).json({ success: false, message: '请选择身份证文件' }); } const result = {}; if (req.files.id_card_front) { const file = req.files.id_card_front[0]; const relativePath = path.relative(path.join(__dirname, '../'), file.path).replace(/\\/g, '/'); const fileUrl = `/uploads/${relativePath.split('/').slice(1).join('/')}`; result.front = { filename: file.filename, originalname: file.originalname, size: file.size, url: fileUrl, path: fileUrl }; } if (req.files.id_card_back) { const file = req.files.id_card_back[0]; const relativePath = path.relative(path.join(__dirname, '../'), file.path).replace(/\\/g, '/'); const fileUrl = `/uploads/${relativePath.split('/').slice(1).join('/')}`; result.back = { filename: file.filename, originalname: file.originalname, size: file.size, url: fileUrl, path: fileUrl }; } logger.info('身份证上传成功', { agentId: req.agent.id, hasFront: !!result.front, hasBack: !!result.back }); res.json({ success: true, message: '身份证上传成功', data: result }); } catch (error) { logger.error('身份证上传失败', { error: error.message, stack: error.stack, agentId: req.agent?.id }); res.status(500).json({ success: false, message: error.message || '身份证上传失败' }); } }); /** * 营业执照上传 * POST /api/upload/license */ router.post('/license', agentAuth, upload.single('business_license'), async (req, res) => { try { if (!req.file) { return res.status(400).json({ success: false, message: '请选择营业执照文件' }); } const file = req.file; const relativePath = path.relative(path.join(__dirname, '../'), file.path).replace(/\\/g, '/'); const fileUrl = `/uploads/${relativePath.split('/').slice(1).join('/')}`; logger.info('营业执照上传成功', { agentId: req.agent.id, filename: file.filename, size: file.size }); res.json({ success: true, message: '营业执照上传成功', data: { filename: file.filename, originalname: file.originalname, size: file.size, url: fileUrl, path: fileUrl } }); } catch (error) { logger.error('营业执照上传失败', { error: error.message, stack: error.stack, agentId: req.agent?.id }); res.status(500).json({ success: false, message: error.message || '营业执照上传失败' }); } }); /** * 删除文件 * DELETE /api/upload/file */ router.delete('/file', agentAuth, async (req, res) => { try { const { path: filePath } = req.body; if (!filePath) { return res.status(400).json({ success: false, message: '请提供文件路径' }); } // 构建完整的文件路径 const fullPath = path.join(__dirname, '../', filePath); // 检查文件是否存在 if (!fs.existsSync(fullPath)) { return res.status(404).json({ success: false, message: '文件不存在' }); } // 删除文件 fs.unlinkSync(fullPath); logger.info('文件删除成功', { agentId: req.agent.id, filePath: filePath }); res.json({ success: true, message: '文件删除成功' }); } catch (error) { logger.error('文件删除失败', { error: error.message, stack: error.stack, agentId: req.agent?.id }); res.status(500).json({ success: false, 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: '文件大小超过限制(最大5MB)' }); } if (error.code === 'LIMIT_FILE_COUNT') { return res.status(400).json({ success: false, message: '文件数量超过限制(最多10个)' }); } if (error.code === 'LIMIT_UNEXPECTED_FILE') { return res.status(400).json({ success: false, message: '意外的文件字段' }); } } res.status(500).json({ success: false, message: error.message || '文件上传失败' }); }); module.exports = router;