Files
eightbf-analysis-go/internal/deerflow/auth.go
T
gozerosandCursor 0c45b86d67 chore: 初始化 eightbf-analysis-go(对齐 Java 会商作业服务)
Go 实现 /api/v1/analysis 契约、Demo/Live Worker、DeerFlow SSE;含设计文档与冒烟脚本。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-11-03 00:13:46 +08:00

73 lines
1.7 KiB
Go

package deerflow
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"sync"
"time"
"git.gozeros.com/wsk/eightbf-analysis-go/internal/config"
)
// Auth 向 DeerFlow 换 Bearer,过期前复用。
type Auth struct {
cfg config.Config
mu sync.Mutex
token string
expiresAt time.Time
httpClient *http.Client
}
func NewAuth(cfg config.Config) *Auth {
return &Auth{
cfg: cfg,
httpClient: &http.Client{Timeout: 30 * time.Second},
}
}
func (a *Auth) BearerToken() (string, error) {
a.mu.Lock()
defer a.mu.Unlock()
now := time.Now()
if a.token != "" && a.expiresAt.After(now.Add(2*time.Minute)) {
return a.token, nil
}
body, _ := json.Marshal(map[string]string{
"username": a.cfg.DeerFlowUser,
"password": a.cfg.DeerFlowPassword,
})
req, err := http.NewRequest(http.MethodPost, a.cfg.DeerFlowBaseURL+"/api/v1/auth/login/username", bytes.NewReader(body))
if err != nil {
return "", err
}
req.Header.Set("Content-Type", "application/json")
resp, err := a.httpClient.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
raw, _ := io.ReadAll(resp.Body)
if resp.StatusCode >= 400 {
return "", fmt.Errorf("DeerFlow 登录失败 HTTP %d: %s", resp.StatusCode, string(raw))
}
var parsed struct {
AccessToken string `json:"access_token"`
ExpiresIn int `json:"expires_in"`
}
if err := json.Unmarshal(raw, &parsed); err != nil {
return "", err
}
if parsed.AccessToken == "" {
return "", fmt.Errorf("DeerFlow 登录响应无 access_token")
}
if parsed.ExpiresIn <= 0 {
parsed.ExpiresIn = 3600
}
a.token = parsed.AccessToken
a.expiresAt = now.Add(time.Duration(parsed.ExpiresIn) * time.Second)
return a.token, nil
}