mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-22 16:43:35 +00:00
docs: codex cli配置优先使用apikey
This commit is contained in:
@@ -483,6 +483,7 @@ model_provider = "crs"
|
|||||||
model = "gpt-5"
|
model = "gpt-5"
|
||||||
model_reasoning_effort = "high"
|
model_reasoning_effort = "high"
|
||||||
disable_response_storage = true
|
disable_response_storage = true
|
||||||
|
preferred_auth_method = "apikey"
|
||||||
|
|
||||||
[model_providers.crs]
|
[model_providers.crs]
|
||||||
name = "crs"
|
name = "crs"
|
||||||
@@ -494,7 +495,7 @@ wire_api = "responses"
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"OPENAI_API_KEY": "你的后台创建的API密钥"
|
"OPENAI_API_KEY": "你的后台创建的API密钥"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -31,8 +31,8 @@ router.post('/api/get-key-id', async (req, res) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 验证API Key
|
// 验证API Key(使用不触发激活的验证方法)
|
||||||
const validation = await apiKeyService.validateApiKey(apiKey)
|
const validation = await apiKeyService.validateApiKeyForStats(apiKey)
|
||||||
|
|
||||||
if (!validation.valid) {
|
if (!validation.valid) {
|
||||||
const clientIP = req.ip || req.connection?.remoteAddress || 'unknown'
|
const clientIP = req.ip || req.connection?.remoteAddress || 'unknown'
|
||||||
@@ -146,6 +146,11 @@ router.post('/api/user-stats', async (req, res) => {
|
|||||||
enableClientRestriction: keyData.enableClientRestriction === 'true',
|
enableClientRestriction: keyData.enableClientRestriction === 'true',
|
||||||
allowedClients,
|
allowedClients,
|
||||||
permissions: keyData.permissions || 'all',
|
permissions: keyData.permissions || 'all',
|
||||||
|
// 添加激活相关字段
|
||||||
|
expirationMode: keyData.expirationMode || 'fixed',
|
||||||
|
isActivated: keyData.isActivated === 'true',
|
||||||
|
activationDays: parseInt(keyData.activationDays || 0),
|
||||||
|
activatedAt: keyData.activatedAt || null,
|
||||||
usage // 使用完整的 usage 数据,而不是只有 total
|
usage // 使用完整的 usage 数据,而不是只有 total
|
||||||
}
|
}
|
||||||
} else if (apiKey) {
|
} else if (apiKey) {
|
||||||
@@ -158,8 +163,8 @@ router.post('/api/user-stats', async (req, res) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 验证API Key(重用现有的验证逻辑)
|
// 验证API Key(使用不触发激活的验证方法)
|
||||||
const validation = await apiKeyService.validateApiKey(apiKey)
|
const validation = await apiKeyService.validateApiKeyForStats(apiKey)
|
||||||
|
|
||||||
if (!validation.valid) {
|
if (!validation.valid) {
|
||||||
const clientIP = req.ip || req.connection?.remoteAddress || 'unknown'
|
const clientIP = req.ip || req.connection?.remoteAddress || 'unknown'
|
||||||
@@ -335,6 +340,11 @@ router.post('/api/user-stats', async (req, res) => {
|
|||||||
isActive: true, // 如果能通过validateApiKey验证,说明一定是激活的
|
isActive: true, // 如果能通过validateApiKey验证,说明一定是激活的
|
||||||
createdAt: keyData.createdAt,
|
createdAt: keyData.createdAt,
|
||||||
expiresAt: keyData.expiresAt,
|
expiresAt: keyData.expiresAt,
|
||||||
|
// 添加激活相关字段
|
||||||
|
expirationMode: keyData.expirationMode || 'fixed',
|
||||||
|
isActivated: keyData.isActivated === 'true',
|
||||||
|
activationDays: parseInt(keyData.activationDays || 0),
|
||||||
|
activatedAt: keyData.activatedAt || null,
|
||||||
permissions: fullKeyData.permissions,
|
permissions: fullKeyData.permissions,
|
||||||
|
|
||||||
// 使用统计(使用验证结果中的完整数据)
|
// 使用统计(使用验证结果中的完整数据)
|
||||||
|
|||||||
@@ -256,6 +256,126 @@ class ApiKeyService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 🔍 验证API Key(仅用于统计查询,不触发激活)
|
||||||
|
async validateApiKeyForStats(apiKey) {
|
||||||
|
try {
|
||||||
|
if (!apiKey || !apiKey.startsWith(this.prefix)) {
|
||||||
|
return { valid: false, error: 'Invalid API key format' }
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算API Key的哈希值
|
||||||
|
const hashedKey = this._hashApiKey(apiKey)
|
||||||
|
|
||||||
|
// 通过哈希值直接查找API Key(性能优化)
|
||||||
|
const keyData = await redis.findApiKeyByHash(hashedKey)
|
||||||
|
|
||||||
|
if (!keyData) {
|
||||||
|
return { valid: false, error: 'API key not found' }
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否激活
|
||||||
|
if (keyData.isActive !== 'true') {
|
||||||
|
return { valid: false, error: 'API key is disabled' }
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注意:这里不处理激活逻辑,保持 API Key 的未激活状态
|
||||||
|
|
||||||
|
// 检查是否过期(仅对已激活的 Key 检查)
|
||||||
|
if (
|
||||||
|
keyData.isActivated === 'true' &&
|
||||||
|
keyData.expiresAt &&
|
||||||
|
new Date() > new Date(keyData.expiresAt)
|
||||||
|
) {
|
||||||
|
return { valid: false, error: 'API key has expired' }
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果API Key属于某个用户,检查用户是否被禁用
|
||||||
|
if (keyData.userId) {
|
||||||
|
try {
|
||||||
|
const userService = require('./userService')
|
||||||
|
const user = await userService.getUserById(keyData.userId, false)
|
||||||
|
if (!user || !user.isActive) {
|
||||||
|
return { valid: false, error: 'User account is disabled' }
|
||||||
|
}
|
||||||
|
} catch (userError) {
|
||||||
|
// 如果用户服务出错,记录但不影响API Key验证
|
||||||
|
logger.warn(`Failed to check user status for API key ${keyData.id}:`, userError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取当日费用
|
||||||
|
const dailyCost = (await redis.getDailyCost(keyData.id)) || 0
|
||||||
|
|
||||||
|
// 获取使用统计
|
||||||
|
const usage = await redis.getUsageStats(keyData.id)
|
||||||
|
|
||||||
|
// 解析限制模型数据
|
||||||
|
let restrictedModels = []
|
||||||
|
try {
|
||||||
|
restrictedModels = keyData.restrictedModels ? JSON.parse(keyData.restrictedModels) : []
|
||||||
|
} catch (e) {
|
||||||
|
restrictedModels = []
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析允许的客户端
|
||||||
|
let allowedClients = []
|
||||||
|
try {
|
||||||
|
allowedClients = keyData.allowedClients ? JSON.parse(keyData.allowedClients) : []
|
||||||
|
} catch (e) {
|
||||||
|
allowedClients = []
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析标签
|
||||||
|
let tags = []
|
||||||
|
try {
|
||||||
|
tags = keyData.tags ? JSON.parse(keyData.tags) : []
|
||||||
|
} catch (e) {
|
||||||
|
tags = []
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
valid: true,
|
||||||
|
keyData: {
|
||||||
|
id: keyData.id,
|
||||||
|
name: keyData.name,
|
||||||
|
description: keyData.description,
|
||||||
|
createdAt: keyData.createdAt,
|
||||||
|
expiresAt: keyData.expiresAt,
|
||||||
|
// 添加激活相关字段
|
||||||
|
expirationMode: keyData.expirationMode || 'fixed',
|
||||||
|
isActivated: keyData.isActivated === 'true',
|
||||||
|
activationDays: parseInt(keyData.activationDays || 0),
|
||||||
|
activatedAt: keyData.activatedAt || null,
|
||||||
|
claudeAccountId: keyData.claudeAccountId,
|
||||||
|
claudeConsoleAccountId: keyData.claudeConsoleAccountId,
|
||||||
|
geminiAccountId: keyData.geminiAccountId,
|
||||||
|
openaiAccountId: keyData.openaiAccountId,
|
||||||
|
azureOpenaiAccountId: keyData.azureOpenaiAccountId,
|
||||||
|
bedrockAccountId: keyData.bedrockAccountId,
|
||||||
|
permissions: keyData.permissions || 'all',
|
||||||
|
tokenLimit: parseInt(keyData.tokenLimit),
|
||||||
|
concurrencyLimit: parseInt(keyData.concurrencyLimit || 0),
|
||||||
|
rateLimitWindow: parseInt(keyData.rateLimitWindow || 0),
|
||||||
|
rateLimitRequests: parseInt(keyData.rateLimitRequests || 0),
|
||||||
|
rateLimitCost: parseFloat(keyData.rateLimitCost || 0),
|
||||||
|
enableModelRestriction: keyData.enableModelRestriction === 'true',
|
||||||
|
restrictedModels,
|
||||||
|
enableClientRestriction: keyData.enableClientRestriction === 'true',
|
||||||
|
allowedClients,
|
||||||
|
dailyCostLimit: parseFloat(keyData.dailyCostLimit || 0),
|
||||||
|
weeklyOpusCostLimit: parseFloat(keyData.weeklyOpusCostLimit || 0),
|
||||||
|
dailyCost: dailyCost || 0,
|
||||||
|
weeklyOpusCost: (await redis.getWeeklyOpusCost(keyData.id)) || 0,
|
||||||
|
tags,
|
||||||
|
usage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('❌ API key validation error (stats):', error)
|
||||||
|
return { valid: false, error: 'Internal validation error' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 📋 获取所有API Keys
|
// 📋 获取所有API Keys
|
||||||
async getAllApiKeys(includeDeleted = false) {
|
async getAllApiKeys(includeDeleted = false) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -115,7 +115,19 @@
|
|||||||
<span class="mt-1 flex-shrink-0 text-sm text-gray-600 dark:text-gray-400 md:text-base"
|
<span class="mt-1 flex-shrink-0 text-sm text-gray-600 dark:text-gray-400 md:text-base"
|
||||||
>过期时间</span
|
>过期时间</span
|
||||||
>
|
>
|
||||||
<div v-if="statsData.expiresAt" class="text-right">
|
<!-- 未激活状态 -->
|
||||||
|
<div
|
||||||
|
v-if="statsData.expirationMode === 'activation' && !statsData.isActivated"
|
||||||
|
class="text-sm font-medium text-amber-600 dark:text-amber-500 md:text-base"
|
||||||
|
>
|
||||||
|
<i class="fas fa-pause-circle mr-1 text-xs md:text-sm" />
|
||||||
|
未激活
|
||||||
|
<span class="ml-1 text-xs text-gray-500 dark:text-gray-400"
|
||||||
|
>(首次使用后{{ statsData.activationDays || 30 }}天过期)</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<!-- 已设置过期时间 -->
|
||||||
|
<div v-else-if="statsData.expiresAt" class="text-right">
|
||||||
<div
|
<div
|
||||||
v-if="isApiKeyExpired(statsData.expiresAt)"
|
v-if="isApiKeyExpired(statsData.expiresAt)"
|
||||||
class="text-sm font-medium text-red-600 md:text-base"
|
class="text-sm font-medium text-red-600 md:text-base"
|
||||||
@@ -137,6 +149,7 @@
|
|||||||
{{ formatExpireDate(statsData.expiresAt) }}
|
{{ formatExpireDate(statsData.expiresAt) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- 永不过期 -->
|
||||||
<div v-else class="text-sm font-medium text-gray-400 dark:text-gray-500 md:text-base">
|
<div v-else class="text-sm font-medium text-gray-400 dark:text-gray-500 md:text-base">
|
||||||
<i class="fas fa-infinity mr-1 text-xs md:text-sm" />
|
<i class="fas fa-infinity mr-1 text-xs md:text-sm" />
|
||||||
永不过期
|
永不过期
|
||||||
|
|||||||
@@ -434,6 +434,7 @@
|
|||||||
<div class="whitespace-nowrap text-gray-300">model = "gpt-5"</div>
|
<div class="whitespace-nowrap text-gray-300">model = "gpt-5"</div>
|
||||||
<div class="whitespace-nowrap text-gray-300">model_reasoning_effort = "high"</div>
|
<div class="whitespace-nowrap text-gray-300">model_reasoning_effort = "high"</div>
|
||||||
<div class="whitespace-nowrap text-gray-300">disable_response_storage = true</div>
|
<div class="whitespace-nowrap text-gray-300">disable_response_storage = true</div>
|
||||||
|
<div class="whitespace-nowrap text-gray-300">preferred_auth_method = "apikey"</div>
|
||||||
<div class="mt-2"></div>
|
<div class="mt-2"></div>
|
||||||
<div class="whitespace-nowrap text-gray-300">[model_providers.crs]</div>
|
<div class="whitespace-nowrap text-gray-300">[model_providers.crs]</div>
|
||||||
<div class="whitespace-nowrap text-gray-300">name = "crs"</div>
|
<div class="whitespace-nowrap text-gray-300">name = "crs"</div>
|
||||||
@@ -920,6 +921,7 @@
|
|||||||
<div class="whitespace-nowrap text-gray-300">model = "gpt-5"</div>
|
<div class="whitespace-nowrap text-gray-300">model = "gpt-5"</div>
|
||||||
<div class="whitespace-nowrap text-gray-300">model_reasoning_effort = "high"</div>
|
<div class="whitespace-nowrap text-gray-300">model_reasoning_effort = "high"</div>
|
||||||
<div class="whitespace-nowrap text-gray-300">disable_response_storage = true</div>
|
<div class="whitespace-nowrap text-gray-300">disable_response_storage = true</div>
|
||||||
|
<div class="whitespace-nowrap text-gray-300">preferred_auth_method = "apikey"</div>
|
||||||
<div class="mt-2"></div>
|
<div class="mt-2"></div>
|
||||||
<div class="whitespace-nowrap text-gray-300">[model_providers.crs]</div>
|
<div class="whitespace-nowrap text-gray-300">[model_providers.crs]</div>
|
||||||
<div class="whitespace-nowrap text-gray-300">name = "crs"</div>
|
<div class="whitespace-nowrap text-gray-300">name = "crs"</div>
|
||||||
@@ -1397,6 +1399,7 @@
|
|||||||
<div class="whitespace-nowrap text-gray-300">model = "gpt-5"</div>
|
<div class="whitespace-nowrap text-gray-300">model = "gpt-5"</div>
|
||||||
<div class="whitespace-nowrap text-gray-300">model_reasoning_effort = "high"</div>
|
<div class="whitespace-nowrap text-gray-300">model_reasoning_effort = "high"</div>
|
||||||
<div class="whitespace-nowrap text-gray-300">disable_response_storage = true</div>
|
<div class="whitespace-nowrap text-gray-300">disable_response_storage = true</div>
|
||||||
|
<div class="whitespace-nowrap text-gray-300">preferred_auth_method = "apikey"</div>
|
||||||
<div class="mt-2"></div>
|
<div class="mt-2"></div>
|
||||||
<div class="whitespace-nowrap text-gray-300">[model_providers.crs]</div>
|
<div class="whitespace-nowrap text-gray-300">[model_providers.crs]</div>
|
||||||
<div class="whitespace-nowrap text-gray-300">name = "crs"</div>
|
<div class="whitespace-nowrap text-gray-300">name = "crs"</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user