Norcoo's picture
init
de995c4
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;