mirror of
https://github.com/CJackHwang/ds2api.git
synced 2026-05-05 00:45:29 +08:00
fix: 修改批量删除会话方式
- 从逐条单个删除改为官方的批量删除接口 - 单个删除函数保留备用
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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": "删除成功"})
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -135,7 +135,7 @@
|
||||
"sessionCount": "会话: {count}",
|
||||
"deleteAllSessions": "删除所有会话",
|
||||
"deleteAllSessionsConfirm": "确定要删除该账号的所有会话吗?此操作不可恢复。",
|
||||
"deleteAllSessionsSuccess": "成功删除 {count} 个会话"
|
||||
"deleteAllSessionsSuccess": "删除成功"
|
||||
},
|
||||
"apiTester": {
|
||||
"defaultMessage": "你好,请用一句话介绍你自己。",
|
||||
|
||||
Reference in New Issue
Block a user