sub2api/backend/internal/handler/admin/windsurf_handler.go
win 21325afb33
Some checks failed
CI / test (push) Failing after 10s
CI / frontend (push) Failing after 8s
CI / golangci-lint (push) Failing after 5s
Security Scan / backend-security (push) Failing after 5s
Security Scan / frontend-security (push) Failing after 4s
feat(windsurf): 补全ops日志记录与endpoint派生,对齐其他平台
- windsurf_gateway_service: 添加上游延迟/TTFT/错误上下文记录
- endpoint: DeriveUpstreamEndpoint 添加 PlatformWindsurf 分支
- ops_error_logger: guessPlatformFromPath 添加 /windsurf/ 识别
2026-04-23 20:46:27 +08:00

312 lines
7.5 KiB
Go

package admin
import (
"net/http"
"strconv"
"github.com/Wei-Shaw/sub2api/internal/handler/dto"
"github.com/Wei-Shaw/sub2api/internal/pkg/response"
"github.com/Wei-Shaw/sub2api/internal/pkg/windsurf"
"github.com/Wei-Shaw/sub2api/internal/service"
"github.com/gin-gonic/gin"
)
type WindsurfHandler struct {
authService *service.WindsurfAuthService
lsService *service.WindsurfLSService
probeService *service.WindsurfProbeService
}
func NewWindsurfHandler(
authService *service.WindsurfAuthService,
lsService *service.WindsurfLSService,
probeService *service.WindsurfProbeService,
) *WindsurfHandler {
return &WindsurfHandler{
authService: authService,
lsService: lsService,
probeService: probeService,
}
}
func (h *WindsurfHandler) Login(c *gin.Context) {
var req dto.WindsurfLoginRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, err.Error())
return
}
concurrency := 1
if req.Concurrency != nil && *req.Concurrency > 0 {
concurrency = *req.Concurrency
}
priority := 0
if req.Priority != nil {
priority = *req.Priority
}
probeAfter := false
if req.ProbeAfter != nil {
probeAfter = *req.ProbeAfter
}
input := &service.WindsurfLoginInput{
Email: req.Email,
Password: req.Password,
Name: req.Name,
Notes: req.Notes,
ProxyID: req.ProxyID,
GroupIDs: req.GroupIDs,
Concurrency: concurrency,
Priority: priority,
ProbeAfter: probeAfter,
LSInstanceID: req.LSInstanceID,
}
output, err := h.authService.Login(c.Request.Context(), input)
if err != nil {
response.Error(c, http.StatusInternalServerError, err.Error())
return
}
response.Success(c, dto.WindsurfLoginResponse{
AccountID: output.AccountID,
Platform: "windsurf",
Type: "windsurf-session",
Email: output.Email,
Tier: output.Tier,
AuthMethod: output.AuthMethod,
APIKeyPresent: output.APIKeyPresent,
RefreshTokenPresent: output.RefreshTokenPresent,
})
}
func (h *WindsurfHandler) BatchLogin(c *gin.Context) {
var req dto.WindsurfBatchLoginRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, err.Error())
return
}
concurrency := 1
if req.Concurrency != nil && *req.Concurrency > 0 {
concurrency = *req.Concurrency
}
priority := 0
if req.Priority != nil {
priority = *req.Priority
}
probeAfter := false
if req.ProbeAfter != nil {
probeAfter = *req.ProbeAfter
}
results, err := h.authService.BatchLogin(
c.Request.Context(),
req.Items,
req.ProxyID,
req.GroupIDs,
concurrency,
priority,
probeAfter,
)
if err != nil {
response.Error(c, http.StatusInternalServerError, err.Error())
return
}
successCount := 0
failCount := 0
batchResults := make([]dto.WindsurfBatchLoginResult, 0, len(results))
for _, r := range results {
br := dto.WindsurfBatchLoginResult{
Email: r.Email,
Success: r.Success,
Error: r.Error,
}
if r.Success && r.Output != nil {
successCount++
br.Account = &dto.WindsurfLoginResponse{
AccountID: r.Output.AccountID,
Platform: "windsurf",
Type: "windsurf-session",
Email: r.Output.Email,
Tier: r.Output.Tier,
AuthMethod: r.Output.AuthMethod,
APIKeyPresent: r.Output.APIKeyPresent,
RefreshTokenPresent: r.Output.RefreshTokenPresent,
}
} else {
failCount++
}
batchResults = append(batchResults, br)
}
response.Success(c, dto.WindsurfBatchLoginResponse{
Results: batchResults,
Total: len(results),
SuccessCount: successCount,
FailCount: failCount,
})
}
func (h *WindsurfHandler) RefreshToken(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.ParseInt(idStr, 10, 64)
if err != nil {
response.BadRequest(c, "invalid account id")
return
}
if err := h.authService.RefreshToken(c.Request.Context(), id); err != nil {
response.Error(c, http.StatusInternalServerError, err.Error())
return
}
response.Success(c, dto.WindsurfRefreshTokenResponse{
Refreshed: true,
})
}
func (h *WindsurfHandler) BatchRefreshTokens(c *gin.Context) {
var req dto.WindsurfBatchIDsRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, err.Error())
return
}
successCount := 0
failCount := 0
for _, id := range req.AccountIDs {
if err := h.authService.RefreshToken(c.Request.Context(), id); err != nil {
failCount++
} else {
successCount++
}
}
response.Success(c, gin.H{
"total": len(req.AccountIDs),
"success_count": successCount,
"fail_count": failCount,
})
}
func (h *WindsurfHandler) GetLSStatus(c *gin.Context) {
if h.lsService == nil {
response.Success(c, dto.WindsurfLSStatusResponse{
Mode: "disabled",
Healthy: false,
})
return
}
status := h.lsService.Status()
resp := dto.WindsurfLSStatusResponse{
Mode: status.Mode,
Healthy: status.Healthy,
Instances: status.Instances,
Endpoint: status.Endpoint,
}
if dc, ok := h.lsService.Connector().(*windsurf.DockerDiscoveryConnector); ok {
for _, inst := range dc.InstanceStatuses() {
resp.Details = append(resp.Details, dto.WindsurfLSInstanceDetail{
ContainerID: inst.ContainerID,
ContainerName: inst.ContainerName,
Host: inst.Host,
Port: inst.Port,
Healthy: inst.Healthy,
DiscoveredAt: inst.DiscoveredAt.Format("2006-01-02T15:04:05Z07:00"),
LastProbeAt: inst.LastProbeAt.Format("2006-01-02T15:04:05Z07:00"),
LastProbeErr: inst.LastProbeErr,
})
}
}
response.Success(c, resp)
}
func (h *WindsurfHandler) ListModels(c *gin.Context) {
models := windsurf.ListModelsOpenAI()
response.Success(c, models)
}
func (h *WindsurfHandler) Probe(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.ParseInt(idStr, 10, 64)
if err != nil {
response.BadRequest(c, "invalid account id")
return
}
result, err := h.probeService.ProbeAccount(c.Request.Context(), id)
if err != nil {
response.Error(c, http.StatusInternalServerError, err.Error())
return
}
response.Success(c, result)
}
func (h *WindsurfHandler) BatchProbe(c *gin.Context) {
var req dto.WindsurfBatchIDsRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, err.Error())
return
}
type probeResult struct {
AccountID int64 `json:"account_id"`
Success bool `json:"success"`
Tier string `json:"tier,omitempty"`
Error string `json:"error,omitempty"`
}
results := make([]probeResult, 0, len(req.AccountIDs))
successCount := 0
failCount := 0
for _, id := range req.AccountIDs {
r, err := h.probeService.ProbeAccount(c.Request.Context(), id)
if err != nil {
failCount++
results = append(results, probeResult{AccountID: id, Error: err.Error()})
continue
}
if r.Error != "" {
failCount++
results = append(results, probeResult{AccountID: id, Error: r.Error})
continue
}
successCount++
results = append(results, probeResult{AccountID: id, Success: true, Tier: r.Tier})
}
response.Success(c, gin.H{
"results": results,
"total": len(req.AccountIDs),
"success_count": successCount,
"fail_count": failCount,
})
}
func (h *WindsurfHandler) GetRuntime(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.ParseInt(idStr, 10, 64)
if err != nil {
response.BadRequest(c, "invalid account id")
return
}
result, err := h.probeService.GetRuntime(c.Request.Context(), id)
if err != nil {
response.Error(c, http.StatusInternalServerError, err.Error())
return
}
response.Success(c, result)
}