更新导入设计

This commit is contained in:
2026-07-03 09:19:56 +08:00
parent 4e2844ff51
commit dbd2d1b79a
2 changed files with 9 additions and 28 deletions
+6 -25
View File
@@ -862,26 +862,12 @@ func (s *TokenService) ImportGrokToken(ctx context.Context, ssoToken, tokenID st
return nil, errors.New("not a grok sso token") return nil, errors.New("not a grok sso token")
} }
sid := grok.SessionIDFromToken(ssoToken) sid := grok.SessionIDFromToken(ssoToken)
// Resolve the real account email up front (GET /api/auth/session) for dedup + // Fully async, no dedup: every import mints a fresh row (a passed-in tokenID is
// display — the sso session_id rotates per login, so email is the stable id. // an explicit edit → update). We do NOT look up an existing account by
email := "" // email/session_id, and we leave account_email empty — email, quota and
if s.grok != nil { // recovery time are all filled off-thread by checkPendingGrok, which also
s.applyProxy(ctx) // disables the account if the sso session is dead.
// Best-effort email resolution. A dead/blocked probe must NOT block import: if strings.TrimSpace(tokenID) == "" {
// land the account and let the async checkPendingGrok disable it if the
// session is truly dead. Empty email just falls back to the session id.
if e, _, ferr := s.grok.FetchSession(ctx, ssoToken); ferr == nil {
email = e
}
}
idKey := email
if idKey == "" {
idKey = sid
}
// Identity is (pool, email): reuse the row for this account, else mint.
if existing, _ := s.tokens.GetByPoolEmail(ctx, "grok", idKey); existing != nil {
tokenID = existing.ID
} else if idKey != "" || tokenID == "" {
tokenID = newTokenID("grok") tokenID = newTokenID("grok")
} }
meta := datatypes.JSONMap{"pending_check": true} meta := datatypes.JSONMap{"pending_check": true}
@@ -900,11 +886,6 @@ func (s *TokenService) ImportGrokToken(ctx context.Context, ssoToken, tokenID st
return nil, err return nil, err
} }
} }
if idKey != "" {
if updated, uerr := s.tokens.Update(ctx, "grok", tokenID, map[string]any{"account_email": idKey}); uerr == nil {
item = updated
}
}
go s.checkPendingGrok(tokenID, ssoToken) go s.checkPendingGrok(tokenID, ssoToken)
return item, nil return item, nil
} }
+3 -3
View File
@@ -45,7 +45,6 @@ async function doSmartImport() {
const errs = [] const errs = []
for (let i = 0; i < items.length; i++) { for (let i = 0; i < items.length; i++) {
const it = items[i] const it = items[i]
setStatus(`正在导入 ${i + 1}/${items.length} (${it.type})…`)
try { try {
const r = it.type === 'openai' const r = it.type === 'openai'
? await api('/tokens/import-chatgpt-token', jsonBody('POST', { access_token: it.value })) ? await api('/tokens/import-chatgpt-token', jsonBody('POST', { access_token: it.value }))
@@ -77,9 +76,10 @@ async function doSmartImport() {
// Quota isn't checked here — the server probes each token off-thread and the // Quota isn't checked here — the server probes each token off-thread and the
// account list flips pending → active/dead on its own. // account list flips pending → active/dead on its own.
if (fail === 0) { if (fail === 0) {
setStatus(`✓ 导入 ${ok} 项 · 正在后台检测额度…`) // Close immediately on success — the account lands in the table right away
// and the server backfills quota/email off-thread (pending → active/dead).
emit('imported') emit('imported')
setTimeout(() => emit('close'), 1000) emit('close')
} else { } else {
setStatus(`成功 ${ok} · 失败 ${fail} · ${errs.slice(0, 3).join(' | ')}`, true) setStatus(`成功 ${ok} · 失败 ${fail} · ${errs.slice(0, 3).join(' | ')}`, true)
emit('imported') emit('imported')