feat: Codex账号管理优化与API Key激活机制

 新功能
- 支持通过refreshToken新增Codex账号,创建时立即验证token有效性
- API Key新增首次使用自动激活机制,支持activation模式设置有效期
- 前端账号表单增加token验证功能,确保账号创建成功

🐛 修复
- 修复Codex token刷新失败问题,增加分布式锁防止并发刷新
- 优化token刷新错误处理,提供更详细的错误信息和建议
- 修复OpenAI账号token过期检测和自动刷新逻辑

📝 文档更新
- 更新README中Codex使用说明,改为config.toml配置方式
- 优化Cherry Studio等第三方工具接入文档
- 添加详细的配置示例和账号类型说明

🎨 界面优化
- 改进账号创建表单UI,支持手动和OAuth两种模式
- 优化API Key过期时间编辑弹窗,支持激活操作
- 调整教程页面布局,提升移动端响应式体验

💡 代码改进
- 重构token刷新服务,增强错误处理和重试机制
- 优化代理配置处理,确保OAuth请求正确使用代理
- 改进webhook通知,增加token刷新失败告警
This commit is contained in:
shaw
2025-09-06 17:39:05 +08:00
parent 0e746b1056
commit d2f3f6866c
19 changed files with 1231 additions and 463 deletions

View File

@@ -570,7 +570,16 @@
</td>
<td class="whitespace-nowrap px-3 py-4 text-sm">
<div class="inline-flex items-center gap-1.5">
<span v-if="key.expiresAt">
<!-- 未激活状态 -->
<span
v-if="key.expirationMode === 'activation' && !key.isActivated"
class="inline-flex items-center text-blue-600 dark:text-blue-400"
>
<i class="fas fa-pause-circle mr-1" />
未激活 ({{ key.activationDays || 30 }}天)
</span>
<!-- 已设置过期时间 -->
<span v-else-if="key.expiresAt">
<span
v-if="isApiKeyExpired(key.expiresAt)"
class="inline-flex items-center text-red-600"
@@ -589,6 +598,7 @@
{{ formatExpireDate(key.expiresAt) }}
</span>
</span>
<!-- 永不过期 -->
<span
v-else
class="inline-flex items-center text-gray-400 dark:text-gray-500"
@@ -2650,18 +2660,29 @@ const closeExpiryEdit = () => {
}
// 保存过期时间
const handleSaveExpiry = async ({ keyId, expiresAt }) => {
const handleSaveExpiry = async ({ keyId, expiresAt, activateNow }) => {
try {
const data = await apiClient.put(`/admin/api-keys/${keyId}`, {
expiresAt: expiresAt || null
// 使用新的PATCH端点来修改过期时间
const data = await apiClient.patch(`/admin/api-keys/${keyId}/expiration`, {
expiresAt: expiresAt || null,
activateNow: activateNow || false
})
if (data.success) {
showToast('过期时间已更新', 'success')
showToast(activateNow ? 'API Key已激活' : '过期时间已更新', 'success')
// 更新本地数据
const key = apiKeys.value.find((k) => k.id === keyId)
if (key) {
key.expiresAt = expiresAt || null
if (activateNow && data.updates) {
key.isActivated = true
key.activatedAt = data.updates.activatedAt
key.expiresAt = data.updates.expiresAt
} else {
key.expiresAt = expiresAt || null
if (expiresAt && !key.isActivated) {
key.isActivated = true
}
}
}
closeExpiryEdit()
} else {