diff --git a/internal/adapter/openai/deps.go b/internal/adapter/openai/deps.go index b033d3d..b2270c7 100644 --- a/internal/adapter/openai/deps.go +++ b/internal/adapter/openai/deps.go @@ -19,7 +19,7 @@ type DeepSeekCaller interface { CreateSession(ctx context.Context, a *auth.RequestAuth, maxAttempts int) (string, error) GetPow(ctx context.Context, a *auth.RequestAuth, maxAttempts int) (string, error) CallCompletion(ctx context.Context, a *auth.RequestAuth, payload map[string]any, powResp string, maxAttempts int) (*http.Response, error) - DeleteAllSessionsForToken(ctx context.Context, token string) (int, error) + DeleteAllSessionsForToken(ctx context.Context, token string) error } type ConfigReader interface { diff --git a/internal/adapter/openai/handler_chat.go b/internal/adapter/openai/handler_chat.go index a06e126..c13e75c 100644 --- a/internal/adapter/openai/handler_chat.go +++ b/internal/adapter/openai/handler_chat.go @@ -42,11 +42,11 @@ func (h *Handler) ChatCompletions(w http.ResponseWriter, r *http.Request) { // 2. 新请求可能获取到同一账号并开始使用 // 3. 异步删除仍在进行,会截断新请求正在使用的会话 if h.Store.AutoDeleteSessions() && a.DeepSeekToken != "" { - deleted, err := h.DS.DeleteAllSessionsForToken(context.Background(), a.DeepSeekToken) + err := h.DS.DeleteAllSessionsForToken(context.Background(), a.DeepSeekToken) if err != nil { config.Logger.Warn("[auto_delete_sessions] failed", "account", a.AccountID, "error", err) } else { - config.Logger.Debug("[auto_delete_sessions] deleted", "account", a.AccountID, "count", deleted) + config.Logger.Debug("[auto_delete_sessions] success", "account", a.AccountID) } } h.Auth.Release(a) diff --git a/internal/adapter/openai/stream_status_test.go b/internal/adapter/openai/stream_status_test.go index 9bd6ccd..033dc37 100644 --- a/internal/adapter/openai/stream_status_test.go +++ b/internal/adapter/openai/stream_status_test.go @@ -53,8 +53,8 @@ func (m streamStatusDSStub) CallCompletion(_ context.Context, _ *auth.RequestAut return m.resp, nil } -func (m streamStatusDSStub) DeleteAllSessionsForToken(_ context.Context, _ string) (int, error) { - return 0, nil +func (m streamStatusDSStub) DeleteAllSessionsForToken(_ context.Context, _ string) error { + return nil } func makeOpenAISSEHTTPResponse(lines ...string) *http.Response { diff --git a/internal/admin/deps.go b/internal/admin/deps.go index 5c58a4f..997c42b 100644 --- a/internal/admin/deps.go +++ b/internal/admin/deps.go @@ -42,7 +42,7 @@ type DeepSeekCaller interface { GetPow(ctx context.Context, a *auth.RequestAuth, maxAttempts int) (string, error) CallCompletion(ctx context.Context, a *auth.RequestAuth, payload map[string]any, powResp string, maxAttempts int) (*http.Response, error) GetSessionCountForToken(ctx context.Context, token string) (*deepseek.SessionStats, error) - DeleteAllSessionsForToken(ctx context.Context, token string) (int, error) + DeleteAllSessionsForToken(ctx context.Context, token string) error } var _ ConfigStore = (*config.Store)(nil) diff --git a/internal/admin/handler_accounts_testing.go b/internal/admin/handler_accounts_testing.go index 33f43c7..0dc602d 100644 --- a/internal/admin/handler_accounts_testing.go +++ b/internal/admin/handler_accounts_testing.go @@ -245,11 +245,11 @@ func (h *Handler) deleteAllSessions(w http.ResponseWriter, r *http.Request) { } // 删除所有会话 - deleted, err := h.DS.DeleteAllSessionsForToken(r.Context(), token) + err := h.DS.DeleteAllSessionsForToken(r.Context(), token) if err != nil { - writeJSON(w, http.StatusOK, map[string]any{"success": false, "message": "删除失败: " + err.Error(), "deleted": deleted}) + writeJSON(w, http.StatusOK, map[string]any{"success": false, "message": "删除失败: " + err.Error()}) return } - writeJSON(w, http.StatusOK, map[string]any{"success": true, "deleted": deleted, "message": fmt.Sprintf("成功删除 %d 个会话", deleted)}) + writeJSON(w, http.StatusOK, map[string]any{"success": true, "message": "删除成功"}) } diff --git a/internal/admin/handler_accounts_testing_test.go b/internal/admin/handler_accounts_testing_test.go index 47013c1..1636669 100644 --- a/internal/admin/handler_accounts_testing_test.go +++ b/internal/admin/handler_accounts_testing_test.go @@ -39,8 +39,8 @@ func (m *testingDSMock) CallCompletion(_ context.Context, _ *auth.RequestAuth, _ return nil, errors.New("should not call CallCompletion in this test") } -func (m *testingDSMock) DeleteAllSessionsForToken(_ context.Context, _ string) (int, error) { - return 0, nil +func (m *testingDSMock) DeleteAllSessionsForToken(_ context.Context, _ string) error { + return nil } func (m *testingDSMock) GetSessionCountForToken(_ context.Context, _ string) (*deepseek.SessionStats, error) { diff --git a/internal/deepseek/client_session_delete.go b/internal/deepseek/client_session_delete.go index a34a56f..e4814bf 100644 --- a/internal/deepseek/client_session_delete.go +++ b/internal/deepseek/client_session_delete.go @@ -5,7 +5,6 @@ import ( "errors" "fmt" "net/http" - "net/url" "ds2api/internal/auth" "ds2api/internal/config" @@ -115,124 +114,43 @@ func (c *Client) DeleteSessionForToken(ctx context.Context, token string, sessio } // DeleteAllSessions 删除所有会话(谨慎使用) -func (c *Client) DeleteAllSessions(ctx context.Context, a *auth.RequestAuth) (int, error) { - const maxNoProgress = 3 // 最大无进度次数 +func (c *Client) DeleteAllSessions(ctx context.Context, a *auth.RequestAuth) error { + headers := c.authHeaders(a.DeepSeekToken) + payload := map[string]any{} - deleted := 0 - cursor := "" - noProgressCount := 0 - - for { - sessions, hasMore, err := c.FetchSessionPage(ctx, a, cursor) - if err != nil { - return deleted, err - } - - deletedThisRound := 0 - for _, session := range sessions { - _, err := c.DeleteSession(ctx, a, session.ID, 1) - if err == nil { - deleted++ - deletedThisRound++ - } - } - - // 无进度检测:如果连续多轮没有成功删除任何会话,退出循环 - if deletedThisRound == 0 { - noProgressCount++ - if noProgressCount >= maxNoProgress { - config.Logger.Warn("[delete_all_sessions] exiting due to no progress", "deleted", deleted) - break - } - } else { - noProgressCount = 0 - } - - if !hasMore || len(sessions) == 0 { - break - } + resp, status, err := c.postJSONWithStatus(ctx, c.regular, DeepSeekDeleteAllSessionsURL, headers, payload) + if err != nil { + config.Logger.Warn("[delete_all_sessions] request error", "error", err) + return err } - return deleted, nil + code := intFrom(resp["code"]) + if status != http.StatusOK || code != 0 { + msg, _ := resp["msg"].(string) + config.Logger.Warn("[delete_all_sessions] failed", "status", status, "code", code, "msg", msg) + return fmt.Errorf("request failed: status=%d, code=%d, msg=%s", status, code, msg) + } + + return nil } // DeleteAllSessionsForToken 直接使用 token 删除所有会话(直通模式) -func (c *Client) DeleteAllSessionsForToken(ctx context.Context, token string) (int, error) { - const maxNoProgress = 3 // 最大无进度次数 +func (c *Client) DeleteAllSessionsForToken(ctx context.Context, token string) error { + headers := c.authHeaders(token) + payload := map[string]any{} - deleted := 0 - cursor := "" - noProgressCount := 0 - - for { - // 获取会话列表 - headers := c.authHeaders(token) - params := url.Values{} - params.Set("lte_cursor.pinned", "false") - if cursor != "" { - params.Set("lte_cursor", cursor) - } - reqURL := DeepSeekFetchSessionURL + "?" + params.Encode() - - resp, status, err := c.getJSONWithStatus(ctx, c.regular, reqURL, headers) - if err != nil { - return deleted, err - } - - code := intFrom(resp["code"]) - if status != http.StatusOK || code != 0 { - msg, _ := resp["msg"].(string) - return deleted, fmt.Errorf("fetch sessions failed: status=%d, code=%d, msg=%s", status, code, msg) - } - - data, _ := resp["data"].(map[string]any) - bizData, _ := data["biz_data"].(map[string]any) - chatSessions, _ := bizData["chat_sessions"].([]any) - hasMore, _ := bizData["has_more"].(bool) - - // 删除每个会话 - deletedThisRound := 0 - for _, s := range chatSessions { - if m, ok := s.(map[string]any); ok { - sessionID := stringFromMap(m, "id") - if sessionID == "" { - continue - } - _, err := c.DeleteSessionForToken(ctx, token, sessionID) - if err == nil { - deleted++ - deletedThisRound++ - } - } - } - - // 无进度检测:如果连续多轮没有成功删除任何会话,退出循环 - if deletedThisRound == 0 { - noProgressCount++ - if noProgressCount >= maxNoProgress { - config.Logger.Warn("[delete_all_sessions_for_token] exiting due to no progress", "deleted", deleted) - break - } - } else { - noProgressCount = 0 - } - - if !hasMore || len(chatSessions) == 0 { - break - } - - // 推进 cursor:从最后一个会话中提取 cursor(通常基于 updated_at 或 id) - if len(chatSessions) > 0 { - if lastSession, ok := chatSessions[len(chatSessions)-1].(map[string]any); ok { - // 尝试从响应中获取 cursor 字段,或使用最后会话的 ID/updated_at - if nextCursor, ok := bizData["cursor"].(string); ok && nextCursor != "" { - cursor = nextCursor - } else if nextCursor := stringFromMap(lastSession, "id"); nextCursor != "" { - cursor = nextCursor - } - } - } + resp, status, err := c.postJSONWithStatus(ctx, c.regular, DeepSeekDeleteAllSessionsURL, headers, payload) + if err != nil { + config.Logger.Warn("[delete_all_sessions_for_token] request error", "error", err) + return err } - return deleted, nil + code := intFrom(resp["code"]) + if status != http.StatusOK || code != 0 { + msg, _ := resp["msg"].(string) + config.Logger.Warn("[delete_all_sessions_for_token] failed", "status", status, "code", code, "msg", msg) + return fmt.Errorf("request failed: status=%d, code=%d, msg=%s", status, code, msg) + } + + return nil } diff --git a/internal/deepseek/constants.go b/internal/deepseek/constants.go index 35b5c29..f35332a 100644 --- a/internal/deepseek/constants.go +++ b/internal/deepseek/constants.go @@ -12,7 +12,8 @@ const ( DeepSeekCreatePowURL = "https://chat.deepseek.com/api/v0/chat/create_pow_challenge" DeepSeekCompletionURL = "https://chat.deepseek.com/api/v0/chat/completion" DeepSeekFetchSessionURL = "https://chat.deepseek.com/api/v0/chat_session/fetch_page" - DeepSeekDeleteSessionURL = "https://chat.deepseek.com/api/v0/chat_session/delete" + DeepSeekDeleteSessionURL = "https://chat.deepseek.com/api/v0/chat_session/delete" + DeepSeekDeleteAllSessionsURL = "https://chat.deepseek.com/api/v0/chat_session/delete_all" ) var defaultBaseHeaders = map[string]string{ diff --git a/webui/src/features/account/useAccountActions.js b/webui/src/features/account/useAccountActions.js index a14df92..e91f8fb 100644 --- a/webui/src/features/account/useAccountActions.js +++ b/webui/src/features/account/useAccountActions.js @@ -196,7 +196,7 @@ export function useAccountActions({ apiFetch, t, onMessage, onRefresh, config, f const data = await res.json() if (data.success) { - onMessage('success', t('accountManager.deleteAllSessionsSuccess', { count: data.deleted })) + onMessage('success', t('accountManager.deleteAllSessionsSuccess')) // 清除会话数显示 setSessionCounts(prev => { const newCounts = { ...prev } diff --git a/webui/src/locales/en.json b/webui/src/locales/en.json index 817b27c..ecdf16f 100644 --- a/webui/src/locales/en.json +++ b/webui/src/locales/en.json @@ -135,7 +135,7 @@ "sessionCount": "Sessions: {count}", "deleteAllSessions": "Delete all sessions", "deleteAllSessionsConfirm": "Are you sure you want to delete all sessions for this account? This action cannot be undone.", - "deleteAllSessionsSuccess": "Successfully deleted {count} sessions" + "deleteAllSessionsSuccess": "Successfully deleted all sessions" }, "apiTester": { "defaultMessage": "Hello, please introduce yourself in one sentence.", diff --git a/webui/src/locales/zh.json b/webui/src/locales/zh.json index 6631f99..a52c72f 100644 --- a/webui/src/locales/zh.json +++ b/webui/src/locales/zh.json @@ -135,7 +135,7 @@ "sessionCount": "会话: {count}", "deleteAllSessions": "删除所有会话", "deleteAllSessionsConfirm": "确定要删除该账号的所有会话吗?此操作不可恢复。", - "deleteAllSessionsSuccess": "成功删除 {count} 个会话" + "deleteAllSessionsSuccess": "删除成功" }, "apiTester": { "defaultMessage": "你好,请用一句话介绍你自己。",