mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-23 21:01:24 +00:00
fix: user stats
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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({
|
||||
@@ -87,7 +86,6 @@ router.post('/logout', authenticateUser, async (req, res) => {
|
||||
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({
|
||||
@@ -141,7 +138,7 @@ router.get('/api-keys', authenticateUser, async (req, res) => {
|
||||
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({
|
||||
@@ -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({
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -29,7 +29,10 @@ class LdapService {
|
||||
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')
|
||||
}
|
||||
|
||||
@@ -184,8 +187,8 @@ 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')
|
||||
@@ -256,7 +259,9 @@ 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)
|
||||
@@ -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
|
||||
@@ -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')
|
||||
}
|
||||
|
||||
@@ -470,7 +478,6 @@ class LdapService {
|
||||
sessionToken,
|
||||
message: 'Authentication successful'
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
logger.error('❌ LDAP authentication error:', error)
|
||||
return {
|
||||
|
||||
@@ -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 {
|
||||
@@ -124,8 +209,12 @@ class UserService {
|
||||
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,7 +337,9 @@ 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)
|
||||
|
||||
@@ -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
|
||||
@@ -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')
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user