345 lines
12 KiB
JavaScript
345 lines
12 KiB
JavaScript
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';
|
||
router.post('/register', async (req, res) => {
|
||
try {
|
||
const db = getDB();
|
||
await db.query('START TRANSACTION');
|
||
|
||
const {
|
||
username,
|
||
phone,
|
||
password,
|
||
city,
|
||
district_id: district,
|
||
province,
|
||
inviter = null,
|
||
captchaId,
|
||
captchaText,
|
||
smsCode, // 短信验证码
|
||
role = 'user'
|
||
} = req.body;
|
||
|
||
if (!username || !phone || !password || !city || !district || !province) {
|
||
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: '验证码错误'
|
||
});
|
||
}
|
||
if (!smsCode) {
|
||
return res.status(400).json({success: false, message: '短信验证码不能为空'});
|
||
}
|
||
// 验证短信验证码
|
||
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 [existingUsers] = await db.execute(
|
||
'SELECT id, payment_status 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, payment_status, province, inviter) VALUES (?, ?, ?, ?, ?, ?, ?, ?, "unpaid", ?, ?)',
|
||
[username, phone, hashedPassword, role, 0, 'pending', city, district, province, inviter]
|
||
);
|
||
|
||
const userId = result.insertId;
|
||
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,
|
||
paymentStatus: 'unpaid'
|
||
},
|
||
needPayment: true
|
||
});
|
||
} 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
|
||
});
|
||
}
|
||
});
|
||
|
||
|
||
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.payment_status === 'unpaid') {
|
||
const token = jwt.sign(
|
||
{userId: user.id, username: user.username, role: user.role},
|
||
JWT_SECRET,
|
||
{expiresIn: '5m'}
|
||
);
|
||
return res.status(200).json({
|
||
success: false,
|
||
message: '您的账户尚未激活,请完成支付后再登录',
|
||
needPayment: true,
|
||
user: user[0],
|
||
token
|
||
});
|
||
}
|
||
|
||
// 检查用户审核状态(管理员除外,只阻止被拒绝的用户)
|
||
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'}
|
||
);
|
||
const [is_distribution] = await db.execute(`
|
||
SELECT *
|
||
FROM distribution
|
||
WHERE user_id = ?`, [user.id]);
|
||
user.distribution = is_distribution.length > 0 ? true : false;
|
||
res.json({
|
||
success: true,
|
||
message: '登录成功',
|
||
token,
|
||
user
|
||
});
|
||
} 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; |