const express = require('express'); const bcrypt = require('bcryptjs'); const jwt = require('jsonwebtoken'); const { getDB } = require('../database'); const router = express.Router(); const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key'; /** * @swagger * tags: * name: Authentication * description: 用户认证API */ /** * @swagger * components: * schemas: * LoginCredentials: * type: object * required: * - username * - password * properties: * username: * type: string * description: 用户名或手机号 * password: * type: string * description: 密码 * RegisterRequest: * type: object * required: * - username * - phone * - password * - registrationCode * - city * - district_id * - captchaId * - captchaText * - smsCode * properties: * username: * type: string * description: 用户名 * phone: * type: string * description: 手机号 * password: * type: string * description: 密码 * registrationCode: * type: string * description: 注册激活码 * city: * type: string * description: 城市 * district_id: * type: string * description: 区域ID * captchaId: * type: string * description: 图形验证码ID * captchaText: * type: string * description: 图形验证码文本 * smsCode: * type: string * description: 短信验证码 * role: * type: string * description: 用户角色 * default: user */ /** * @swagger * /auth/register: * post: * summary: 用户注册 * description: 需要提供有效的激活码才能注册 * tags: [Authentication] * requestBody: * required: true * content: * application/json: * schema: * $ref: '#/components/schemas/RegisterRequest' * responses: * 201: * description: 用户注册成功 * content: * application/json: * schema: * type: object * properties: * success: * type: boolean * message: * type: string * token: * type: string * description: JWT认证令牌 * user: * type: object * properties: * id: * type: integer * username: * type: string * role: * type: string * 400: * description: 请求参数错误 * 500: * description: 服务器错误 */ router.post('/register', async (req, res) => { try { const db = getDB(); await db.query('START TRANSACTION'); const { username, phone, password, registrationCode, city, district_id: district, captchaId, captchaText, smsCode, // 短信验证码 role = 'user' } = req.body; if (!username || !phone || !password || !registrationCode || !city || !district) { return res.status(400).json({ success: false, message: '用户名、手机号、密码、激活码、城市和区域不能为空' }); } if (!captchaId || !captchaText) { return res.status(400).json({ success: false, message: '图形验证码不能为空' }); } if (!smsCode) { return res.status(400).json({ success: false, message: '短信验证码不能为空' }); } // 注意:图形验证码已在前端通过 /captcha/verify 接口验证过,这里不再重复验证 // 验证短信验证码 const smsAPI = require('./sms'); const smsValid = smsAPI.verifySMSCode(phone, smsCode); if (!smsValid) { return res.status(400).json({ success: false, message: '短信验证码错误或已过期' }); } // 验证手机号格式 const phoneRegex = /^1[3-9]\d{9}$/; if (!phoneRegex.test(phone)) { return res.status(400).json({ success: false, message: '手机号格式不正确' }); } // 验证激活码 const [registrationCodes] = await db.execute( 'SELECT id, is_used, expires_at, agent_id FROM registration_codes WHERE code = ?', [registrationCode] ); if (registrationCodes.length === 0) { return res.status(400).json({ success: false, message: '激活码不存在' }); } const regCode = registrationCodes[0]; // 检查激活码是否已使用 if (regCode.is_used) { return res.status(400).json({ success: false, message: '激活码已被使用' }); } // 检查激活码是否过期 if (new Date() > new Date(regCode.expires_at)) { return res.status(400).json({ success: false, message: '激活码已过期' }); } // 检查用户是否已存在 const [existingUsers] = await db.execute( 'SELECT id FROM users WHERE username = ? OR phone = ?', [username, phone] ); if (existingUsers.length > 0) { return res.status(400).json({ success: false, message: '用户名或手机号已存在' }); } // 加密密码 const hashedPassword = await bcrypt.hash(password, 10); // 创建用户(待审核状态,可以进入系统但匹配需审核) const [result] = await db.execute( 'INSERT INTO users (username, phone, password, role, points, audit_status, city, district_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?)', [username, phone, hashedPassword, role, 0, 'pending', city, district] ); const userId = result.insertId; // 用户余额已在创建用户时设置为默认值0.00,无需额外操作 // 标记激活码为已使用 await db.execute( 'UPDATE registration_codes SET is_used = TRUE, used_at = NOW(), used_by_user_id = ? WHERE id = ?', [userId, regCode.id] ); // 如果是代理邀请码,建立代理关系 if (regCode.agent_id) { // 验证agent_id是否存在于regional_agents表中 const [agentExists] = await db.execute( 'SELECT id FROM regional_agents WHERE id = ?', [regCode.agent_id] ); if (agentExists.length > 0) { await db.execute( 'INSERT INTO agent_merchants (agent_id, merchant_id, created_at) VALUES (?, ?, NOW())', [regCode.agent_id, userId] ); } } else { // 如果不是代理邀请码,根据地区自动关联代理 const [agents] = await db.execute( 'SELECT ra.id FROM users u INNER JOIN regional_agents ra ON u.id = ra.user_id WHERE ra.region_id = ? AND ra.status = "active" ORDER BY ra.created_at ASC LIMIT 1', [district] ); if (agents.length > 0) { await db.execute( 'INSERT INTO agent_merchants (agent_id, merchant_id, created_at) VALUES (?, ?, NOW())', [agents[0].id, userId] ); } } await db.query('COMMIT'); // 生成JWT token const token = jwt.sign( { userId: userId, username, role }, JWT_SECRET, { expiresIn: '24h' } ); res.status(201).json({ success: true, message: '注册成功', token, user: { id: userId, username, phone, role, points: 0, audit_status: 'pending', city, district } }); } catch (error) { try { await getDB().query('ROLLBACK'); } catch (rollbackError) { console.error('回滚错误:', rollbackError); } console.error('注册错误详情:', error); console.error('错误堆栈:', error.stack); res.status(500).json({ success: false, message: '注册失败', error: process.env.NODE_ENV === 'development' ? error.message : undefined }); } }); /** * @swagger * /auth/login: * post: * summary: 用户登录 * tags: [Authentication] * requestBody: * required: true * content: * application/json: * schema: * $ref: '#/components/schemas/LoginCredentials' * responses: * 200: * description: 登录成功 * content: * application/json: * schema: * type: object * properties: * success: * type: boolean * message: * type: string * token: * type: string * description: JWT认证令牌 * user: * type: object * properties: * id: * type: integer * username: * type: string * role: * type: string * avatar: * type: string * points: * type: integer * 400: * description: 请求参数错误 * 401: * description: 用户名或密码错误 * 403: * description: 账户审核未通过 * 500: * description: 服务器错误 */ router.post('/login', async (req, res) => { try { const db = getDB(); const { username, password, captchaId, captchaText } = req.body; if (!username || !password) { return res.status(400).json({ success: false, message: '用户名和密码不能为空' }); } if (!captchaId || !captchaText) { return res.status(400).json({ success: false, message: '验证码不能为空' }); } // 获取存储的验证码 const storedCaptcha = global.captchaStore.get(captchaId); console.log(storedCaptcha); if (!storedCaptcha) { return res.status(400).json({ success: false, message: '验证码不存在或已过期' }); } // 检查是否过期 if (Date.now() > storedCaptcha.expires) { global.captchaStore.delete(captchaId); return res.status(400).json({ success: false, message: '验证码已过期' }); } // 验证验证码(不区分大小写) const isValid = storedCaptcha.text === captchaText.toLowerCase(); // 删除已验证的验证码 global.captchaStore.delete(captchaId); if (!isValid) { return res.status(400).json({ success: false, message: '验证码错误' }); } // 注意:验证码已在前端通过 /captcha/verify 接口验证过,这里不再重复验证 // 查找用户 console.log('登录尝试 - 用户名:', username); const [users] = await db.execute( 'SELECT * FROM users WHERE username = ?', [username] ); console.log('查找到的用户数量:', users.length); if (users.length === 0) { console.log('用户不存在:', username); return res.status(401).json({ success: false, message: '用户名或密码错误' }); } const user = users[0]; console.log('找到用户:', user.username, '密码长度:', user.password ? user.password.length : 'null'); // 验证密码 console.log('验证密码 - 输入密码:', password, '数据库密码前10位:', user.password ? user.password.substring(0, 10) : 'null'); const isValidPassword = await bcrypt.compare(password, user.password); console.log('密码验证结果:', isValidPassword); if (!isValidPassword) { console.log('密码验证失败'); return res.status(401).json({ success: false, message: '用户名或密码错误' }); } // 检查用户审核状态(管理员除外,只阻止被拒绝的用户) if (user.role !== 'admin' && user.audit_status === 'rejected') { return res.status(403).json({ success: false, message: '您的账户审核未通过,请联系管理员' }); } // 待审核用户可以正常登录使用系统,但匹配功能会有限制 // 生成JWT token const token = jwt.sign( { userId: user.id, username: user.username, role: user.role }, JWT_SECRET, { expiresIn: '24h' } ); res.json({ success: true, message: '登录成功', token, user: { id: user.id, username: user.username, role: user.role, avatar: user.avatar, points: user.points } }); } catch (error) { console.error('登录错误:', error); res.status(500).json({ success: false, message: '登录失败' }); } }); // 验证token中间件 const authenticateToken = (req, res, next) => { const authHeader = req.headers['authorization']; const token = authHeader && authHeader.split(' ')[1]; if (!token) { return res.status(401).json({ success: false, message: '访问令牌缺失' }); } jwt.verify(token, JWT_SECRET, (err, user) => { if (err) { return res.status(403).json({ success: false, message: '访问令牌无效' }); } req.user = user; next(); }); }; // 获取当前用户信息 router.get('/me', authenticateToken, async (req, res) => { try { const db = getDB(); const [users] = await db.execute( 'SELECT id, username, role, avatar, points, created_at FROM users WHERE id = ?', [req.user.userId] ); if (users.length === 0) { return res.status(404).json({ success: false, message: '用户不存在' }); } res.json({ success: true, user: users[0] }); } catch (error) { console.error('获取用户信息错误:', error); res.status(500).json({ success: false, message: '获取用户信息失败' }); } }); // 修改密码 router.put('/change-password', authenticateToken, async (req, res) => { try { const db = getDB(); const { currentPassword, newPassword } = req.body; if (!currentPassword || !newPassword) { return res.status(400).json({ success: false, message: '旧密码和新密码不能为空' }); } // 获取用户当前密码 const [users] = await db.execute( 'SELECT password FROM users WHERE id = ?', [req.user.userId] ); if (users.length === 0) { return res.status(404).json({ success: false, message: '用户不存在' }); } // 验证旧密码 const isValidPassword = await bcrypt.compare(currentPassword, users[0].password); if (!isValidPassword) { return res.status(400).json({ success: false, message: '旧密码错误' }); } // 加密新密码 const hashedNewPassword = await bcrypt.hash(newPassword, 10); // 更新密码 await db.execute( 'UPDATE users SET password = ? WHERE id = ?', [hashedNewPassword, req.user.userId] ); res.json({ success: true, message: '密码修改成功' }); } catch (error) { console.error('修改密码错误:', error); res.status(500).json({ success: false, message: '修改密码失败' }); } }); module.exports = router; module.exports.authenticateToken = authenticateToken;