Files
ds2api/internal/deepseek/client_session_delete.go
latticeon dfea092583 fix: 更新测试 mock 结构体以实现新增的接口方法
会话管理功能新增接口方法后,同步更新测试 mock 结构体:
- mockOpenAIConfig: 添加 AutoDeleteSessions() 方法
- streamStatusDSStub: 添加 DeleteAllSessionsForToken() 方法
- testingDSMock: 添加 DeleteAllSessionsForToken() 和 GetSessionCountForToken() 方法

同时修复 client_session_delete.go 中 fmt.Errorf 使用非常量格式字符串的编译错误,改用 errors.New()
2026-03-16 11:58:07 +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, errors.New(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
}