fix: API key limit now only counts active keys and uses config value

- Modified API key limit to count only active (non-deleted) keys instead of all keys
- Fixed frontend to use MAX_API_KEYS_PER_USER environment variable instead of hardcoded value
- Added activeApiKeysCount computed property to filter deleted keys
- Updated user profile endpoint to include maxApiKeysPerUser config
- Enhanced user store to persist and retrieve config values

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Feng Yue
2025-08-15 13:36:05 +08:00
parent 94eed70cf2
commit 71ce1e33b7
3 changed files with 19 additions and 4 deletions

View File

@@ -116,6 +116,9 @@ router.get('/profile', authenticateUser, async (req, res) => {
lastLoginAt: user.lastLoginAt, lastLoginAt: user.lastLoginAt,
apiKeyCount: user.apiKeyCount, apiKeyCount: user.apiKeyCount,
totalUsage: user.totalUsage totalUsage: user.totalUsage
},
config: {
maxApiKeysPerUser: config.userManagement.maxApiKeysPerUser
} }
}) })
} catch (error) { } catch (error) {

View File

@@ -10,7 +10,7 @@
<div class="mt-4 sm:ml-16 sm:mt-0 sm:flex-none"> <div class="mt-4 sm:ml-16 sm:mt-0 sm:flex-none">
<button <button
class="inline-flex items-center justify-center rounded-md border border-transparent bg-blue-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 sm:w-auto" class="inline-flex items-center justify-center rounded-md border border-transparent bg-blue-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 sm:w-auto"
:disabled="apiKeys.length >= maxApiKeys" :disabled="activeApiKeysCount >= maxApiKeys"
@click="showCreateModal = true" @click="showCreateModal = true"
> >
<svg class="-ml-1 mr-2 h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg class="-ml-1 mr-2 h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -28,7 +28,7 @@
<!-- API Keys 数量限制提示 --> <!-- API Keys 数量限制提示 -->
<div <div
v-if="apiKeys.length >= maxApiKeys" v-if="activeApiKeysCount >= maxApiKeys"
class="rounded-md border border-yellow-200 bg-yellow-50 p-4" class="rounded-md border border-yellow-200 bg-yellow-50 p-4"
> >
<div class="flex"> <div class="flex">
@@ -254,7 +254,7 @@ const userStore = useUserStore()
const loading = ref(true) const loading = ref(true)
const apiKeys = ref([]) const apiKeys = ref([])
const maxApiKeys = ref(5) // 从配置获取 const maxApiKeys = computed(() => userStore.config?.maxApiKeysPerUser || 5)
const showCreateModal = ref(false) const showCreateModal = ref(false)
const showViewModal = ref(false) const showViewModal = ref(false)
@@ -270,6 +270,11 @@ const sortedApiKeys = computed(() => {
}) })
}) })
// Computed property to count only active (non-deleted) API keys
const activeApiKeysCount = computed(() => {
return apiKeys.value.filter((key) => !(key.isDeleted === 'true' || key.deletedAt)).length
})
const formatNumber = (num) => { const formatNumber = (num) => {
if (num >= 1000000) { if (num >= 1000000) {
return (num / 1000000).toFixed(1) + 'M' return (num / 1000000).toFixed(1) + 'M'

View File

@@ -9,7 +9,8 @@ export const useUserStore = defineStore('user', {
user: null, user: null,
isAuthenticated: false, isAuthenticated: false,
sessionToken: null, sessionToken: null,
loading: false loading: false,
config: null
}), }),
getters: { getters: {
@@ -72,6 +73,7 @@ export const useUserStore = defineStore('user', {
async checkAuth() { async checkAuth() {
const token = localStorage.getItem('userToken') const token = localStorage.getItem('userToken')
const userData = localStorage.getItem('userData') const userData = localStorage.getItem('userData')
const userConfig = localStorage.getItem('userConfig')
if (!token || !userData) { if (!token || !userData) {
this.clearAuth() this.clearAuth()
@@ -81,6 +83,7 @@ export const useUserStore = defineStore('user', {
try { try {
this.sessionToken = token this.sessionToken = token
this.user = JSON.parse(userData) this.user = JSON.parse(userData)
this.config = userConfig ? JSON.parse(userConfig) : null
this.isAuthenticated = true this.isAuthenticated = true
this.setAuthHeader() this.setAuthHeader()
@@ -101,7 +104,9 @@ export const useUserStore = defineStore('user', {
if (response.data.success) { if (response.data.success) {
this.user = response.data.user this.user = response.data.user
this.config = response.data.config
localStorage.setItem('userData', JSON.stringify(this.user)) localStorage.setItem('userData', JSON.stringify(this.user))
localStorage.setItem('userConfig', JSON.stringify(this.config))
return response.data.user return response.data.user
} }
} catch (error) { } catch (error) {
@@ -170,9 +175,11 @@ export const useUserStore = defineStore('user', {
this.user = null this.user = null
this.sessionToken = null this.sessionToken = null
this.isAuthenticated = false this.isAuthenticated = false
this.config = null
localStorage.removeItem('userToken') localStorage.removeItem('userToken')
localStorage.removeItem('userData') localStorage.removeItem('userData')
localStorage.removeItem('userConfig')
// 清除 axios 默认头部 // 清除 axios 默认头部
delete axios.defaults.headers.common['x-user-token'] delete axios.defaults.headers.common['x-user-token']