const express = require('express'); const router = express.Router(); const multer = require('multer'); const path = require('path'); const fs = require('fs'); const { auth } = require('../middleware/auth'); const User = require('../models/User'); const { downloadAndSaveAvatar } = require('../services/avatarService'); const { compressAvatar } = require('../services/imageService'); const uploadDir = path.join(__dirname, '../../public/uploads/avatars'); if (!fs.existsSync(uploadDir)) { fs.mkdirSync(uploadDir, { recursive: true }); } const storage = multer.diskStorage({ destination: (req, file, cb) => { cb(null, uploadDir); }, filename: (req, file, cb) => { const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9); const ext = path.extname(file.originalname || '') || '.jpg'; cb(null, 'avatar-' + uniqueSuffix + ext); } }); const upload = multer({ storage, limits: { fileSize: 5 * 1024 * 1024 }, fileFilter: (req, file, cb) => { const allowedTypes = /jpeg|jpg|png|gif|webp/; const extname = allowedTypes.test(path.extname(file.originalname || '').toLowerCase()); const mimetype = allowedTypes.test(file.mimetype || ''); if (extname || mimetype) { cb(null, true); } else { cb(new Error('只允许上传图片文件')); } } }); router.get('/profile', auth, async (req, res, next) => { try { const user = await User.findById(req.user._id); if (!user) { return res.status(404).json({ success: false, error: '用户不存在' }); } res.json({ success: true, data: { userId: user.userId || '', nickname: user.nickname || '', avatarUrl: user.avatarUrl || '', status: user.status || 'active', isVip: user.isVip || false, vipExpireAt: user.vipExpireAt || null, ocrCount: user.ocrCount || 10, ocrCountTotal: user.ocrCountTotal || 10, platformLimit: user.platformLimit || 15, platformCount: user.platformCount || 0, lastLoginAt: user.lastLoginAt || null } }); } catch (error) { next(error); } }); router.put('/profile', auth, async (req, res, next) => { try { const allowedUpdates = ['nickname', 'avatarUrl', 'phoneNumber', 'profile']; const updates = {}; Object.keys(req.body).forEach(key => { if (allowedUpdates.includes(key)) { updates[key] = req.body[key]; } }); if (updates.avatarUrl && updates.avatarUrl.startsWith('http')) { const savedAvatarUrl = await downloadAndSaveAvatar(updates.avatarUrl); if (savedAvatarUrl) { updates.avatarUrl = savedAvatarUrl; } } const user = await User.findByIdAndUpdate( req.user._id, updates, { new: true, runValidators: true } ).select('-__v'); res.json({ success: true, data: user }); } catch (error) { next(error); } }); router.post('/avatar', auth, upload.single('avatar'), async (req, res, next) => { try { if (!req.file) { console.error('[头像上传] 未收到文件,req.body keys:', Object.keys(req.body || {})); return res.status(400).json({ success: false, error: '没有上传文件' }); } console.log('[头像上传] 收到文件:', req.file.originalname, '大小:', req.file.size, '类型:', req.file.mimetype); console.log('[头像上传] 临时路径:', req.file.path); const originalPath = req.file.path; try { await compressAvatar(originalPath); console.log('[头像上传] 压缩完成'); } catch (compressError) { console.error('[头像上传] 压缩失败:', compressError.message); console.error('[头像上传] 压缩错误栈:', compressError.stack); return res.status(500).json({ success: false, error: '图片处理失败,请重试' }); } let finalFilename = req.file.filename; const ext = path.extname(finalFilename).toLowerCase(); if (ext !== '.jpg' && ext !== '.jpeg') { const newFilename = finalFilename.replace(ext, '.jpg'); const newPath = path.join(uploadDir, newFilename); fs.renameSync(originalPath, newPath); finalFilename = newFilename; console.log('[头像上传] 文件名从', req.file.filename, '改为', newFilename); } const avatarUrl = `${process.env.SERVER_URL || 'https://api-miniapp.dxz99wyr.cn'}/uploads/avatars/${finalFilename}`; const user = await User.findByIdAndUpdate( req.user._id, { avatarUrl }, { new: true } ); res.json({ success: true, data: { avatarUrl: user.avatarUrl, url: avatarUrl } }); } catch (error) { next(error); } }); router.get('/stats', auth, async (req, res, next) => { try { const Equity = require('../models/Equity'); const Trade = require('../models/Trade'); const [totalEquities, activeEquities, totalTrades, sellingTrades] = await Promise.all([ Equity.countDocuments({ owner: req.user._id }), Equity.countDocuments({ owner: req.user._id, status: 'active' }), Trade.countDocuments({ $or: [{ seller: req.user._id }, { buyer: req.user._id }] }), Trade.countDocuments({ seller: req.user._id, status: 'pending' }) ]); res.json({ success: true, data: { totalEquities, activeEquities, totalTrades, sellingTrades } }); } catch (error) { next(error); } }); router.get('/growth-stats', auth, async (req, res, next) => { try { const thirtyDaysAgo = new Date(); thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30); thirtyDaysAgo.setHours(0, 0, 0, 0); const dailyCounts = await User.aggregate([ { $match: { createdAt: { $gte: thirtyDaysAgo } } }, { $group: { _id: { $dateToString: { format: '%Y-%m-%d', date: '$createdAt', timezone: 'Asia/Shanghai' } }, count: { $sum: 1 } } }, { $sort: { _id: 1 } } ]); const countMap = {}; dailyCounts.forEach(item => { countMap[item._id] = item.count; }); const list = []; let cumulative = 0; for (let i = 0; i <= 30; i++) { const d = new Date(thirtyDaysAgo); d.setDate(d.getDate() + i); const dateStr = d.toISOString().split('T')[0]; const month = d.getMonth() + 1; const day = d.getDate(); cumulative += countMap[dateStr] || 0; list.push({ date: `${month}月${day}日`, userCount: cumulative }); } res.json({ success: true, data: { list } }); } catch (error) { next(error); } }); module.exports = router;