fix: user stats

This commit is contained in:
Feng Yue
2025-08-13 22:34:22 +08:00
parent a3c9e39401
commit 2756671117
5 changed files with 235 additions and 165 deletions

View File

@@ -490,7 +490,9 @@ const authenticateUser = async (req, res, next) => {
// 检查用户是否被禁用
if (!user.isActive) {
logger.security(`🔒 Disabled user login attempt: ${user.username} from ${req.ip || 'unknown'}`)
logger.security(
`🔒 Disabled user login attempt: ${user.username} from ${req.ip || 'unknown'}`
)
return res.status(403).json({
error: 'Account disabled',
message: 'Your account has been disabled. Please contact administrator.'
@@ -506,7 +508,7 @@ const authenticateUser = async (req, res, next) => {
firstName: user.firstName,
lastName: user.lastName,
role: user.role,
sessionToken: sessionToken,
sessionToken,
sessionCreatedAt: session.createdAt
}
@@ -559,7 +561,7 @@ const authenticateUserOrAdmin = async (req, res, next) => {
loginTime: adminSession.loginTime
}
req.userType = 'admin'
const authDuration = Date.now() - startTime
logger.security(`🔐 Admin authenticated: ${adminSession.username} in ${authDuration}ms`)
return next()
@@ -575,7 +577,7 @@ const authenticateUserOrAdmin = async (req, res, next) => {
const sessionValidation = await userService.validateUserSession(userToken)
if (sessionValidation) {
const { session, user } = sessionValidation
if (user.isActive) {
req.user = {
id: user.id,
@@ -589,7 +591,7 @@ const authenticateUserOrAdmin = async (req, res, next) => {
sessionCreatedAt: session.createdAt
}
req.userType = 'user'
const authDuration = Date.now() - startTime
logger.info(`👤 User authenticated: ${user.username} (${user.id}) in ${authDuration}ms`)
return next()
@@ -606,7 +608,6 @@ const authenticateUserOrAdmin = async (req, res, next) => {
error: 'Authentication required',
message: 'Please login as user or admin to access this resource'
})
} catch (error) {
const authDuration = Date.now() - startTime
logger.error(`❌ User/Admin authentication error (${authDuration}ms):`, {
@@ -624,34 +625,34 @@ const authenticateUserOrAdmin = async (req, res, next) => {
}
// 🛡️ 权限检查中间件
const requireRole = (allowedRoles) => {
return (req, res, next) => {
// 管理员始终有权限
if (req.admin) {
return next()
}
// 检查用户角色
if (req.user) {
const userRole = req.user.role
const allowed = Array.isArray(allowedRoles) ? allowedRoles : [allowedRoles]
if (allowed.includes(userRole)) {
return next()
} else {
logger.security(`🚫 Access denied for user ${req.user.username} (role: ${userRole}) to ${req.originalUrl}`)
return res.status(403).json({
error: 'Insufficient permissions',
message: `This resource requires one of the following roles: ${allowed.join(', ')}`
})
}
}
return res.status(401).json({
error: 'Authentication required',
message: 'Please login to access this resource'
})
const requireRole = (allowedRoles) => (req, res, next) => {
// 管理员始终有权限
if (req.admin) {
return next()
}
// 检查用户角色
if (req.user) {
const userRole = req.user.role
const allowed = Array.isArray(allowedRoles) ? allowedRoles : [allowedRoles]
if (allowed.includes(userRole)) {
return next()
} else {
logger.security(
`🚫 Access denied for user ${req.user.username} (role: ${userRole}) to ${req.originalUrl}`
)
return res.status(403).json({
error: 'Insufficient permissions',
message: `This resource requires one of the following roles: ${allowed.join(', ')}`
})
}
}
return res.status(401).json({
error: 'Authentication required',
message: 'Please login to access this resource'
})
}
// 🔒 管理员权限检查中间件
@@ -665,7 +666,9 @@ const requireAdmin = (req, res, next) => {
return next()
}
logger.security(`🚫 Admin access denied for ${req.user?.username || 'unknown'} from ${req.ip || 'unknown'}`)
logger.security(
`🚫 Admin access denied for ${req.user?.username || 'unknown'} from ${req.ip || 'unknown'}`
)
return res.status(403).json({
error: 'Admin access required',
message: 'This resource requires administrator privileges'

View File

@@ -66,7 +66,6 @@ router.post('/login', async (req, res) => {
},
sessionToken: authResult.sessionToken
})
} catch (error) {
logger.error('❌ User login error:', error)
res.status(500).json({
@@ -80,14 +79,13 @@ router.post('/login', async (req, res) => {
router.post('/logout', authenticateUser, async (req, res) => {
try {
await userService.invalidateUserSession(req.user.sessionToken)
logger.info(`👋 User logout: ${req.user.username}`)
res.json({
success: true,
message: 'Logout successful'
})
} catch (error) {
logger.error('❌ User logout error:', error)
res.status(500).json({
@@ -125,7 +123,6 @@ router.get('/profile', authenticateUser, async (req, res) => {
totalUsage: user.totalUsage
}
})
} catch (error) {
logger.error('❌ Get user profile error:', error)
res.status(500).json({
@@ -139,9 +136,9 @@ router.get('/profile', authenticateUser, async (req, res) => {
router.get('/api-keys', authenticateUser, async (req, res) => {
try {
const apiKeys = await apiKeyService.getUserApiKeys(req.user.id)
// 移除敏感信息
const safeApiKeys = apiKeys.map(key => ({
const safeApiKeys = apiKeys.map((key) => ({
id: key.id,
name: key.name,
description: key.description,
@@ -154,7 +151,9 @@ router.get('/api-keys', authenticateUser, async (req, res) => {
dailyCost: key.dailyCost,
dailyCostLimit: key.dailyCostLimit,
// 不返回实际的key值只返回前缀和后几位
keyPreview: key.key ? `${key.key.substring(0, 8)}...${key.key.substring(key.key.length - 4)}` : null
keyPreview: key.key
? `${key.key.substring(0, 8)}...${key.key.substring(key.key.length - 4)}`
: null
}))
res.json({
@@ -162,7 +161,6 @@ router.get('/api-keys', authenticateUser, async (req, res) => {
apiKeys: safeApiKeys,
total: safeApiKeys.length
})
} catch (error) {
logger.error('❌ Get user API keys error:', error)
res.status(500).json({
@@ -207,7 +205,7 @@ router.post('/api-keys', authenticateUser, async (req, res) => {
}
const newApiKey = await apiKeyService.createApiKey(apiKeyData)
// 更新用户API Key数量
await userService.updateUserApiKeyCount(req.user.id, userApiKeys.length + 1)
@@ -227,7 +225,6 @@ router.post('/api-keys', authenticateUser, async (req, res) => {
createdAt: newApiKey.createdAt
}
})
} catch (error) {
logger.error('❌ Create user API key error:', error)
res.status(500).json({
@@ -265,7 +262,6 @@ router.post('/api-keys/:keyId/regenerate', authenticateUser, async (req, res) =>
updatedAt: newKey.updatedAt
}
})
} catch (error) {
logger.error('❌ Regenerate user API key error:', error)
res.status(500).json({
@@ -301,7 +297,6 @@ router.delete('/api-keys/:keyId', authenticateUser, async (req, res) => {
success: true,
message: 'API key deleted successfully'
})
} catch (error) {
logger.error('❌ Delete user API key error:', error)
res.status(500).json({
@@ -318,7 +313,7 @@ router.get('/usage-stats', authenticateUser, async (req, res) => {
// 获取用户的API Keys
const userApiKeys = await apiKeyService.getUserApiKeys(req.user.id)
const apiKeyIds = userApiKeys.map(key => key.id)
const apiKeyIds = userApiKeys.map((key) => key.id)
if (apiKeyIds.length === 0) {
return res.json({
@@ -341,7 +336,6 @@ router.get('/usage-stats', authenticateUser, async (req, res) => {
success: true,
stats
})
} catch (error) {
logger.error('❌ Get user usage stats error:', error)
res.status(500).json({
@@ -371,10 +365,11 @@ router.get('/', authenticateUserOrAdmin, requireAdmin, async (req, res) => {
let filteredUsers = result.users
if (search) {
const searchLower = search.toLowerCase()
filteredUsers = result.users.filter(user =>
user.username.toLowerCase().includes(searchLower) ||
user.displayName.toLowerCase().includes(searchLower) ||
user.email.toLowerCase().includes(searchLower)
filteredUsers = result.users.filter(
(user) =>
user.username.toLowerCase().includes(searchLower) ||
user.displayName.toLowerCase().includes(searchLower) ||
user.email.toLowerCase().includes(searchLower)
)
}
@@ -388,7 +383,6 @@ router.get('/', authenticateUserOrAdmin, requireAdmin, async (req, res) => {
totalPages: result.totalPages
}
})
} catch (error) {
logger.error('❌ Get users list error:', error)
res.status(500).json({
@@ -418,7 +412,7 @@ router.get('/:userId', authenticateUserOrAdmin, requireAdmin, async (req, res) =
success: true,
user: {
...user,
apiKeys: apiKeys.map(key => ({
apiKeys: apiKeys.map((key) => ({
id: key.id,
name: key.name,
description: key.description,
@@ -426,11 +420,12 @@ router.get('/:userId', authenticateUserOrAdmin, requireAdmin, async (req, res) =
createdAt: key.createdAt,
lastUsedAt: key.lastUsedAt,
usage: key.usage,
keyPreview: key.key ? `${key.key.substring(0, 8)}...${key.key.substring(key.key.length - 4)}` : null
keyPreview: key.key
? `${key.key.substring(0, 8)}...${key.key.substring(key.key.length - 4)}`
: null
}))
}
})
} catch (error) {
logger.error('❌ Get user details error:', error)
res.status(500).json({
@@ -456,7 +451,9 @@ router.patch('/:userId/status', authenticateUserOrAdmin, requireAdmin, async (re
const updatedUser = await userService.updateUserStatus(userId, isActive)
const adminUser = req.admin?.username || req.user?.username
logger.info(`🔄 Admin ${adminUser} ${isActive ? 'enabled' : 'disabled'} user: ${updatedUser.username}`)
logger.info(
`🔄 Admin ${adminUser} ${isActive ? 'enabled' : 'disabled'} user: ${updatedUser.username}`
)
res.json({
success: true,
@@ -468,7 +465,6 @@ router.patch('/:userId/status', authenticateUserOrAdmin, requireAdmin, async (re
updatedAt: updatedUser.updatedAt
}
})
} catch (error) {
logger.error('❌ Update user status error:', error)
res.status(500).json({
@@ -507,7 +503,6 @@ router.patch('/:userId/role', authenticateUserOrAdmin, requireAdmin, async (req,
updatedAt: updatedUser.updatedAt
}
})
} catch (error) {
logger.error('❌ Update user role error:', error)
res.status(500).json({
@@ -540,7 +535,6 @@ router.post('/:userId/disable-keys', authenticateUserOrAdmin, requireAdmin, asyn
message: `Disabled ${result.count} API keys for user ${user.username}`,
disabledCount: result.count
})
} catch (error) {
logger.error('❌ Disable user API keys error:', error)
res.status(500).json({
@@ -566,7 +560,7 @@ router.get('/:userId/usage-stats', authenticateUserOrAdmin, requireAdmin, async
// 获取用户的API Keys
const userApiKeys = await apiKeyService.getUserApiKeys(userId)
const apiKeyIds = userApiKeys.map(key => key.id)
const apiKeyIds = userApiKeys.map((key) => key.id)
if (apiKeyIds.length === 0) {
return res.json({
@@ -599,7 +593,6 @@ router.get('/:userId/usage-stats', authenticateUserOrAdmin, requireAdmin, async
},
stats
})
} catch (error) {
logger.error('❌ Get user usage stats (admin) error:', error)
res.status(500).json({
@@ -618,7 +611,6 @@ router.get('/stats/overview', authenticateUserOrAdmin, requireAdmin, async (req,
success: true,
stats
})
} catch (error) {
logger.error('❌ Get user stats overview error:', error)
res.status(500).json({
@@ -638,7 +630,6 @@ router.get('/admin/ldap-test', authenticateUserOrAdmin, requireAdmin, async (req
ldapTest: testResult,
config: ldapService.getConfigInfo()
})
} catch (error) {
logger.error('❌ LDAP test error:', error)
res.status(500).json({
@@ -648,4 +639,4 @@ router.get('/admin/ldap-test', authenticateUserOrAdmin, requireAdmin, async (req
}
})
module.exports = router
module.exports = router

View File

@@ -492,8 +492,8 @@ class ApiKeyService {
try {
const allKeys = await redis.getAllApiKeys()
return allKeys
.filter(key => key.userId === userId)
.map(key => ({
.filter((key) => key.userId === userId)
.map((key) => ({
id: key.id,
name: key.name,
description: key.description,
@@ -520,7 +520,9 @@ class ApiKeyService {
async getApiKeyById(keyId, userId = null) {
try {
const keyData = await redis.getApiKey(keyId)
if (!keyData) return null
if (!keyData) {
return null
}
// 如果指定了用户ID检查权限
if (userId && keyData.userId !== userId) {

View File

@@ -7,7 +7,7 @@ class LdapService {
constructor() {
this.config = config.ldap
this.client = null
// 验证配置
if (this.config.enabled) {
this.validateConfiguration()
@@ -17,31 +17,34 @@ class LdapService {
// 🔍 验证LDAP配置
validateConfiguration() {
const errors = []
if (!this.config.server) {
errors.push('LDAP server configuration is missing')
} else {
if (!this.config.server.url || typeof this.config.server.url !== 'string') {
errors.push('LDAP server URL is not configured or invalid')
}
if (!this.config.server.bindDN || typeof this.config.server.bindDN !== 'string') {
errors.push('LDAP bind DN is not configured or invalid')
}
if (!this.config.server.bindCredentials || typeof this.config.server.bindCredentials !== 'string') {
if (
!this.config.server.bindCredentials ||
typeof this.config.server.bindCredentials !== 'string'
) {
errors.push('LDAP bind credentials are not configured or invalid')
}
if (!this.config.server.searchBase || typeof this.config.server.searchBase !== 'string') {
errors.push('LDAP search base is not configured or invalid')
}
if (!this.config.server.searchFilter || typeof this.config.server.searchFilter !== 'string') {
errors.push('LDAP search filter is not configured or invalid')
}
}
if (errors.length > 0) {
logger.error('❌ LDAP configuration validation failed:', errors)
// Don't throw error during initialization, just log warnings
@@ -59,7 +62,7 @@ class LdapService {
// Try different ways to get the DN
let dn = null
// Method 1: Direct dn property
if (ldapEntry.dn) {
dn = ldapEntry.dn
@@ -107,35 +110,35 @@ class LdapService {
// 如果使用 LDAPS (SSL/TLS),添加 TLS 选项
if (this.config.server.url.toLowerCase().startsWith('ldaps://')) {
const tlsOptions = {}
// 证书验证设置
if (this.config.server.tls) {
if (typeof this.config.server.tls.rejectUnauthorized === 'boolean') {
tlsOptions.rejectUnauthorized = this.config.server.tls.rejectUnauthorized
}
// CA 证书
if (this.config.server.tls.ca) {
tlsOptions.ca = this.config.server.tls.ca
}
// 客户端证书和私钥 (双向认证)
if (this.config.server.tls.cert) {
tlsOptions.cert = this.config.server.tls.cert
}
if (this.config.server.tls.key) {
tlsOptions.key = this.config.server.tls.key
}
// 服务器名称 (SNI)
if (this.config.server.tls.servername) {
tlsOptions.servername = this.config.server.tls.servername
}
}
clientOptions.tlsOptions = tlsOptions
logger.debug('🔒 Creating LDAPS client with TLS options:', {
url: this.config.server.url,
rejectUnauthorized: tlsOptions.rejectUnauthorized,
@@ -184,23 +187,23 @@ class LdapService {
async bindClient(client) {
return new Promise((resolve, reject) => {
// 验证绑定凭据
const bindDN = this.config.server.bindDN
const bindCredentials = this.config.server.bindCredentials
const { bindDN } = this.config.server
const { bindCredentials } = this.config.server
if (!bindDN || typeof bindDN !== 'string') {
const error = new Error('LDAP bind DN is not configured or invalid')
logger.error('❌ LDAP configuration error:', error.message)
reject(error)
return
}
if (!bindCredentials || typeof bindCredentials !== 'string') {
const error = new Error('LDAP bind credentials are not configured or invalid')
logger.error('❌ LDAP configuration error:', error.message)
reject(error)
return
}
client.bind(bindDN, bindCredentials, (err) => {
if (err) {
logger.error('❌ LDAP bind failed:', err)
@@ -226,7 +229,7 @@ class LdapService {
logger.debug(`🔍 Searching for user: ${username} with filter: ${searchFilter}`)
const entries = []
client.search(this.config.server.searchBase, searchOptions, (err, res) => {
if (err) {
logger.error('❌ LDAP search error:', err)
@@ -256,8 +259,10 @@ class LdapService {
})
res.on('end', (result) => {
logger.debug(`✅ LDAP search completed. Status: ${result.status}, Found ${entries.length} entries`)
logger.debug(
`✅ LDAP search completed. Status: ${result.status}, Found ${entries.length} entries`
)
if (entries.length === 0) {
resolve(null)
} else {
@@ -270,7 +275,7 @@ class LdapService {
entryStringified: JSON.stringify(entries[0], null, 2).substring(0, 500)
})
}
if (entries.length === 1) {
resolve(entries[0])
} else {
@@ -293,18 +298,18 @@ class LdapService {
reject(error)
return
}
if (!password || typeof password !== 'string') {
logger.debug(`🚫 Invalid or empty password for DN: ${userDN}`)
resolve(false)
return
}
const authClient = this.createClient()
authClient.bind(userDN, password, (err) => {
authClient.unbind() // 立即关闭认证客户端
if (err) {
if (err.name === 'InvalidCredentialsError') {
logger.debug(`🚫 Invalid credentials for DN: ${userDN}`)
@@ -329,7 +334,7 @@ class LdapService {
// 创建属性映射
const attrMap = {}
attributes.forEach(attr => {
attributes.forEach((attr) => {
const name = attr.type || attr.name
const values = Array.isArray(attr.values) ? attr.values : [attr.values]
attrMap[name] = values.length === 1 ? values[0] : values
@@ -337,7 +342,7 @@ class LdapService {
// 根据配置映射用户属性
const mapping = this.config.userMapping
userInfo.displayName = attrMap[mapping.displayName] || username
userInfo.email = attrMap[mapping.email] || ''
userInfo.firstName = attrMap[mapping.firstName] || ''
@@ -386,7 +391,10 @@ class LdapService {
throw new Error('LDAP bind DN is not configured')
}
if (!this.config.server.bindCredentials || typeof this.config.server.bindCredentials !== 'string') {
if (
!this.config.server.bindCredentials ||
typeof this.config.server.bindCredentials !== 'string'
) {
throw new Error('LDAP bind credentials are not configured')
}
@@ -450,9 +458,9 @@ class LdapService {
// 7. 检查用户是否被禁用
if (!user.isActive) {
logger.security(`🔒 Disabled user LDAP login attempt: ${username} from LDAP authentication`)
return {
success: false,
message: 'Your account has been disabled. Please contact administrator.'
return {
success: false,
message: 'Your account has been disabled. Please contact administrator.'
}
}
@@ -470,7 +478,6 @@ class LdapService {
sessionToken,
message: 'Authentication successful'
}
} catch (error) {
logger.error('❌ LDAP authentication error:', error)
return {
@@ -499,7 +506,7 @@ class LdapService {
try {
await this.bindClient(client)
return {
success: true,
message: 'LDAP connection successful',
@@ -553,4 +560,4 @@ class LdapService {
}
}
module.exports = new LdapService()
module.exports = new LdapService()

View File

@@ -1,6 +1,5 @@
const redis = require('../models/redis')
const crypto = require('crypto')
const bcrypt = require('bcryptjs')
const logger = require('../utils/logger')
const config = require('../../config/config')
@@ -88,7 +87,9 @@ class UserService {
async getUserByUsername(username) {
try {
const userId = await redis.get(`${this.usernamePrefix}${username}`)
if (!userId) return null
if (!userId) {
return null
}
const userData = await redis.get(`${this.userPrefix}${userId}`)
return userData ? JSON.parse(userData) : null
@@ -99,16 +100,100 @@ class UserService {
}
// 👤 通过ID获取用户
async getUserById(userId) {
async getUserById(userId, calculateUsage = true) {
try {
const userData = await redis.get(`${this.userPrefix}${userId}`)
return userData ? JSON.parse(userData) : null
if (!userData) {
return null
}
const user = JSON.parse(userData)
// Calculate totalUsage by aggregating user's API keys usage (if requested)
if (calculateUsage) {
try {
const usageStats = await this.calculateUserUsageStats(userId)
user.totalUsage = usageStats.totalUsage
user.apiKeyCount = usageStats.apiKeyCount
} catch (error) {
logger.error('❌ Error calculating user usage stats:', error)
// Fallback to stored values if calculation fails
user.totalUsage = user.totalUsage || {
requests: 0,
inputTokens: 0,
outputTokens: 0,
totalCost: 0
}
user.apiKeyCount = user.apiKeyCount || 0
}
}
return user
} catch (error) {
logger.error('❌ Error getting user by ID:', error)
throw error
}
}
// 📊 计算用户使用统计通过聚合API Keys
async calculateUserUsageStats(userId) {
try {
// Use redis directly to avoid circular dependency
const client = redis.getClientSafe()
const pattern = 'api_key:*'
const keys = await client.keys(pattern)
const userApiKeys = []
for (const key of keys) {
const keyData = await client.get(key)
if (keyData) {
const apiKey = JSON.parse(keyData)
if (apiKey.userId === userId) {
// Get usage stats for this API key
const usage = await redis.getUsageStats(apiKey.id)
userApiKeys.push({
id: apiKey.id,
name: apiKey.name,
usage
})
}
}
}
const totalUsage = {
requests: 0,
inputTokens: 0,
outputTokens: 0,
totalCost: 0
}
for (const apiKey of userApiKeys) {
if (apiKey.usage) {
totalUsage.requests += apiKey.usage.requests || 0
totalUsage.inputTokens += apiKey.usage.inputTokens || 0
totalUsage.outputTokens += apiKey.usage.outputTokens || 0
totalUsage.totalCost += apiKey.usage.totalCost || 0
}
}
return {
totalUsage,
apiKeyCount: userApiKeys.length
}
} catch (error) {
logger.error('❌ Error calculating user usage stats:', error)
return {
totalUsage: {
requests: 0,
inputTokens: 0,
outputTokens: 0,
totalCost: 0
},
apiKeyCount: 0
}
}
}
// 📋 获取所有用户列表(管理员功能)
async getAllUsers(options = {}) {
try {
@@ -116,17 +201,21 @@ class UserService {
const { page = 1, limit = 20, role, isActive } = options
const pattern = `${this.userPrefix}*`
const keys = await client.keys(pattern)
const users = []
for (const key of keys) {
const userData = await client.get(key)
if (userData) {
const user = JSON.parse(userData)
// 应用过滤条件
if (role && user.role !== role) continue
if (typeof isActive === 'boolean' && user.isActive !== isActive) continue
if (role && user.role !== role) {
continue
}
if (typeof isActive === 'boolean' && user.isActive !== isActive) {
continue
}
users.push(user)
}
}
@@ -153,7 +242,7 @@ class UserService {
// 🔄 更新用户状态
async updateUserStatus(userId, isActive) {
try {
const user = await this.getUserById(userId)
const user = await this.getUserById(userId, false) // Skip usage calculation
if (!user) {
throw new Error('User not found')
}
@@ -179,7 +268,7 @@ class UserService {
// 🔄 更新用户角色
async updateUserRole(userId, role) {
try {
const user = await this.getUserById(userId)
const user = await this.getUserById(userId, false) // Skip usage calculation
if (!user) {
throw new Error('User not found')
}
@@ -197,46 +286,22 @@ class UserService {
}
}
// 📊 更新用户使用统计
async updateUserUsage(userId, usage) {
try {
const user = await this.getUserById(userId)
if (!user) return
const { requests = 0, inputTokens = 0, outputTokens = 0, cost = 0 } = usage
user.totalUsage.requests += requests
user.totalUsage.inputTokens += inputTokens
user.totalUsage.outputTokens += outputTokens
user.totalUsage.totalCost += cost
user.updatedAt = new Date().toISOString()
await redis.set(`${this.userPrefix}${userId}`, JSON.stringify(user))
} catch (error) {
logger.error('❌ Error updating user usage:', error)
}
}
// 📊 更新用户API Key数量
async updateUserApiKeyCount(userId, count) {
try {
const user = await this.getUserById(userId)
if (!user) return
user.apiKeyCount = count
user.updatedAt = new Date().toISOString()
await redis.set(`${this.userPrefix}${userId}`, JSON.stringify(user))
} catch (error) {
logger.error('❌ Error updating user API key count:', error)
}
// 📊 更新用户API Key数量 (已废弃,现在通过聚合计算)
async updateUserApiKeyCount(userId, _count) {
// This method is deprecated since apiKeyCount is now calculated dynamically
// in getUserById by aggregating the user's API keys
logger.debug(
`📊 updateUserApiKeyCount called for ${userId} but is now deprecated (count auto-calculated)`
)
}
// 📝 记录用户登录
async recordUserLogin(userId) {
try {
const user = await this.getUserById(userId)
if (!user) return
const user = await this.getUserById(userId, false) // Skip usage calculation
if (!user) {
return
}
user.lastLoginAt = new Date().toISOString()
await redis.set(`${this.userPrefix}${userId}`, JSON.stringify(user))
@@ -272,10 +337,12 @@ class UserService {
async validateUserSession(sessionToken) {
try {
const sessionData = await redis.get(`${this.userSessionPrefix}${sessionToken}`)
if (!sessionData) return null
if (!sessionData) {
return null
}
const session = JSON.parse(sessionData)
// 检查会话是否过期
if (new Date() > new Date(session.expiresAt)) {
await this.invalidateUserSession(sessionToken)
@@ -283,7 +350,7 @@ class UserService {
}
// 获取用户信息
const user = await this.getUserById(session.userId)
const user = await this.getUserById(session.userId, false) // Skip usage calculation for validation
if (!user || !user.isActive) {
await this.invalidateUserSession(sessionToken)
return null
@@ -312,7 +379,7 @@ class UserService {
const client = redis.getClientSafe()
const pattern = `${this.userSessionPrefix}*`
const keys = await client.keys(pattern)
for (const key of keys) {
const sessionData = await client.get(key)
if (sessionData) {
@@ -322,7 +389,7 @@ class UserService {
}
}
}
logger.info(`🚫 Invalidated all sessions for user: ${userId}`)
} catch (error) {
logger.error('❌ Error invalidating user sessions:', error)
@@ -332,7 +399,7 @@ class UserService {
// 🗑️ 删除用户(软删除,标记为不活跃)
async deleteUser(userId) {
try {
const user = await this.getUserById(userId)
const user = await this.getUserById(userId, false) // Skip usage calculation
if (!user) {
throw new Error('User not found')
}
@@ -343,10 +410,10 @@ class UserService {
user.updatedAt = new Date().toISOString()
await redis.set(`${this.userPrefix}${userId}`, JSON.stringify(user))
// 删除所有会话
await this.invalidateUserSessions(userId)
logger.info(`🗑️ Soft deleted user: ${user.username} (${userId})`)
return user
} catch (error) {
@@ -361,7 +428,7 @@ class UserService {
const client = redis.getClientSafe()
const pattern = `${this.userPrefix}*`
const keys = await client.keys(pattern)
const stats = {
totalUsers: 0,
activeUsers: 0,
@@ -381,17 +448,17 @@ class UserService {
if (userData) {
const user = JSON.parse(userData)
stats.totalUsers++
if (user.isActive) {
stats.activeUsers++
}
if (user.role === 'admin') {
stats.adminUsers++
} else {
stats.regularUsers++
}
stats.totalApiKeys += user.apiKeyCount || 0
stats.totalUsage.requests += user.totalUsage?.requests || 0
stats.totalUsage.inputTokens += user.totalUsage?.inputTokens || 0
@@ -408,4 +475,4 @@ class UserService {
}
}
module.exports = new UserService()
module.exports = new UserService()