Files
Docker-Proxy/hubcmdui/services/userServiceDB.js
T

368 lines
10 KiB
JavaScript

/**
* 基于SQLite的用户服务模块
*/
const bcrypt = require('bcrypt');
const logger = require('../logger');
const database = require('../database/database');
class UserServiceDB {
/**
* 获取所有用户
*/
async getUsers() {
try {
const users = await database.all('SELECT * FROM users ORDER BY created_at DESC');
return { users };
} catch (error) {
logger.error('获取用户列表失败:', error);
throw error;
}
}
/**
* 通过用户名获取用户
*/
async getUserByUsername(username) {
try {
return await database.get('SELECT * FROM users WHERE username = ?', [username]);
} catch (error) {
logger.error('获取用户失败:', error);
throw error;
}
}
/**
* 创建新用户
*/
async createUser(username, password) {
try {
// 检查用户是否已存在
const existingUser = await this.getUserByUsername(username);
if (existingUser) {
throw new Error('用户名已存在');
}
const hashedPassword = await bcrypt.hash(password, 10);
const result = await database.run(
'INSERT INTO users (username, password, created_at, login_count, last_login) VALUES (?, ?, ?, ?, ?)',
[username, hashedPassword, new Date().toISOString(), 0, null]
);
return { success: true, username, id: result.id };
} catch (error) {
logger.error('创建用户失败:', error);
throw error;
}
}
/**
* 更新用户登录信息
*/
async updateUserLoginInfo(username) {
try {
const user = await this.getUserByUsername(username);
if (user) {
await database.run(
'UPDATE users SET login_count = login_count + 1, last_login = ? WHERE username = ?',
[new Date().toISOString(), username]
);
}
} catch (error) {
logger.error('更新用户登录信息失败:', error);
}
}
/**
* 获取用户统计信息
*/
async getUserStats(username) {
try {
const user = await this.getUserByUsername(username);
if (!user) {
return { loginCount: '0', lastLogin: '未知', accountAge: '0' };
}
// 计算账户年龄
let accountAge = '0';
if (user.created_at) {
const createdDate = new Date(user.created_at);
const currentDate = new Date();
const diffTime = Math.abs(currentDate - createdDate);
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
accountAge = diffDays.toString();
}
// 格式化最后登录时间
let lastLogin = '未知';
if (user.last_login) {
const lastLoginDate = new Date(user.last_login);
const now = new Date();
const isToday = lastLoginDate.toDateString() === now.toDateString();
if (isToday) {
lastLogin = '今天 ' + lastLoginDate.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
} else {
lastLogin = lastLoginDate.toLocaleDateString() + ' ' + lastLoginDate.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
}
}
return {
username: user.username,
loginCount: (user.login_count || 0).toString(),
lastLogin,
accountAge
};
} catch (error) {
logger.error('获取用户统计信息失败:', error);
return { loginCount: '0', lastLogin: '未知', accountAge: '0' };
}
}
/**
* 修改用户密码
*/
async changePassword(username, currentPassword, newPassword) {
try {
const user = await this.getUserByUsername(username);
if (!user) {
throw new Error('用户不存在');
}
// 验证当前密码
const isMatch = await bcrypt.compare(currentPassword, user.password);
if (!isMatch) {
throw new Error('当前密码不正确');
}
// 验证新密码复杂度
if (!this.isPasswordComplex(newPassword)) {
throw new Error('新密码不符合复杂度要求');
}
// 更新密码
const hashedNewPassword = await bcrypt.hash(newPassword, 10);
await database.run(
'UPDATE users SET password = ?, updated_at = ? WHERE username = ?',
[hashedNewPassword, new Date().toISOString(), username]
);
logger.info(`用户 ${username} 密码已成功修改`);
} catch (error) {
logger.error('修改密码失败:', error);
throw error;
}
}
/**
* 修改用户名
*/
async changeUsername(currentUsername, newUsername, password) {
try {
const user = await this.getUserByUsername(currentUsername);
if (!user) {
throw new Error('用户不存在');
}
// 验证密码
const isMatch = await bcrypt.compare(password, user.password);
if (!isMatch) {
throw new Error('密码不正确');
}
// 验证新用户名格式
if (!this.isUsernameValid(newUsername)) {
throw new Error('用户名格式不正确(3-20位,只能包含字母、数字和下划线)');
}
// 检查新用户名是否已存在
const existingUser = await this.getUserByUsername(newUsername);
if (existingUser) {
throw new Error('该用户名已被使用');
}
// 更新用户名
await database.run(
'UPDATE users SET username = ?, updated_at = ? WHERE username = ?',
[newUsername, new Date().toISOString(), currentUsername]
);
logger.info(`用户 ${currentUsername} 已成功修改用户名为 ${newUsername}`);
return { success: true, newUsername };
} catch (error) {
logger.error('修改用户名失败:', error);
throw error;
}
}
/**
* 验证用户名格式
*/
isUsernameValid(username) {
// 3-20位,只能包含字母、数字和下划线
const usernameRegex = /^[a-zA-Z0-9_]{3,20}$/;
return usernameRegex.test(username);
}
/**
* 验证密码复杂度
*/
isPasswordComplex(password) {
// 至少包含1个字母、1个数字和1个特殊字符,长度在8-16位之间
const passwordRegex = /^(?=.*[A-Za-z])(?=.*\d)(?=.*[.,\-_+=()[\]{}|\\;:'"<>?/@$!%*#?&])[A-Za-z\d.,\-_+=()[\]{}|\\;:'"<>?/@$!%*#?&]{8,16}$/;
return passwordRegex.test(password);
}
/**
* 验证用户登录
*/
async validateUser(username, password) {
try {
const user = await this.getUserByUsername(username);
if (!user) {
return null;
}
const isMatch = await bcrypt.compare(password, user.password);
if (isMatch) {
return user;
}
return null;
} catch (error) {
logger.error('验证用户失败:', error);
throw error;
}
}
/**
* 生成密码重置令牌
* 返回一个临时令牌,存储在内存中,有效期10分钟
*/
generateResetToken(username) {
const crypto = require('crypto');
const token = crypto.randomBytes(32).toString('hex');
const expiry = Date.now() + 10 * 60 * 1000; // 10分钟有效期
// 存储在内存中(简单实现)
if (!this.resetTokens) {
this.resetTokens = {};
}
this.resetTokens[token] = {
username,
expiry
};
// 清理过期令牌
this.cleanExpiredTokens();
logger.info(`为用户 ${username} 生成了密码重置令牌`);
return token;
}
/**
* 清理过期的重置令牌
*/
cleanExpiredTokens() {
if (!this.resetTokens) return;
const now = Date.now();
for (const token in this.resetTokens) {
if (this.resetTokens[token].expiry < now) {
delete this.resetTokens[token];
}
}
}
/**
* 验证重置令牌
*/
validateResetToken(token) {
if (!this.resetTokens || !this.resetTokens[token]) {
return null;
}
const tokenData = this.resetTokens[token];
if (tokenData.expiry < Date.now()) {
delete this.resetTokens[token];
return null;
}
return tokenData.username;
}
/**
* 使用令牌重置密码
*/
async resetPasswordWithToken(token, newPassword) {
try {
const username = this.validateResetToken(token);
if (!username) {
throw new Error('重置令牌无效或已过期');
}
// 验证新密码复杂度
if (!this.isPasswordComplex(newPassword)) {
throw new Error('新密码不符合复杂度要求(需要8-16位,包含字母、数字和特殊字符)');
}
const user = await this.getUserByUsername(username);
if (!user) {
throw new Error('用户不存在');
}
// 更新密码
const hashedNewPassword = await bcrypt.hash(newPassword, 10);
await database.run(
'UPDATE users SET password = ?, updated_at = ? WHERE username = ?',
[hashedNewPassword, new Date().toISOString(), username]
);
// 删除使用过的令牌
delete this.resetTokens[token];
logger.info(`用户 ${username} 密码已通过重置令牌成功修改`);
return { success: true, username };
} catch (error) {
logger.error('重置密码失败:', error);
throw error;
}
}
/**
* 直接重置用户密码(管理员功能,无需旧密码)
* 用于忘记密码时,通过验证用户名来重置
*/
async forceResetPassword(username, newPassword) {
try {
const user = await this.getUserByUsername(username);
if (!user) {
throw new Error('用户不存在');
}
// 验证新密码复杂度
if (!this.isPasswordComplex(newPassword)) {
throw new Error('新密码不符合复杂度要求(需要8-16位,包含字母、数字和特殊字符)');
}
// 更新密码
const hashedNewPassword = await bcrypt.hash(newPassword, 10);
await database.run(
'UPDATE users SET password = ?, updated_at = ? WHERE username = ?',
[hashedNewPassword, new Date().toISOString(), username]
);
logger.info(`用户 ${username} 密码已被强制重置`);
return { success: true };
} catch (error) {
logger.error('强制重置密码失败:', error);
throw error;
}
}
}
module.exports = new UserServiceDB();