mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-22 16:43:35 +00:00
fix: 继续修复PR-541遗留的系列bug
This commit is contained in:
@@ -2267,7 +2267,8 @@ router.post('/claude-accounts', authenticateAdmin, async (req, res) => {
|
|||||||
useUnifiedUserAgent,
|
useUnifiedUserAgent,
|
||||||
useUnifiedClientId,
|
useUnifiedClientId,
|
||||||
unifiedClientId,
|
unifiedClientId,
|
||||||
expiresAt
|
expiresAt,
|
||||||
|
subscriptionExpiresAt
|
||||||
} = req.body
|
} = req.body
|
||||||
|
|
||||||
if (!name) {
|
if (!name) {
|
||||||
@@ -2311,7 +2312,7 @@ router.post('/claude-accounts', authenticateAdmin, async (req, res) => {
|
|||||||
useUnifiedUserAgent: useUnifiedUserAgent === true, // 默认为false
|
useUnifiedUserAgent: useUnifiedUserAgent === true, // 默认为false
|
||||||
useUnifiedClientId: useUnifiedClientId === true, // 默认为false
|
useUnifiedClientId: useUnifiedClientId === true, // 默认为false
|
||||||
unifiedClientId: unifiedClientId || '', // 统一的客户端标识
|
unifiedClientId: unifiedClientId || '', // 统一的客户端标识
|
||||||
expiresAt: expiresAt || null // 账户订阅到期时间
|
expiresAt: subscriptionExpiresAt ?? expiresAt ?? null // 账户订阅到期时间
|
||||||
})
|
})
|
||||||
|
|
||||||
// 如果是分组类型,将账户添加到分组
|
// 如果是分组类型,将账户添加到分组
|
||||||
@@ -2400,8 +2401,12 @@ router.put('/claude-accounts/:accountId', authenticateAdmin, async (req, res) =>
|
|||||||
|
|
||||||
// 映射字段名:前端的expiresAt -> 后端的subscriptionExpiresAt
|
// 映射字段名:前端的expiresAt -> 后端的subscriptionExpiresAt
|
||||||
const mappedUpdates = { ...updates }
|
const mappedUpdates = { ...updates }
|
||||||
if ('expiresAt' in mappedUpdates) {
|
if (Object.prototype.hasOwnProperty.call(updates, 'subscriptionExpiresAt')) {
|
||||||
|
mappedUpdates.subscriptionExpiresAt = updates.subscriptionExpiresAt
|
||||||
|
} else if (Object.prototype.hasOwnProperty.call(mappedUpdates, 'expiresAt')) {
|
||||||
mappedUpdates.subscriptionExpiresAt = mappedUpdates.expiresAt
|
mappedUpdates.subscriptionExpiresAt = mappedUpdates.expiresAt
|
||||||
|
}
|
||||||
|
if (Object.prototype.hasOwnProperty.call(mappedUpdates, 'subscriptionExpiresAt')) {
|
||||||
delete mappedUpdates.expiresAt
|
delete mappedUpdates.expiresAt
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2797,8 +2802,12 @@ router.put('/claude-console-accounts/:accountId', authenticateAdmin, async (req,
|
|||||||
|
|
||||||
// 映射字段名:前端的expiresAt -> 后端的subscriptionExpiresAt
|
// 映射字段名:前端的expiresAt -> 后端的subscriptionExpiresAt
|
||||||
const mappedUpdates = { ...updates }
|
const mappedUpdates = { ...updates }
|
||||||
if ('expiresAt' in mappedUpdates) {
|
if (Object.prototype.hasOwnProperty.call(updates, 'subscriptionExpiresAt')) {
|
||||||
|
mappedUpdates.subscriptionExpiresAt = updates.subscriptionExpiresAt
|
||||||
|
} else if (Object.prototype.hasOwnProperty.call(mappedUpdates, 'expiresAt')) {
|
||||||
mappedUpdates.subscriptionExpiresAt = mappedUpdates.expiresAt
|
mappedUpdates.subscriptionExpiresAt = mappedUpdates.expiresAt
|
||||||
|
}
|
||||||
|
if (Object.prototype.hasOwnProperty.call(mappedUpdates, 'subscriptionExpiresAt')) {
|
||||||
delete mappedUpdates.expiresAt
|
delete mappedUpdates.expiresAt
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3214,8 +3223,12 @@ router.put('/ccr-accounts/:accountId', authenticateAdmin, async (req, res) => {
|
|||||||
|
|
||||||
// 映射字段名:前端的expiresAt -> 后端的subscriptionExpiresAt
|
// 映射字段名:前端的expiresAt -> 后端的subscriptionExpiresAt
|
||||||
const mappedUpdates = { ...updates }
|
const mappedUpdates = { ...updates }
|
||||||
if ('expiresAt' in mappedUpdates) {
|
if (Object.prototype.hasOwnProperty.call(updates, 'subscriptionExpiresAt')) {
|
||||||
|
mappedUpdates.subscriptionExpiresAt = updates.subscriptionExpiresAt
|
||||||
|
} else if (Object.prototype.hasOwnProperty.call(mappedUpdates, 'expiresAt')) {
|
||||||
mappedUpdates.subscriptionExpiresAt = mappedUpdates.expiresAt
|
mappedUpdates.subscriptionExpiresAt = mappedUpdates.expiresAt
|
||||||
|
}
|
||||||
|
if (Object.prototype.hasOwnProperty.call(mappedUpdates, 'subscriptionExpiresAt')) {
|
||||||
delete mappedUpdates.expiresAt
|
delete mappedUpdates.expiresAt
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3582,8 +3595,12 @@ router.put('/bedrock-accounts/:accountId', authenticateAdmin, async (req, res) =
|
|||||||
|
|
||||||
// 映射字段名:前端的expiresAt -> 后端的subscriptionExpiresAt
|
// 映射字段名:前端的expiresAt -> 后端的subscriptionExpiresAt
|
||||||
const mappedUpdates = { ...updates }
|
const mappedUpdates = { ...updates }
|
||||||
if ('expiresAt' in mappedUpdates) {
|
if (Object.prototype.hasOwnProperty.call(updates, 'subscriptionExpiresAt')) {
|
||||||
|
mappedUpdates.subscriptionExpiresAt = updates.subscriptionExpiresAt
|
||||||
|
} else if (Object.prototype.hasOwnProperty.call(mappedUpdates, 'expiresAt')) {
|
||||||
mappedUpdates.subscriptionExpiresAt = mappedUpdates.expiresAt
|
mappedUpdates.subscriptionExpiresAt = mappedUpdates.expiresAt
|
||||||
|
}
|
||||||
|
if (Object.prototype.hasOwnProperty.call(mappedUpdates, 'subscriptionExpiresAt')) {
|
||||||
delete mappedUpdates.expiresAt
|
delete mappedUpdates.expiresAt
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3912,8 +3929,11 @@ router.get('/gemini-accounts', authenticateAdmin, async (req, res) => {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
...account,
|
...account,
|
||||||
// 映射字段:使用 subscriptionExpiresAt 作为前端显示的 expiresAt
|
expiresAt: account.expiresAt || null,
|
||||||
expiresAt: account.subscriptionExpiresAt || null,
|
subscriptionExpiresAt:
|
||||||
|
account.subscriptionExpiresAt && account.subscriptionExpiresAt !== ''
|
||||||
|
? account.subscriptionExpiresAt
|
||||||
|
: null,
|
||||||
groupInfos,
|
groupInfos,
|
||||||
usage: {
|
usage: {
|
||||||
daily: usageStats.daily,
|
daily: usageStats.daily,
|
||||||
@@ -3931,8 +3951,11 @@ router.get('/gemini-accounts', authenticateAdmin, async (req, res) => {
|
|||||||
const groupInfos = await accountGroupService.getAccountGroups(account.id)
|
const groupInfos = await accountGroupService.getAccountGroups(account.id)
|
||||||
return {
|
return {
|
||||||
...account,
|
...account,
|
||||||
// 映射字段:使用 subscriptionExpiresAt 作为前端显示的 expiresAt
|
expiresAt: account.expiresAt || null,
|
||||||
expiresAt: account.subscriptionExpiresAt || null,
|
subscriptionExpiresAt:
|
||||||
|
account.subscriptionExpiresAt && account.subscriptionExpiresAt !== ''
|
||||||
|
? account.subscriptionExpiresAt
|
||||||
|
: null,
|
||||||
groupInfos,
|
groupInfos,
|
||||||
usage: {
|
usage: {
|
||||||
daily: { tokens: 0, requests: 0, allTokens: 0 },
|
daily: { tokens: 0, requests: 0, allTokens: 0 },
|
||||||
@@ -3947,6 +3970,11 @@ router.get('/gemini-accounts', authenticateAdmin, async (req, res) => {
|
|||||||
)
|
)
|
||||||
return {
|
return {
|
||||||
...account,
|
...account,
|
||||||
|
expiresAt: account.expiresAt || null,
|
||||||
|
subscriptionExpiresAt:
|
||||||
|
account.subscriptionExpiresAt && account.subscriptionExpiresAt !== ''
|
||||||
|
? account.subscriptionExpiresAt
|
||||||
|
: null,
|
||||||
groupInfos: [],
|
groupInfos: [],
|
||||||
usage: {
|
usage: {
|
||||||
daily: { tokens: 0, requests: 0, allTokens: 0 },
|
daily: { tokens: 0, requests: 0, allTokens: 0 },
|
||||||
@@ -4059,8 +4087,12 @@ router.put('/gemini-accounts/:accountId', authenticateAdmin, async (req, res) =>
|
|||||||
|
|
||||||
// 映射字段名:前端的expiresAt -> 后端的subscriptionExpiresAt
|
// 映射字段名:前端的expiresAt -> 后端的subscriptionExpiresAt
|
||||||
const mappedUpdates = { ...updates }
|
const mappedUpdates = { ...updates }
|
||||||
if ('expiresAt' in mappedUpdates) {
|
if (Object.prototype.hasOwnProperty.call(updates, 'subscriptionExpiresAt')) {
|
||||||
|
mappedUpdates.subscriptionExpiresAt = updates.subscriptionExpiresAt
|
||||||
|
} else if (Object.prototype.hasOwnProperty.call(mappedUpdates, 'expiresAt')) {
|
||||||
mappedUpdates.subscriptionExpiresAt = mappedUpdates.expiresAt
|
mappedUpdates.subscriptionExpiresAt = mappedUpdates.expiresAt
|
||||||
|
}
|
||||||
|
if (Object.prototype.hasOwnProperty.call(mappedUpdates, 'subscriptionExpiresAt')) {
|
||||||
delete mappedUpdates.expiresAt
|
delete mappedUpdates.expiresAt
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -7270,7 +7302,8 @@ router.post('/openai-accounts', authenticateAdmin, async (req, res) => {
|
|||||||
rateLimitDuration,
|
rateLimitDuration,
|
||||||
priority,
|
priority,
|
||||||
needsImmediateRefresh, // 是否需要立即刷新
|
needsImmediateRefresh, // 是否需要立即刷新
|
||||||
requireRefreshSuccess // 是否必须刷新成功才能创建
|
requireRefreshSuccess, // 是否必须刷新成功才能创建
|
||||||
|
subscriptionExpiresAt
|
||||||
} = req.body
|
} = req.body
|
||||||
|
|
||||||
if (!name) {
|
if (!name) {
|
||||||
@@ -7292,7 +7325,8 @@ router.post('/openai-accounts', authenticateAdmin, async (req, res) => {
|
|||||||
accountInfo: accountInfo || {},
|
accountInfo: accountInfo || {},
|
||||||
proxy: proxy || null,
|
proxy: proxy || null,
|
||||||
isActive: true,
|
isActive: true,
|
||||||
schedulable: true
|
schedulable: true,
|
||||||
|
subscriptionExpiresAt: subscriptionExpiresAt || null
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果需要立即刷新且必须成功(OpenAI 手动模式)
|
// 如果需要立即刷新且必须成功(OpenAI 手动模式)
|
||||||
@@ -7571,10 +7605,16 @@ router.put('/openai-accounts/:id', authenticateAdmin, async (req, res) => {
|
|||||||
: currentAccount.emailVerified
|
: currentAccount.emailVerified
|
||||||
}
|
}
|
||||||
|
|
||||||
// 映射字段名:前端的expiresAt -> 后端的subscriptionExpiresAt (订阅过期时间)
|
const hasOauthExpiry = Boolean(updates.openaiOauth?.expires_in)
|
||||||
// 注意:这里不影响上面 OAuth token 的 expiresAt 字段
|
|
||||||
if ('expiresAt' in updates && !updates.openaiOauth?.expires_in) {
|
// 处理订阅过期时间字段:优先使用 subscriptionExpiresAt,兼容旧版的 expiresAt
|
||||||
|
if (Object.prototype.hasOwnProperty.call(updates, 'subscriptionExpiresAt')) {
|
||||||
|
updateData.subscriptionExpiresAt = updates.subscriptionExpiresAt
|
||||||
|
} else if (Object.prototype.hasOwnProperty.call(updates, 'expiresAt') && !hasOauthExpiry) {
|
||||||
updateData.subscriptionExpiresAt = updates.expiresAt
|
updateData.subscriptionExpiresAt = updates.expiresAt
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasOauthExpiry && Object.prototype.hasOwnProperty.call(updateData, 'subscriptionExpiresAt')) {
|
||||||
delete updateData.expiresAt
|
delete updateData.expiresAt
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -7966,8 +8006,12 @@ router.put('/azure-openai-accounts/:id', authenticateAdmin, async (req, res) =>
|
|||||||
|
|
||||||
// 映射字段名:前端的expiresAt -> 后端的subscriptionExpiresAt
|
// 映射字段名:前端的expiresAt -> 后端的subscriptionExpiresAt
|
||||||
const mappedUpdates = { ...updates }
|
const mappedUpdates = { ...updates }
|
||||||
if ('expiresAt' in mappedUpdates) {
|
if (Object.prototype.hasOwnProperty.call(updates, 'subscriptionExpiresAt')) {
|
||||||
|
mappedUpdates.subscriptionExpiresAt = updates.subscriptionExpiresAt
|
||||||
|
} else if (Object.prototype.hasOwnProperty.call(mappedUpdates, 'expiresAt')) {
|
||||||
mappedUpdates.subscriptionExpiresAt = mappedUpdates.expiresAt
|
mappedUpdates.subscriptionExpiresAt = mappedUpdates.expiresAt
|
||||||
|
}
|
||||||
|
if (Object.prototype.hasOwnProperty.call(mappedUpdates, 'subscriptionExpiresAt')) {
|
||||||
delete mappedUpdates.expiresAt
|
delete mappedUpdates.expiresAt
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -8346,8 +8390,12 @@ router.put('/openai-responses-accounts/:id', authenticateAdmin, async (req, res)
|
|||||||
|
|
||||||
// 映射字段名:前端的expiresAt -> 后端的subscriptionExpiresAt
|
// 映射字段名:前端的expiresAt -> 后端的subscriptionExpiresAt
|
||||||
const mappedUpdates = { ...updates }
|
const mappedUpdates = { ...updates }
|
||||||
if ('expiresAt' in mappedUpdates) {
|
if (Object.prototype.hasOwnProperty.call(updates, 'subscriptionExpiresAt')) {
|
||||||
|
mappedUpdates.subscriptionExpiresAt = updates.subscriptionExpiresAt
|
||||||
|
} else if (Object.prototype.hasOwnProperty.call(mappedUpdates, 'expiresAt')) {
|
||||||
mappedUpdates.subscriptionExpiresAt = mappedUpdates.expiresAt
|
mappedUpdates.subscriptionExpiresAt = mappedUpdates.expiresAt
|
||||||
|
}
|
||||||
|
if (Object.prototype.hasOwnProperty.call(mappedUpdates, 'subscriptionExpiresAt')) {
|
||||||
delete mappedUpdates.expiresAt
|
delete mappedUpdates.expiresAt
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -8717,9 +8765,11 @@ router.get('/droid-accounts', authenticateAdmin, async (req, res) => {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
...account,
|
...account,
|
||||||
// 映射字段:使用 subscriptionExpiresAt 作为前端显示的 expiresAt
|
expiresAt: account.expiresAt || null,
|
||||||
// OAuth token 的原始 expiresAt 保留在内部使用
|
subscriptionExpiresAt:
|
||||||
expiresAt: account.subscriptionExpiresAt || null,
|
account.subscriptionExpiresAt && account.subscriptionExpiresAt !== ''
|
||||||
|
? account.subscriptionExpiresAt
|
||||||
|
: null,
|
||||||
schedulable: account.schedulable === 'true',
|
schedulable: account.schedulable === 'true',
|
||||||
boundApiKeysCount,
|
boundApiKeysCount,
|
||||||
groupInfos,
|
groupInfos,
|
||||||
@@ -8733,8 +8783,11 @@ router.get('/droid-accounts', authenticateAdmin, async (req, res) => {
|
|||||||
logger.warn(`Failed to get stats for Droid account ${account.id}:`, error.message)
|
logger.warn(`Failed to get stats for Droid account ${account.id}:`, error.message)
|
||||||
return {
|
return {
|
||||||
...account,
|
...account,
|
||||||
// 映射字段:使用 subscriptionExpiresAt 作为前端显示的 expiresAt
|
expiresAt: account.expiresAt || null,
|
||||||
expiresAt: account.subscriptionExpiresAt || null,
|
subscriptionExpiresAt:
|
||||||
|
account.subscriptionExpiresAt && account.subscriptionExpiresAt !== ''
|
||||||
|
? account.subscriptionExpiresAt
|
||||||
|
: null,
|
||||||
boundApiKeysCount: 0,
|
boundApiKeysCount: 0,
|
||||||
groupInfos: [],
|
groupInfos: [],
|
||||||
usage: {
|
usage: {
|
||||||
@@ -8851,8 +8904,12 @@ router.put('/droid-accounts/:id', authenticateAdmin, async (req, res) => {
|
|||||||
|
|
||||||
// 映射字段名:前端的expiresAt -> 后端的subscriptionExpiresAt
|
// 映射字段名:前端的expiresAt -> 后端的subscriptionExpiresAt
|
||||||
const mappedUpdates = { ...updates }
|
const mappedUpdates = { ...updates }
|
||||||
if ('expiresAt' in mappedUpdates) {
|
if (Object.prototype.hasOwnProperty.call(updates, 'subscriptionExpiresAt')) {
|
||||||
|
mappedUpdates.subscriptionExpiresAt = updates.subscriptionExpiresAt
|
||||||
|
} else if (Object.prototype.hasOwnProperty.call(mappedUpdates, 'expiresAt')) {
|
||||||
mappedUpdates.subscriptionExpiresAt = mappedUpdates.expiresAt
|
mappedUpdates.subscriptionExpiresAt = mappedUpdates.expiresAt
|
||||||
|
}
|
||||||
|
if (Object.prototype.hasOwnProperty.call(mappedUpdates, 'subscriptionExpiresAt')) {
|
||||||
delete mappedUpdates.expiresAt
|
delete mappedUpdates.expiresAt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -65,6 +65,19 @@ const AZURE_OPENAI_ACCOUNT_KEY_PREFIX = 'azure_openai:account:'
|
|||||||
const SHARED_AZURE_OPENAI_ACCOUNTS_KEY = 'shared_azure_openai_accounts'
|
const SHARED_AZURE_OPENAI_ACCOUNTS_KEY = 'shared_azure_openai_accounts'
|
||||||
const ACCOUNT_SESSION_MAPPING_PREFIX = 'azure_openai_session_account_mapping:'
|
const ACCOUNT_SESSION_MAPPING_PREFIX = 'azure_openai_session_account_mapping:'
|
||||||
|
|
||||||
|
function normalizeSubscriptionExpiresAt(value) {
|
||||||
|
if (value === undefined || value === null || value === '') {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const date = value instanceof Date ? value : new Date(value)
|
||||||
|
if (Number.isNaN(date.getTime())) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
return date.toISOString()
|
||||||
|
}
|
||||||
|
|
||||||
// 加密函数
|
// 加密函数
|
||||||
function encrypt(text) {
|
function encrypt(text) {
|
||||||
if (!text) {
|
if (!text) {
|
||||||
@@ -133,6 +146,9 @@ async function createAccount(accountData) {
|
|||||||
isActive: accountData.isActive !== false ? 'true' : 'false',
|
isActive: accountData.isActive !== false ? 'true' : 'false',
|
||||||
status: 'active',
|
status: 'active',
|
||||||
schedulable: accountData.schedulable !== false ? 'true' : 'false',
|
schedulable: accountData.schedulable !== false ? 'true' : 'false',
|
||||||
|
subscriptionExpiresAt: normalizeSubscriptionExpiresAt(
|
||||||
|
accountData.subscriptionExpiresAt || ''
|
||||||
|
),
|
||||||
createdAt: now,
|
createdAt: now,
|
||||||
updatedAt: now
|
updatedAt: now
|
||||||
}
|
}
|
||||||
@@ -152,7 +168,10 @@ async function createAccount(accountData) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
logger.info(`Created Azure OpenAI account: ${accountId}`)
|
logger.info(`Created Azure OpenAI account: ${accountId}`)
|
||||||
return account
|
return {
|
||||||
|
...account,
|
||||||
|
subscriptionExpiresAt: account.subscriptionExpiresAt || null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取账户
|
// 获取账户
|
||||||
@@ -187,6 +206,11 @@ async function getAccount(accountId) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
accountData.subscriptionExpiresAt =
|
||||||
|
accountData.subscriptionExpiresAt && accountData.subscriptionExpiresAt !== ''
|
||||||
|
? accountData.subscriptionExpiresAt
|
||||||
|
: null
|
||||||
|
|
||||||
return accountData
|
return accountData
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -218,6 +242,15 @@ async function updateAccount(accountId, updates) {
|
|||||||
: JSON.stringify(updates.supportedModels)
|
: JSON.stringify(updates.supportedModels)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Object.prototype.hasOwnProperty.call(updates, 'subscriptionExpiresAt')) {
|
||||||
|
updates.subscriptionExpiresAt = normalizeSubscriptionExpiresAt(
|
||||||
|
updates.subscriptionExpiresAt
|
||||||
|
)
|
||||||
|
} else if (Object.prototype.hasOwnProperty.call(updates, 'expiresAt')) {
|
||||||
|
updates.subscriptionExpiresAt = normalizeSubscriptionExpiresAt(updates.expiresAt)
|
||||||
|
delete updates.expiresAt
|
||||||
|
}
|
||||||
|
|
||||||
// 更新账户类型时处理共享账户集合
|
// 更新账户类型时处理共享账户集合
|
||||||
const client = redisClient.getClientSafe()
|
const client = redisClient.getClientSafe()
|
||||||
if (updates.accountType && updates.accountType !== existingAccount.accountType) {
|
if (updates.accountType && updates.accountType !== existingAccount.accountType) {
|
||||||
@@ -244,6 +277,10 @@ async function updateAccount(accountId, updates) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!updatedAccount.subscriptionExpiresAt) {
|
||||||
|
updatedAccount.subscriptionExpiresAt = null
|
||||||
|
}
|
||||||
|
|
||||||
return updatedAccount
|
return updatedAccount
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -303,7 +340,8 @@ async function getAllAccounts() {
|
|||||||
accounts.push({
|
accounts.push({
|
||||||
...accountData,
|
...accountData,
|
||||||
isActive: accountData.isActive === 'true',
|
isActive: accountData.isActive === 'true',
|
||||||
schedulable: accountData.schedulable !== 'false'
|
schedulable: accountData.schedulable !== 'false',
|
||||||
|
subscriptionExpiresAt: accountData.subscriptionExpiresAt || null
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,19 @@ const config = require('../../config/config')
|
|||||||
const bedrockRelayService = require('./bedrockRelayService')
|
const bedrockRelayService = require('./bedrockRelayService')
|
||||||
const LRUCache = require('../utils/lruCache')
|
const LRUCache = require('../utils/lruCache')
|
||||||
|
|
||||||
|
function normalizeSubscriptionExpiresAt(value) {
|
||||||
|
if (value === undefined || value === null || value === '') {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const date = value instanceof Date ? value : new Date(value)
|
||||||
|
if (Number.isNaN(date.getTime())) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
return date.toISOString()
|
||||||
|
}
|
||||||
|
|
||||||
class BedrockAccountService {
|
class BedrockAccountService {
|
||||||
constructor() {
|
constructor() {
|
||||||
// 加密相关常量
|
// 加密相关常量
|
||||||
@@ -40,7 +53,8 @@ class BedrockAccountService {
|
|||||||
accountType = 'shared', // 'dedicated' or 'shared'
|
accountType = 'shared', // 'dedicated' or 'shared'
|
||||||
priority = 50, // 调度优先级 (1-100,数字越小优先级越高)
|
priority = 50, // 调度优先级 (1-100,数字越小优先级越高)
|
||||||
schedulable = true, // 是否可被调度
|
schedulable = true, // 是否可被调度
|
||||||
credentialType = 'default' // 'default', 'access_key', 'bearer_token'
|
credentialType = 'default', // 'default', 'access_key', 'bearer_token'
|
||||||
|
subscriptionExpiresAt = null
|
||||||
} = options
|
} = options
|
||||||
|
|
||||||
const accountId = uuidv4()
|
const accountId = uuidv4()
|
||||||
@@ -56,6 +70,7 @@ class BedrockAccountService {
|
|||||||
priority,
|
priority,
|
||||||
schedulable,
|
schedulable,
|
||||||
credentialType,
|
credentialType,
|
||||||
|
subscriptionExpiresAt: normalizeSubscriptionExpiresAt(subscriptionExpiresAt),
|
||||||
createdAt: new Date().toISOString(),
|
createdAt: new Date().toISOString(),
|
||||||
updatedAt: new Date().toISOString(),
|
updatedAt: new Date().toISOString(),
|
||||||
type: 'bedrock' // 标识这是Bedrock账户
|
type: 'bedrock' // 标识这是Bedrock账户
|
||||||
@@ -84,6 +99,7 @@ class BedrockAccountService {
|
|||||||
priority,
|
priority,
|
||||||
schedulable,
|
schedulable,
|
||||||
credentialType,
|
credentialType,
|
||||||
|
subscriptionExpiresAt: accountData.subscriptionExpiresAt || null,
|
||||||
createdAt: accountData.createdAt,
|
createdAt: accountData.createdAt,
|
||||||
type: 'bedrock'
|
type: 'bedrock'
|
||||||
}
|
}
|
||||||
@@ -106,6 +122,11 @@ class BedrockAccountService {
|
|||||||
account.awsCredentials = this._decryptAwsCredentials(account.awsCredentials)
|
account.awsCredentials = this._decryptAwsCredentials(account.awsCredentials)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
account.subscriptionExpiresAt =
|
||||||
|
account.subscriptionExpiresAt && account.subscriptionExpiresAt !== ''
|
||||||
|
? account.subscriptionExpiresAt
|
||||||
|
: null
|
||||||
|
|
||||||
logger.debug(`🔍 获取Bedrock账户 - ID: ${accountId}, 名称: ${account.name}`)
|
logger.debug(`🔍 获取Bedrock账户 - ID: ${accountId}, 名称: ${account.name}`)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -141,13 +162,15 @@ class BedrockAccountService {
|
|||||||
accountType: account.accountType,
|
accountType: account.accountType,
|
||||||
priority: account.priority,
|
priority: account.priority,
|
||||||
schedulable: account.schedulable,
|
schedulable: account.schedulable,
|
||||||
credentialType: account.credentialType,
|
credentialType: account.credentialType,
|
||||||
createdAt: account.createdAt,
|
createdAt: account.createdAt,
|
||||||
updatedAt: account.updatedAt,
|
updatedAt: account.updatedAt,
|
||||||
type: 'bedrock',
|
type: 'bedrock',
|
||||||
hasCredentials: !!account.awsCredentials
|
hasCredentials: !!account.awsCredentials,
|
||||||
})
|
expiresAt: account.expiresAt || null,
|
||||||
}
|
subscriptionExpiresAt: account.subscriptionExpiresAt || null
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 按优先级和名称排序
|
// 按优先级和名称排序
|
||||||
@@ -211,6 +234,14 @@ class BedrockAccountService {
|
|||||||
account.credentialType = updates.credentialType
|
account.credentialType = updates.credentialType
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Object.prototype.hasOwnProperty.call(updates, 'subscriptionExpiresAt')) {
|
||||||
|
account.subscriptionExpiresAt = normalizeSubscriptionExpiresAt(
|
||||||
|
updates.subscriptionExpiresAt
|
||||||
|
)
|
||||||
|
} else if (Object.prototype.hasOwnProperty.call(updates, 'expiresAt')) {
|
||||||
|
account.subscriptionExpiresAt = normalizeSubscriptionExpiresAt(updates.expiresAt)
|
||||||
|
}
|
||||||
|
|
||||||
// 更新AWS凭证
|
// 更新AWS凭证
|
||||||
if (updates.awsCredentials !== undefined) {
|
if (updates.awsCredentials !== undefined) {
|
||||||
if (updates.awsCredentials) {
|
if (updates.awsCredentials) {
|
||||||
@@ -245,7 +276,9 @@ class BedrockAccountService {
|
|||||||
schedulable: account.schedulable,
|
schedulable: account.schedulable,
|
||||||
credentialType: account.credentialType,
|
credentialType: account.credentialType,
|
||||||
updatedAt: account.updatedAt,
|
updatedAt: account.updatedAt,
|
||||||
type: 'bedrock'
|
type: 'bedrock',
|
||||||
|
expiresAt: account.expiresAt || null,
|
||||||
|
subscriptionExpiresAt: account.subscriptionExpiresAt || null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -6,6 +6,19 @@ const logger = require('../utils/logger')
|
|||||||
const config = require('../../config/config')
|
const config = require('../../config/config')
|
||||||
const LRUCache = require('../utils/lruCache')
|
const LRUCache = require('../utils/lruCache')
|
||||||
|
|
||||||
|
function normalizeSubscriptionExpiresAt(value) {
|
||||||
|
if (value === undefined || value === null || value === '') {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const date = value instanceof Date ? value : new Date(value)
|
||||||
|
if (Number.isNaN(date.getTime())) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
return date.toISOString()
|
||||||
|
}
|
||||||
|
|
||||||
class CcrAccountService {
|
class CcrAccountService {
|
||||||
constructor() {
|
constructor() {
|
||||||
// 加密相关常量
|
// 加密相关常量
|
||||||
@@ -49,7 +62,8 @@ class CcrAccountService {
|
|||||||
accountType = 'shared', // 'dedicated' or 'shared'
|
accountType = 'shared', // 'dedicated' or 'shared'
|
||||||
schedulable = true, // 是否可被调度
|
schedulable = true, // 是否可被调度
|
||||||
dailyQuota = 0, // 每日额度限制(美元),0表示不限制
|
dailyQuota = 0, // 每日额度限制(美元),0表示不限制
|
||||||
quotaResetTime = '00:00' // 额度重置时间(HH:mm格式)
|
quotaResetTime = '00:00', // 额度重置时间(HH:mm格式)
|
||||||
|
subscriptionExpiresAt = null
|
||||||
} = options
|
} = options
|
||||||
|
|
||||||
// 验证必填字段
|
// 验证必填字段
|
||||||
@@ -91,7 +105,8 @@ class CcrAccountService {
|
|||||||
// 使用与统计一致的时区日期,避免边界问题
|
// 使用与统计一致的时区日期,避免边界问题
|
||||||
lastResetDate: redis.getDateStringInTimezone(), // 最后重置日期(按配置时区)
|
lastResetDate: redis.getDateStringInTimezone(), // 最后重置日期(按配置时区)
|
||||||
quotaResetTime, // 额度重置时间
|
quotaResetTime, // 额度重置时间
|
||||||
quotaStoppedAt: '' // 因额度停用的时间
|
quotaStoppedAt: '', // 因额度停用的时间
|
||||||
|
subscriptionExpiresAt: normalizeSubscriptionExpiresAt(subscriptionExpiresAt)
|
||||||
}
|
}
|
||||||
|
|
||||||
const client = redis.getClientSafe()
|
const client = redis.getClientSafe()
|
||||||
@@ -127,7 +142,8 @@ class CcrAccountService {
|
|||||||
dailyUsage: 0,
|
dailyUsage: 0,
|
||||||
lastResetDate: accountData.lastResetDate,
|
lastResetDate: accountData.lastResetDate,
|
||||||
quotaResetTime,
|
quotaResetTime,
|
||||||
quotaStoppedAt: null
|
quotaStoppedAt: null,
|
||||||
|
subscriptionExpiresAt: accountData.subscriptionExpiresAt || null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,12 +183,14 @@ class CcrAccountService {
|
|||||||
schedulable: accountData.schedulable !== 'false', // 默认为true,只有明确设置为false才不可调度
|
schedulable: accountData.schedulable !== 'false', // 默认为true,只有明确设置为false才不可调度
|
||||||
// 额度管理相关
|
// 额度管理相关
|
||||||
dailyQuota: parseFloat(accountData.dailyQuota || '0'),
|
dailyQuota: parseFloat(accountData.dailyQuota || '0'),
|
||||||
dailyUsage: parseFloat(accountData.dailyUsage || '0'),
|
dailyUsage: parseFloat(accountData.dailyUsage || '0'),
|
||||||
lastResetDate: accountData.lastResetDate || '',
|
lastResetDate: accountData.lastResetDate || '',
|
||||||
quotaResetTime: accountData.quotaResetTime || '00:00',
|
quotaResetTime: accountData.quotaResetTime || '00:00',
|
||||||
quotaStoppedAt: accountData.quotaStoppedAt || null
|
quotaStoppedAt: accountData.quotaStoppedAt || null,
|
||||||
})
|
expiresAt: accountData.expiresAt || null,
|
||||||
}
|
subscriptionExpiresAt: accountData.subscriptionExpiresAt || null
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return accounts
|
return accounts
|
||||||
@@ -225,6 +243,11 @@ class CcrAccountService {
|
|||||||
`[DEBUG] Final CCR account data - name: ${accountData.name}, hasApiUrl: ${!!accountData.apiUrl}, hasApiKey: ${!!accountData.apiKey}, supportedModels: ${JSON.stringify(accountData.supportedModels)}`
|
`[DEBUG] Final CCR account data - name: ${accountData.name}, hasApiUrl: ${!!accountData.apiUrl}, hasApiKey: ${!!accountData.apiKey}, supportedModels: ${JSON.stringify(accountData.supportedModels)}`
|
||||||
)
|
)
|
||||||
|
|
||||||
|
accountData.subscriptionExpiresAt =
|
||||||
|
accountData.subscriptionExpiresAt && accountData.subscriptionExpiresAt !== ''
|
||||||
|
? accountData.subscriptionExpiresAt
|
||||||
|
: null
|
||||||
|
|
||||||
return accountData
|
return accountData
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -288,6 +311,14 @@ class CcrAccountService {
|
|||||||
updatedData.quotaResetTime = updates.quotaResetTime
|
updatedData.quotaResetTime = updates.quotaResetTime
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Object.prototype.hasOwnProperty.call(updates, 'subscriptionExpiresAt')) {
|
||||||
|
updatedData.subscriptionExpiresAt = normalizeSubscriptionExpiresAt(
|
||||||
|
updates.subscriptionExpiresAt
|
||||||
|
)
|
||||||
|
} else if (Object.prototype.hasOwnProperty.call(updates, 'expiresAt')) {
|
||||||
|
updatedData.subscriptionExpiresAt = normalizeSubscriptionExpiresAt(updates.expiresAt)
|
||||||
|
}
|
||||||
|
|
||||||
await client.hset(`${this.ACCOUNT_KEY_PREFIX}${accountId}`, updatedData)
|
await client.hset(`${this.ACCOUNT_KEY_PREFIX}${accountId}`, updatedData)
|
||||||
|
|
||||||
// 处理共享账户集合变更
|
// 处理共享账户集合变更
|
||||||
|
|||||||
@@ -185,6 +185,10 @@ class ClaudeAccountService {
|
|||||||
status: accountData.status,
|
status: accountData.status,
|
||||||
createdAt: accountData.createdAt,
|
createdAt: accountData.createdAt,
|
||||||
expiresAt: accountData.expiresAt,
|
expiresAt: accountData.expiresAt,
|
||||||
|
subscriptionExpiresAt:
|
||||||
|
accountData.subscriptionExpiresAt && accountData.subscriptionExpiresAt !== ''
|
||||||
|
? accountData.subscriptionExpiresAt
|
||||||
|
: null,
|
||||||
scopes: claudeAiOauth ? claudeAiOauth.scopes : [],
|
scopes: claudeAiOauth ? claudeAiOauth.scopes : [],
|
||||||
autoStopOnWarning,
|
autoStopOnWarning,
|
||||||
useUnifiedUserAgent,
|
useUnifiedUserAgent,
|
||||||
@@ -491,7 +495,11 @@ class ClaudeAccountService {
|
|||||||
createdAt: account.createdAt,
|
createdAt: account.createdAt,
|
||||||
lastUsedAt: account.lastUsedAt,
|
lastUsedAt: account.lastUsedAt,
|
||||||
lastRefreshAt: account.lastRefreshAt,
|
lastRefreshAt: account.lastRefreshAt,
|
||||||
expiresAt: account.subscriptionExpiresAt || null, // 账户订阅到期时间
|
expiresAt: account.expiresAt || null,
|
||||||
|
subscriptionExpiresAt:
|
||||||
|
account.subscriptionExpiresAt && account.subscriptionExpiresAt !== ''
|
||||||
|
? account.subscriptionExpiresAt
|
||||||
|
: null,
|
||||||
// 添加 scopes 字段用于判断认证方式
|
// 添加 scopes 字段用于判断认证方式
|
||||||
// 处理空字符串的情况,避免返回 ['']
|
// 处理空字符串的情况,避免返回 ['']
|
||||||
scopes: account.scopes && account.scopes.trim() ? account.scopes.split(' ') : [],
|
scopes: account.scopes && account.scopes.trim() ? account.scopes.split(' ') : [],
|
||||||
|
|||||||
@@ -6,6 +6,19 @@ const logger = require('../utils/logger')
|
|||||||
const config = require('../../config/config')
|
const config = require('../../config/config')
|
||||||
const LRUCache = require('../utils/lruCache')
|
const LRUCache = require('../utils/lruCache')
|
||||||
|
|
||||||
|
function normalizeSubscriptionExpiresAt(value) {
|
||||||
|
if (value === undefined || value === null || value === '') {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const date = value instanceof Date ? value : new Date(value)
|
||||||
|
if (Number.isNaN(date.getTime())) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
return date.toISOString()
|
||||||
|
}
|
||||||
|
|
||||||
class ClaudeConsoleAccountService {
|
class ClaudeConsoleAccountService {
|
||||||
constructor() {
|
constructor() {
|
||||||
// 加密相关常量
|
// 加密相关常量
|
||||||
@@ -52,7 +65,8 @@ class ClaudeConsoleAccountService {
|
|||||||
accountType = 'shared', // 'dedicated' or 'shared'
|
accountType = 'shared', // 'dedicated' or 'shared'
|
||||||
schedulable = true, // 是否可被调度
|
schedulable = true, // 是否可被调度
|
||||||
dailyQuota = 0, // 每日额度限制(美元),0表示不限制
|
dailyQuota = 0, // 每日额度限制(美元),0表示不限制
|
||||||
quotaResetTime = '00:00' // 额度重置时间(HH:mm格式)
|
quotaResetTime = '00:00', // 额度重置时间(HH:mm格式)
|
||||||
|
subscriptionExpiresAt = null
|
||||||
} = options
|
} = options
|
||||||
|
|
||||||
// 验证必填字段
|
// 验证必填字段
|
||||||
@@ -94,7 +108,8 @@ class ClaudeConsoleAccountService {
|
|||||||
// 使用与统计一致的时区日期,避免边界问题
|
// 使用与统计一致的时区日期,避免边界问题
|
||||||
lastResetDate: redis.getDateStringInTimezone(), // 最后重置日期(按配置时区)
|
lastResetDate: redis.getDateStringInTimezone(), // 最后重置日期(按配置时区)
|
||||||
quotaResetTime, // 额度重置时间
|
quotaResetTime, // 额度重置时间
|
||||||
quotaStoppedAt: '' // 因额度停用的时间
|
quotaStoppedAt: '', // 因额度停用的时间
|
||||||
|
subscriptionExpiresAt: normalizeSubscriptionExpiresAt(subscriptionExpiresAt)
|
||||||
}
|
}
|
||||||
|
|
||||||
const client = redis.getClientSafe()
|
const client = redis.getClientSafe()
|
||||||
@@ -130,7 +145,8 @@ class ClaudeConsoleAccountService {
|
|||||||
dailyUsage: 0,
|
dailyUsage: 0,
|
||||||
lastResetDate: accountData.lastResetDate,
|
lastResetDate: accountData.lastResetDate,
|
||||||
quotaResetTime,
|
quotaResetTime,
|
||||||
quotaStoppedAt: null
|
quotaStoppedAt: null,
|
||||||
|
subscriptionExpiresAt: accountData.subscriptionExpiresAt || null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,12 +186,14 @@ class ClaudeConsoleAccountService {
|
|||||||
schedulable: accountData.schedulable !== 'false', // 默认为true,只有明确设置为false才不可调度
|
schedulable: accountData.schedulable !== 'false', // 默认为true,只有明确设置为false才不可调度
|
||||||
// 额度管理相关
|
// 额度管理相关
|
||||||
dailyQuota: parseFloat(accountData.dailyQuota || '0'),
|
dailyQuota: parseFloat(accountData.dailyQuota || '0'),
|
||||||
dailyUsage: parseFloat(accountData.dailyUsage || '0'),
|
dailyUsage: parseFloat(accountData.dailyUsage || '0'),
|
||||||
lastResetDate: accountData.lastResetDate || '',
|
lastResetDate: accountData.lastResetDate || '',
|
||||||
quotaResetTime: accountData.quotaResetTime || '00:00',
|
quotaResetTime: accountData.quotaResetTime || '00:00',
|
||||||
quotaStoppedAt: accountData.quotaStoppedAt || null
|
quotaStoppedAt: accountData.quotaStoppedAt || null,
|
||||||
})
|
expiresAt: accountData.expiresAt || null,
|
||||||
}
|
subscriptionExpiresAt: accountData.subscriptionExpiresAt || null
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return accounts
|
return accounts
|
||||||
@@ -224,6 +242,11 @@ class ClaudeConsoleAccountService {
|
|||||||
accountData.proxy = JSON.parse(accountData.proxy)
|
accountData.proxy = JSON.parse(accountData.proxy)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
accountData.subscriptionExpiresAt =
|
||||||
|
accountData.subscriptionExpiresAt && accountData.subscriptionExpiresAt !== ''
|
||||||
|
? accountData.subscriptionExpiresAt
|
||||||
|
: null
|
||||||
|
|
||||||
logger.debug(
|
logger.debug(
|
||||||
`[DEBUG] Final account data - name: ${accountData.name}, hasApiUrl: ${!!accountData.apiUrl}, hasApiKey: ${!!accountData.apiKey}, supportedModels: ${JSON.stringify(accountData.supportedModels)}`
|
`[DEBUG] Final account data - name: ${accountData.name}, hasApiUrl: ${!!accountData.apiUrl}, hasApiKey: ${!!accountData.apiKey}, supportedModels: ${JSON.stringify(accountData.supportedModels)}`
|
||||||
)
|
)
|
||||||
@@ -318,6 +341,14 @@ class ClaudeConsoleAccountService {
|
|||||||
updatedData.quotaStoppedAt = updates.quotaStoppedAt
|
updatedData.quotaStoppedAt = updates.quotaStoppedAt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Object.prototype.hasOwnProperty.call(updates, 'subscriptionExpiresAt')) {
|
||||||
|
updatedData.subscriptionExpiresAt = normalizeSubscriptionExpiresAt(
|
||||||
|
updates.subscriptionExpiresAt
|
||||||
|
)
|
||||||
|
} else if (Object.prototype.hasOwnProperty.call(updates, 'expiresAt')) {
|
||||||
|
updatedData.subscriptionExpiresAt = normalizeSubscriptionExpiresAt(updates.expiresAt)
|
||||||
|
}
|
||||||
|
|
||||||
// 处理账户类型变更
|
// 处理账户类型变更
|
||||||
if (updates.accountType && updates.accountType !== existingAccount.accountType) {
|
if (updates.accountType && updates.accountType !== existingAccount.accountType) {
|
||||||
updatedData.accountType = updates.accountType
|
updatedData.accountType = updates.accountType
|
||||||
|
|||||||
@@ -42,6 +42,19 @@ function generateEncryptionKey() {
|
|||||||
return _encryptionKeyCache
|
return _encryptionKeyCache
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeSubscriptionExpiresAt(value) {
|
||||||
|
if (value === undefined || value === null || value === '') {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const date = value instanceof Date ? value : new Date(value)
|
||||||
|
if (Number.isNaN(date.getTime())) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
return date.toISOString()
|
||||||
|
}
|
||||||
|
|
||||||
// Gemini 账户键前缀
|
// Gemini 账户键前缀
|
||||||
const GEMINI_ACCOUNT_KEY_PREFIX = 'gemini_account:'
|
const GEMINI_ACCOUNT_KEY_PREFIX = 'gemini_account:'
|
||||||
const SHARED_GEMINI_ACCOUNTS_KEY = 'shared_gemini_accounts'
|
const SHARED_GEMINI_ACCOUNTS_KEY = 'shared_gemini_accounts'
|
||||||
@@ -333,6 +346,10 @@ async function createAccount(accountData) {
|
|||||||
let refreshToken = ''
|
let refreshToken = ''
|
||||||
let expiresAt = ''
|
let expiresAt = ''
|
||||||
|
|
||||||
|
const subscriptionExpiresAt = normalizeSubscriptionExpiresAt(
|
||||||
|
accountData.subscriptionExpiresAt || ''
|
||||||
|
)
|
||||||
|
|
||||||
if (accountData.geminiOauth || accountData.accessToken) {
|
if (accountData.geminiOauth || accountData.accessToken) {
|
||||||
// 如果提供了完整的 OAuth 数据
|
// 如果提供了完整的 OAuth 数据
|
||||||
if (accountData.geminiOauth) {
|
if (accountData.geminiOauth) {
|
||||||
@@ -404,7 +421,8 @@ async function createAccount(accountData) {
|
|||||||
createdAt: now,
|
createdAt: now,
|
||||||
updatedAt: now,
|
updatedAt: now,
|
||||||
lastUsedAt: '',
|
lastUsedAt: '',
|
||||||
lastRefreshAt: ''
|
lastRefreshAt: '',
|
||||||
|
subscriptionExpiresAt
|
||||||
}
|
}
|
||||||
|
|
||||||
// 保存到 Redis
|
// 保存到 Redis
|
||||||
@@ -428,6 +446,10 @@ async function createAccount(accountData) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!returnAccount.subscriptionExpiresAt) {
|
||||||
|
returnAccount.subscriptionExpiresAt = null
|
||||||
|
}
|
||||||
|
|
||||||
return returnAccount
|
return returnAccount
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -464,6 +486,10 @@ async function getAccount(accountId) {
|
|||||||
// 转换 schedulable 字符串为布尔值(与 claudeConsoleAccountService 保持一致)
|
// 转换 schedulable 字符串为布尔值(与 claudeConsoleAccountService 保持一致)
|
||||||
accountData.schedulable = accountData.schedulable !== 'false' // 默认为true,只有明确设置为'false'才为false
|
accountData.schedulable = accountData.schedulable !== 'false' // 默认为true,只有明确设置为'false'才为false
|
||||||
|
|
||||||
|
if (!accountData.subscriptionExpiresAt) {
|
||||||
|
accountData.subscriptionExpiresAt = null
|
||||||
|
}
|
||||||
|
|
||||||
return accountData
|
return accountData
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -477,6 +503,12 @@ async function updateAccount(accountId, updates) {
|
|||||||
const now = new Date().toISOString()
|
const now = new Date().toISOString()
|
||||||
updates.updatedAt = now
|
updates.updatedAt = now
|
||||||
|
|
||||||
|
if (Object.prototype.hasOwnProperty.call(updates, 'subscriptionExpiresAt')) {
|
||||||
|
updates.subscriptionExpiresAt = normalizeSubscriptionExpiresAt(
|
||||||
|
updates.subscriptionExpiresAt
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// 检查是否新增了 refresh token
|
// 检查是否新增了 refresh token
|
||||||
// existingAccount.refreshToken 已经是解密后的值了(从 getAccount 返回)
|
// existingAccount.refreshToken 已经是解密后的值了(从 getAccount 返回)
|
||||||
const oldRefreshToken = existingAccount.refreshToken || ''
|
const oldRefreshToken = existingAccount.refreshToken || ''
|
||||||
@@ -586,6 +618,10 @@ async function updateAccount(accountId, updates) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!updatedAccount.subscriptionExpiresAt) {
|
||||||
|
updatedAccount.subscriptionExpiresAt = null
|
||||||
|
}
|
||||||
|
|
||||||
return updatedAccount
|
return updatedAccount
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -649,6 +685,7 @@ async function getAllAccounts() {
|
|||||||
geminiOauth: accountData.geminiOauth ? '[ENCRYPTED]' : '',
|
geminiOauth: accountData.geminiOauth ? '[ENCRYPTED]' : '',
|
||||||
accessToken: accountData.accessToken ? '[ENCRYPTED]' : '',
|
accessToken: accountData.accessToken ? '[ENCRYPTED]' : '',
|
||||||
refreshToken: accountData.refreshToken ? '[ENCRYPTED]' : '',
|
refreshToken: accountData.refreshToken ? '[ENCRYPTED]' : '',
|
||||||
|
subscriptionExpiresAt: accountData.subscriptionExpiresAt || null,
|
||||||
// 添加 scopes 字段用于判断认证方式
|
// 添加 scopes 字段用于判断认证方式
|
||||||
// 处理空字符串和默认值的情况
|
// 处理空字符串和默认值的情况
|
||||||
scopes:
|
scopes:
|
||||||
|
|||||||
@@ -194,6 +194,19 @@ function buildCodexUsageSnapshot(accountData) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeSubscriptionExpiresAt(value) {
|
||||||
|
if (value === undefined || value === null || value === '') {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const date = value instanceof Date ? value : new Date(value)
|
||||||
|
if (Number.isNaN(date.getTime())) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
return date.toISOString()
|
||||||
|
}
|
||||||
|
|
||||||
// 刷新访问令牌
|
// 刷新访问令牌
|
||||||
async function refreshAccessToken(refreshToken, proxy = null) {
|
async function refreshAccessToken(refreshToken, proxy = null) {
|
||||||
try {
|
try {
|
||||||
@@ -517,6 +530,13 @@ async function createAccount(accountData) {
|
|||||||
// 处理账户信息
|
// 处理账户信息
|
||||||
const accountInfo = accountData.accountInfo || {}
|
const accountInfo = accountData.accountInfo || {}
|
||||||
|
|
||||||
|
const tokenExpiresAt = oauthData.expires_in
|
||||||
|
? new Date(Date.now() + oauthData.expires_in * 1000).toISOString()
|
||||||
|
: ''
|
||||||
|
const subscriptionExpiresAt = normalizeSubscriptionExpiresAt(
|
||||||
|
accountData.subscriptionExpiresAt || accountInfo.subscriptionExpiresAt || ''
|
||||||
|
)
|
||||||
|
|
||||||
// 检查邮箱是否已经是加密格式(包含冒号分隔的32位十六进制字符)
|
// 检查邮箱是否已经是加密格式(包含冒号分隔的32位十六进制字符)
|
||||||
const isEmailEncrypted =
|
const isEmailEncrypted =
|
||||||
accountInfo.email && accountInfo.email.length >= 33 && accountInfo.email.charAt(32) === ':'
|
accountInfo.email && accountInfo.email.length >= 33 && accountInfo.email.charAt(32) === ':'
|
||||||
@@ -553,9 +573,8 @@ async function createAccount(accountData) {
|
|||||||
email: isEmailEncrypted ? accountInfo.email : encrypt(accountInfo.email || ''),
|
email: isEmailEncrypted ? accountInfo.email : encrypt(accountInfo.email || ''),
|
||||||
emailVerified: accountInfo.emailVerified === true ? 'true' : 'false',
|
emailVerified: accountInfo.emailVerified === true ? 'true' : 'false',
|
||||||
// 过期时间
|
// 过期时间
|
||||||
expiresAt: oauthData.expires_in
|
expiresAt: tokenExpiresAt,
|
||||||
? new Date(Date.now() + oauthData.expires_in * 1000).toISOString()
|
subscriptionExpiresAt,
|
||||||
: new Date(Date.now() + 365 * 24 * 60 * 60 * 1000).toISOString(), // 默认1年
|
|
||||||
// 状态字段
|
// 状态字段
|
||||||
isActive: accountData.isActive !== false ? 'true' : 'false',
|
isActive: accountData.isActive !== false ? 'true' : 'false',
|
||||||
status: 'active',
|
status: 'active',
|
||||||
@@ -580,7 +599,10 @@ async function createAccount(accountData) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
logger.info(`Created OpenAI account: ${accountId}`)
|
logger.info(`Created OpenAI account: ${accountId}`)
|
||||||
return account
|
return {
|
||||||
|
...account,
|
||||||
|
subscriptionExpiresAt: account.subscriptionExpiresAt || null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取账户
|
// 获取账户
|
||||||
@@ -623,6 +645,11 @@ async function getAccount(accountId) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
accountData.subscriptionExpiresAt =
|
||||||
|
accountData.subscriptionExpiresAt && accountData.subscriptionExpiresAt !== ''
|
||||||
|
? accountData.subscriptionExpiresAt
|
||||||
|
: null
|
||||||
|
|
||||||
return accountData
|
return accountData
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -656,6 +683,10 @@ async function updateAccount(accountId, updates) {
|
|||||||
updates.email = encrypt(updates.email)
|
updates.email = encrypt(updates.email)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Object.prototype.hasOwnProperty.call(updates, 'subscriptionExpiresAt')) {
|
||||||
|
updates.subscriptionExpiresAt = normalizeSubscriptionExpiresAt(updates.subscriptionExpiresAt)
|
||||||
|
}
|
||||||
|
|
||||||
// 处理代理配置
|
// 处理代理配置
|
||||||
if (updates.proxy) {
|
if (updates.proxy) {
|
||||||
updates.proxy =
|
updates.proxy =
|
||||||
@@ -688,6 +719,10 @@ async function updateAccount(accountId, updates) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!updatedAccount.subscriptionExpiresAt) {
|
||||||
|
updatedAccount.subscriptionExpiresAt = null
|
||||||
|
}
|
||||||
|
|
||||||
return updatedAccount
|
return updatedAccount
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -770,6 +805,8 @@ async function getAllAccounts() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const subscriptionExpiresAt = accountData.subscriptionExpiresAt || null
|
||||||
|
|
||||||
// 不解密敏感字段,只返回基本信息
|
// 不解密敏感字段,只返回基本信息
|
||||||
accounts.push({
|
accounts.push({
|
||||||
...accountData,
|
...accountData,
|
||||||
@@ -784,6 +821,7 @@ async function getAllAccounts() {
|
|||||||
accountData.scopes && accountData.scopes.trim() ? accountData.scopes.split(' ') : [],
|
accountData.scopes && accountData.scopes.trim() ? accountData.scopes.split(' ') : [],
|
||||||
// 添加 hasRefreshToken 标记
|
// 添加 hasRefreshToken 标记
|
||||||
hasRefreshToken: hasRefreshTokenFlag,
|
hasRefreshToken: hasRefreshTokenFlag,
|
||||||
|
subscriptionExpiresAt,
|
||||||
// 添加限流状态信息(统一格式)
|
// 添加限流状态信息(统一格式)
|
||||||
rateLimitStatus: rateLimitInfo
|
rateLimitStatus: rateLimitInfo
|
||||||
? {
|
? {
|
||||||
|
|||||||
@@ -5,6 +5,19 @@ const logger = require('../utils/logger')
|
|||||||
const config = require('../../config/config')
|
const config = require('../../config/config')
|
||||||
const LRUCache = require('../utils/lruCache')
|
const LRUCache = require('../utils/lruCache')
|
||||||
|
|
||||||
|
function normalizeSubscriptionExpiresAt(value) {
|
||||||
|
if (value === undefined || value === null || value === '') {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const date = value instanceof Date ? value : new Date(value)
|
||||||
|
if (Number.isNaN(date.getTime())) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
return date.toISOString()
|
||||||
|
}
|
||||||
|
|
||||||
class OpenAIResponsesAccountService {
|
class OpenAIResponsesAccountService {
|
||||||
constructor() {
|
constructor() {
|
||||||
// 加密相关常量
|
// 加密相关常量
|
||||||
@@ -49,7 +62,8 @@ class OpenAIResponsesAccountService {
|
|||||||
schedulable = true, // 是否可被调度
|
schedulable = true, // 是否可被调度
|
||||||
dailyQuota = 0, // 每日额度限制(美元),0表示不限制
|
dailyQuota = 0, // 每日额度限制(美元),0表示不限制
|
||||||
quotaResetTime = '00:00', // 额度重置时间(HH:mm格式)
|
quotaResetTime = '00:00', // 额度重置时间(HH:mm格式)
|
||||||
rateLimitDuration = 60 // 限流时间(分钟)
|
rateLimitDuration = 60, // 限流时间(分钟)
|
||||||
|
subscriptionExpiresAt = null
|
||||||
} = options
|
} = options
|
||||||
|
|
||||||
// 验证必填字段
|
// 验证必填字段
|
||||||
@@ -88,7 +102,8 @@ class OpenAIResponsesAccountService {
|
|||||||
dailyUsage: '0',
|
dailyUsage: '0',
|
||||||
lastResetDate: redis.getDateStringInTimezone(),
|
lastResetDate: redis.getDateStringInTimezone(),
|
||||||
quotaResetTime,
|
quotaResetTime,
|
||||||
quotaStoppedAt: ''
|
quotaStoppedAt: '',
|
||||||
|
subscriptionExpiresAt: normalizeSubscriptionExpiresAt(subscriptionExpiresAt)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 保存到 Redis
|
// 保存到 Redis
|
||||||
@@ -98,6 +113,7 @@ class OpenAIResponsesAccountService {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
...accountData,
|
...accountData,
|
||||||
|
subscriptionExpiresAt: accountData.subscriptionExpiresAt || null,
|
||||||
apiKey: '***' // 返回时隐藏敏感信息
|
apiKey: '***' // 返回时隐藏敏感信息
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -124,6 +140,11 @@ class OpenAIResponsesAccountService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
accountData.subscriptionExpiresAt =
|
||||||
|
accountData.subscriptionExpiresAt && accountData.subscriptionExpiresAt !== ''
|
||||||
|
? accountData.subscriptionExpiresAt
|
||||||
|
: null
|
||||||
|
|
||||||
return accountData
|
return accountData
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,6 +172,15 @@ class OpenAIResponsesAccountService {
|
|||||||
: updates.baseApi
|
: updates.baseApi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Object.prototype.hasOwnProperty.call(updates, 'subscriptionExpiresAt')) {
|
||||||
|
updates.subscriptionExpiresAt = normalizeSubscriptionExpiresAt(
|
||||||
|
updates.subscriptionExpiresAt
|
||||||
|
)
|
||||||
|
} else if (Object.prototype.hasOwnProperty.call(updates, 'expiresAt')) {
|
||||||
|
updates.subscriptionExpiresAt = normalizeSubscriptionExpiresAt(updates.expiresAt)
|
||||||
|
delete updates.expiresAt
|
||||||
|
}
|
||||||
|
|
||||||
// 更新 Redis
|
// 更新 Redis
|
||||||
const client = redis.getClientSafe()
|
const client = redis.getClientSafe()
|
||||||
const key = `${this.ACCOUNT_KEY_PREFIX}${accountId}`
|
const key = `${this.ACCOUNT_KEY_PREFIX}${accountId}`
|
||||||
@@ -257,6 +287,10 @@ class OpenAIResponsesAccountService {
|
|||||||
accountData.schedulable = accountData.schedulable !== 'false'
|
accountData.schedulable = accountData.schedulable !== 'false'
|
||||||
// 转换 isActive 字段为布尔值
|
// 转换 isActive 字段为布尔值
|
||||||
accountData.isActive = accountData.isActive === 'true'
|
accountData.isActive = accountData.isActive === 'true'
|
||||||
|
accountData.subscriptionExpiresAt =
|
||||||
|
accountData.subscriptionExpiresAt && accountData.subscriptionExpiresAt !== ''
|
||||||
|
? accountData.subscriptionExpiresAt
|
||||||
|
: null
|
||||||
|
|
||||||
accounts.push(accountData)
|
accounts.push(accountData)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user