Spaces:
Sleeping
Sleeping
| const bcrypt = require('bcryptjs'); | |
| const jwt = require('jsonwebtoken'); | |
| const { runQuery, getQuery, allQuery } = require('../database/database'); | |
| const JWT_SECRET = process.env.JWT_SECRET || 'your-super-secret-jwt-key-change-this-in-production'; | |
| const JWT_EXPIRES_IN = '7d'; // Token有效期7天 | |
| class User { | |
| // 创建新用户 | |
| static async create(email, password) { | |
| try { | |
| // 检查用户是否已存在 | |
| const existingUser = await this.findByEmail(email); | |
| if (existingUser) { | |
| throw new Error('该邮箱已被注册'); | |
| } | |
| // 加密密码 | |
| const saltRounds = 12; | |
| const passwordHash = await bcrypt.hash(password, saltRounds); | |
| // 插入用户 | |
| const result = await runQuery( | |
| `INSERT INTO users (email, password_hash) VALUES (?, ?)`, | |
| [email, passwordHash] | |
| ); | |
| return await this.findById(result.id); | |
| } catch (error) { | |
| throw error; | |
| } | |
| } | |
| // 根据ID查找用户 | |
| static async findById(id) { | |
| try { | |
| const user = await getQuery( | |
| `SELECT id, email, is_verified, is_activated, activation_expires, created_at, updated_at FROM users WHERE id = ?`, | |
| [id] | |
| ); | |
| return user; | |
| } catch (error) { | |
| throw error; | |
| } | |
| } | |
| // 根据邮箱查找用户 | |
| static async findByEmail(email) { | |
| try { | |
| const user = await getQuery( | |
| `SELECT id, email, password_hash, is_verified, verification_code, verification_expires, | |
| reset_token, reset_expires, created_at, updated_at FROM users WHERE email = ?`, | |
| [email] | |
| ); | |
| return user; | |
| } catch (error) { | |
| throw error; | |
| } | |
| } | |
| // 验证密码 | |
| static async verifyPassword(plainPassword, hashedPassword) { | |
| try { | |
| return await bcrypt.compare(plainPassword, hashedPassword); | |
| } catch (error) { | |
| throw error; | |
| } | |
| } | |
| // 生成JWT Token | |
| static generateToken(userId) { | |
| return jwt.sign( | |
| { userId }, | |
| JWT_SECRET, | |
| { expiresIn: JWT_EXPIRES_IN } | |
| ); | |
| } | |
| // 验证JWT Token | |
| static verifyToken(token) { | |
| try { | |
| return jwt.verify(token, JWT_SECRET); | |
| } catch (error) { | |
| throw new Error('Invalid token'); | |
| } | |
| } | |
| // 设置邮箱验证码 | |
| static async setVerificationCode(userId, code) { | |
| try { | |
| const expiresAt = Date.now() + 10 * 60 * 1000; // 10分钟后过期 | |
| await runQuery( | |
| `UPDATE users SET verification_code = ?, verification_expires = ? WHERE id = ?`, | |
| [code, Math.floor(expiresAt / 1000), userId] | |
| ); | |
| } catch (error) { | |
| throw error; | |
| } | |
| } | |
| // 验证邮箱验证码 | |
| static async verifyEmailCode(email, code) { | |
| try { | |
| const user = await this.findByEmail(email); | |
| if (!user) { | |
| throw new Error('用户不存在'); | |
| } | |
| const now = Math.floor(Date.now() / 1000); | |
| if (!user.verification_code || user.verification_expires < now) { | |
| throw new Error('验证码已过期'); | |
| } | |
| if (user.verification_code !== code) { | |
| throw new Error('验证码错误'); | |
| } | |
| // 标记邮箱为已验证,清除验证码 | |
| await runQuery( | |
| `UPDATE users SET is_verified = 1, verification_code = NULL, | |
| verification_expires = NULL, updated_at = strftime('%s', 'now') WHERE id = ?`, | |
| [user.id] | |
| ); | |
| return await this.findById(user.id); | |
| } catch (error) { | |
| throw error; | |
| } | |
| } | |
| // 设置密码重置token | |
| static async setResetToken(email, token) { | |
| try { | |
| const expiresAt = Date.now() + 60 * 60 * 1000; // 1小时后过期 | |
| await runQuery( | |
| `UPDATE users SET reset_token = ?, reset_expires = ? WHERE email = ?`, | |
| [token, Math.floor(expiresAt / 1000), email] | |
| ); | |
| } catch (error) { | |
| throw error; | |
| } | |
| } | |
| // 重置密码 | |
| static async resetPassword(token, newPassword) { | |
| try { | |
| const user = await getQuery( | |
| `SELECT id, email, reset_expires FROM users WHERE reset_token = ?`, | |
| [token] | |
| ); | |
| if (!user) { | |
| throw new Error('无效的重置token'); | |
| } | |
| const now = Math.floor(Date.now() / 1000); | |
| if (user.reset_expires < now) { | |
| throw new Error('重置token已过期'); | |
| } | |
| // 加密新密码 | |
| const saltRounds = 12; | |
| const passwordHash = await bcrypt.hash(newPassword, saltRounds); | |
| // 更新密码并清除重置token | |
| await runQuery( | |
| `UPDATE users SET password_hash = ?, reset_token = NULL, | |
| reset_expires = NULL, updated_at = strftime('%s', 'now') WHERE id = ?`, | |
| [passwordHash, user.id] | |
| ); | |
| return await this.findById(user.id); | |
| } catch (error) { | |
| throw error; | |
| } | |
| } | |
| // 更新密码 | |
| static async updatePassword(userId, oldPassword, newPassword) { | |
| try { | |
| const user = await getQuery( | |
| `SELECT password_hash FROM users WHERE id = ?`, | |
| [userId] | |
| ); | |
| if (!user) { | |
| throw new Error('用户不存在'); | |
| } | |
| // 验证旧密码 | |
| const isValidOldPassword = await this.verifyPassword(oldPassword, user.password_hash); | |
| if (!isValidOldPassword) { | |
| throw new Error('原密码错误'); | |
| } | |
| // 加密新密码 | |
| const saltRounds = 12; | |
| const passwordHash = await bcrypt.hash(newPassword, saltRounds); | |
| // 更新密码 | |
| await runQuery( | |
| `UPDATE users SET password_hash = ?, updated_at = strftime('%s', 'now') WHERE id = ?`, | |
| [passwordHash, userId] | |
| ); | |
| return await this.findById(userId); | |
| } catch (error) { | |
| throw error; | |
| } | |
| } | |
| // 设置初始密码(用于注册流程,不验证旧密码) | |
| static async setInitialPassword(userId, newPassword) { | |
| try { | |
| const user = await getQuery( | |
| `SELECT id FROM users WHERE id = ?`, | |
| [userId] | |
| ); | |
| if (!user) { | |
| throw new Error('用户不存在'); | |
| } | |
| // 加密新密码 | |
| const saltRounds = 12; | |
| const passwordHash = await bcrypt.hash(newPassword, saltRounds); | |
| // 更新密码 | |
| await runQuery( | |
| `UPDATE users SET password_hash = ?, updated_at = strftime('%s', 'now') WHERE id = ?`, | |
| [passwordHash, userId] | |
| ); | |
| return await this.findById(userId); | |
| } catch (error) { | |
| throw error; | |
| } | |
| } | |
| // 保存用户token(用于记录登录状态) | |
| static async saveToken(userId, tokenHash, expiresAt) { | |
| try { | |
| await runQuery( | |
| `INSERT INTO user_tokens (user_id, token_hash, expires_at) VALUES (?, ?, ?)`, | |
| [userId, tokenHash, Math.floor(expiresAt / 1000)] | |
| ); | |
| } catch (error) { | |
| throw error; | |
| } | |
| } | |
| // 清除过期token | |
| static async cleanupExpiredTokens() { | |
| try { | |
| const now = Math.floor(Date.now() / 1000); | |
| const result = await runQuery( | |
| `DELETE FROM user_tokens WHERE expires_at < ?`, | |
| [now] | |
| ); | |
| console.log(`🧹 Cleaned up ${result.changes} expired tokens`); | |
| } catch (error) { | |
| console.error('Error cleaning up expired tokens:', error); | |
| } | |
| } | |
| // 获取所有用户 | |
| static async getAllUsers() { | |
| try { | |
| const users = await allQuery( | |
| `SELECT id, email, is_verified, is_activated, activation_expires, created_at, updated_at FROM users ORDER BY created_at DESC` | |
| ); | |
| return users; | |
| } catch (error) { | |
| throw error; | |
| } | |
| } | |
| // 删除用户 | |
| static async deleteUser(userId) { | |
| try { | |
| // 先删除相关的tokens | |
| await runQuery( | |
| `DELETE FROM user_tokens WHERE user_id = ?`, | |
| [userId] | |
| ); | |
| // 删除用户 | |
| const result = await runQuery( | |
| `DELETE FROM users WHERE id = ?`, | |
| [userId] | |
| ); | |
| return result.changes > 0; | |
| } catch (error) { | |
| throw error; | |
| } | |
| } | |
| // 激活用户账号 | |
| static async activateUser(userId, days = 30) { | |
| try { | |
| const expiresAt = Math.floor(Date.now() / 1000) + (days * 24 * 60 * 60); // days后过期 | |
| await runQuery( | |
| `UPDATE users SET is_activated = 1, activation_expires = ?, updated_at = strftime('%s', 'now') WHERE id = ?`, | |
| [expiresAt, userId] | |
| ); | |
| return await this.findById(userId); | |
| } catch (error) { | |
| throw error; | |
| } | |
| } | |
| // 延长激活时间 | |
| static async extendActivation(userId, days = 30) { | |
| try { | |
| const user = await this.findById(userId); | |
| if (!user) { | |
| throw new Error('用户不存在'); | |
| } | |
| let newExpiresAt; | |
| const now = Math.floor(Date.now() / 1000); | |
| if (user.is_activated && user.activation_expires > now) { | |
| // 如果还在有效期内,从当前过期时间延长 | |
| newExpiresAt = user.activation_expires + (days * 24 * 60 * 60); | |
| } else { | |
| // 如果已过期或未激活,从现在开始计算 | |
| newExpiresAt = now + (days * 24 * 60 * 60); | |
| } | |
| await runQuery( | |
| `UPDATE users SET is_activated = 1, activation_expires = ?, updated_at = strftime('%s', 'now') WHERE id = ?`, | |
| [newExpiresAt, userId] | |
| ); | |
| return await this.findById(userId); | |
| } catch (error) { | |
| throw error; | |
| } | |
| } | |
| // 检查用户激活状态 | |
| static async checkActivationStatus(userId) { | |
| try { | |
| const user = await this.findById(userId); | |
| if (!user) { | |
| throw new Error('用户不存在'); | |
| } | |
| const now = Math.floor(Date.now() / 1000); | |
| const isActive = user.is_activated && user.activation_expires > now; | |
| return { | |
| isActivated: user.is_activated, | |
| activationExpires: user.activation_expires, | |
| isActive, | |
| remainingDays: isActive ? Math.ceil((user.activation_expires - now) / (24 * 60 * 60)) : 0 | |
| }; | |
| } catch (error) { | |
| throw error; | |
| } | |
| } | |
| // 获取激活统计 | |
| static async getActivationStatistics() { | |
| try { | |
| const totalResult = await getQuery( | |
| `SELECT COUNT(*) as total FROM users WHERE is_verified = 1` | |
| ); | |
| const activatedResult = await getQuery( | |
| `SELECT COUNT(*) as activated FROM users WHERE is_activated = 1` | |
| ); | |
| const activeResult = await getQuery( | |
| `SELECT COUNT(*) as active FROM users WHERE is_activated = 1 AND activation_expires > strftime('%s', 'now')` | |
| ); | |
| const expiredResult = await getQuery( | |
| `SELECT COUNT(*) as expired FROM users WHERE is_activated = 1 AND activation_expires <= strftime('%s', 'now')` | |
| ); | |
| return { | |
| total: totalResult.total, | |
| activated: activatedResult.activated, | |
| active: activeResult.active, | |
| expired: expiredResult.expired | |
| }; | |
| } catch (error) { | |
| throw error; | |
| } | |
| } | |
| // 设置密码重置验证码 | |
| static async setPasswordResetCode(userId, code) { | |
| try { | |
| const expiresAt = Date.now() + 10 * 60 * 1000; // 10分钟后过期 | |
| await runQuery( | |
| `UPDATE users SET verification_code = ?, verification_expires = ? WHERE id = ?`, | |
| [code, Math.floor(expiresAt / 1000), userId] | |
| ); | |
| } catch (error) { | |
| throw error; | |
| } | |
| } | |
| // 验证密码重置验证码 | |
| static async verifyPasswordResetCode(email, code) { | |
| try { | |
| const user = await this.findByEmail(email); | |
| if (!user) { | |
| throw new Error('用户不存在'); | |
| } | |
| if (!user.is_verified) { | |
| throw new Error('该邮箱尚未验证'); | |
| } | |
| const now = Math.floor(Date.now() / 1000); | |
| if (!user.verification_code || user.verification_expires < now) { | |
| throw new Error('验证码已过期'); | |
| } | |
| if (user.verification_code !== code) { | |
| throw new Error('验证码错误'); | |
| } | |
| // 清除验证码(使用一次后即失效) | |
| await runQuery( | |
| `UPDATE users SET verification_code = NULL, verification_expires = NULL WHERE id = ?`, | |
| [user.id] | |
| ); | |
| return user; | |
| } catch (error) { | |
| throw error; | |
| } | |
| } | |
| // 直接更新密码(不验证旧密码,用于重置密码) | |
| static async updatePasswordDirectly(userId, newPassword) { | |
| try { | |
| const user = await getQuery( | |
| `SELECT id FROM users WHERE id = ?`, | |
| [userId] | |
| ); | |
| if (!user) { | |
| throw new Error('用户不存在'); | |
| } | |
| // 加密新密码 | |
| const saltRounds = 12; | |
| const passwordHash = await bcrypt.hash(newPassword, saltRounds); | |
| // 更新密码 | |
| await runQuery( | |
| `UPDATE users SET password_hash = ?, updated_at = strftime('%s', 'now') WHERE id = ?`, | |
| [passwordHash, userId] | |
| ); | |
| return await this.findById(userId); | |
| } catch (error) { | |
| throw error; | |
| } | |
| } | |
| } | |
| module.exports = User; | |