Files
ds2api/internal/deepseek/client_session_delete.go
latticeon af7dc134bb fix: 修复会话管理相关问题并拆分文件
1. 修复无限循环问题
   - DeleteAllSessions/DeleteAllSessionsForToken 添加无进度检测
   - 连续 3 轮删除失败则退出循环
   - DeleteAllSessionsForToken 添加 cursor 推进逻辑

2. 修复字段语义不准确
   - TotalCount 重命名为 FirstPageCount
   - 明确该值仅统计第一页,多页账户需关注 HasMore

3. 修复 defer 执行顺序问题
   - 合并两个 defer,确保先删除会话再释放账号
   - 使用同步删除避免并发截断风险

4. 文件拆分
   - 新建 client_session_delete.go 处理会话删除
   - client_session.go 专注于会话查询
2026-03-16 01:44:21 +08:00

239 lines
6.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package deepseek
import (
"context"
"errors"
"fmt"
"net/http"
"net/url"
"ds2api/internal/auth"
"ds2api/internal/config"
)
// DeleteSessionResult 删除会话结果
type DeleteSessionResult struct {
SessionID string // 会话 ID
Success bool // 是否成功
ErrorMessage string // 错误信息
}
// DeleteSession 删除单个会话
func (c *Client) DeleteSession(ctx context.Context, a *auth.RequestAuth, sessionID string, maxAttempts int) (*DeleteSessionResult, error) {
if maxAttempts <= 0 {
maxAttempts = c.maxRetries
}
result := &DeleteSessionResult{
SessionID: sessionID,
}
if sessionID == "" {
result.ErrorMessage = "session_id is required"
return result, errors.New(result.ErrorMessage)
}
attempts := 0
refreshed := false
for attempts < maxAttempts {
headers := c.authHeaders(a.DeepSeekToken)
payload := map[string]any{
"chat_session_id": sessionID,
}
resp, status, err := c.postJSONWithStatus(ctx, c.regular, DeepSeekDeleteSessionURL, headers, payload)
if err != nil {
config.Logger.Warn("[delete_session] request error", "error", err, "session_id", sessionID)
attempts++
continue
}
code := intFrom(resp["code"])
if status == http.StatusOK && code == 0 {
result.Success = true
return result, nil
}
msg, _ := resp["msg"].(string)
result.ErrorMessage = fmt.Sprintf("status=%d, code=%d, msg=%s", status, code, msg)
config.Logger.Warn("[delete_session] failed", "status", status, "code", code, "msg", msg, "session_id", sessionID)
if a.UseConfigToken {
if isTokenInvalid(status, code, msg) && !refreshed {
if c.Auth.RefreshToken(ctx, a) {
refreshed = true
continue
}
}
if c.Auth.SwitchAccount(ctx, a) {
refreshed = false
attempts++
continue
}
}
attempts++
}
result.Success = false
result.ErrorMessage = "delete session failed after retries"
return result, errors.New(result.ErrorMessage)
}
// DeleteSessionForToken 直接使用 token 删除会话(直通模式)
func (c *Client) DeleteSessionForToken(ctx context.Context, token string, sessionID string) (*DeleteSessionResult, error) {
result := &DeleteSessionResult{
SessionID: sessionID,
}
if sessionID == "" {
result.ErrorMessage = "session_id is required"
return result, errors.New(result.ErrorMessage)
}
headers := c.authHeaders(token)
payload := map[string]any{
"chat_session_id": sessionID,
}
resp, status, err := c.postJSONWithStatus(ctx, c.regular, DeepSeekDeleteSessionURL, headers, payload)
if err != nil {
result.ErrorMessage = err.Error()
return result, err
}
code := intFrom(resp["code"])
if status != http.StatusOK || code != 0 {
msg, _ := resp["msg"].(string)
result.ErrorMessage = fmt.Sprintf("request failed: status=%d, code=%d, msg=%s", status, code, msg)
return result, fmt.Errorf(result.ErrorMessage)
}
result.Success = true
return result, nil
}
// DeleteAllSessions 删除所有会话(谨慎使用)
func (c *Client) DeleteAllSessions(ctx context.Context, a *auth.RequestAuth) (int, error) {
const maxNoProgress = 3 // 最大无进度次数
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
}
}
return deleted, nil
}
// DeleteAllSessionsForToken 直接使用 token 删除所有会话(直通模式)
func (c *Client) DeleteAllSessionsForToken(ctx context.Context, token string) (int, error) {
const maxNoProgress = 3 // 最大无进度次数
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
}
}
}
}
return deleted, nil
}