262 lines
7.1 KiB
Go
262 lines
7.1 KiB
Go
package main
|
||
|
||
import (
|
||
"encoding/json"
|
||
"fmt"
|
||
"io"
|
||
"log"
|
||
"net/http"
|
||
"strconv"
|
||
"strings"
|
||
"time"
|
||
)
|
||
|
||
const (
|
||
totalRetryCount = 100000
|
||
nacosURL = "http://192.168.31.41:30848/nacos/v1/cs/configs?dataId=wq_account&group=quantify"
|
||
)
|
||
|
||
// NacosConfig 对应 nacos 返回的账号配置
|
||
type NacosConfig struct {
|
||
UserName string `json:"user_name"`
|
||
Password string `json:"password"`
|
||
}
|
||
|
||
// basicAuthTransport 为每个请求自动添加 Basic Auth 头
|
||
type basicAuthTransport struct {
|
||
username string
|
||
password string
|
||
base http.RoundTripper
|
||
}
|
||
|
||
func (t *basicAuthTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||
req.SetBasicAuth(t.username, t.password)
|
||
return t.base.RoundTrip(req)
|
||
}
|
||
|
||
// Login 登录 WorldQuant Brain API,返回带 BasicAuth 的 HTTP Client
|
||
func Login() (*http.Client, error) {
|
||
// 1. 从 nacos 获取账号密码
|
||
resp, err := http.Get(nacosURL)
|
||
if err != nil {
|
||
log.Printf("获取账号配置失败: %v", err)
|
||
return nil, err
|
||
}
|
||
defer resp.Body.Close()
|
||
|
||
if resp.StatusCode != http.StatusOK {
|
||
return nil, fmt.Errorf("nacos 返回非 200 状态码: %d", resp.StatusCode)
|
||
}
|
||
|
||
var config NacosConfig
|
||
if err := json.NewDecoder(resp.Body).Decode(&config); err != nil {
|
||
return nil, fmt.Errorf("解析 nacos 配置失败: %w", err)
|
||
}
|
||
|
||
log.Printf("正在登录账户: %s", config.UserName)
|
||
|
||
// 2. 创建 HTTP Client,后续所有请求都会使用 BasicAuth
|
||
client := &http.Client{
|
||
Timeout: 30 * time.Second,
|
||
Transport: &basicAuthTransport{
|
||
username: config.UserName,
|
||
password: config.Password,
|
||
base: http.DefaultTransport,
|
||
},
|
||
}
|
||
|
||
// 3. 发送登录请求
|
||
loginReq, _ := http.NewRequest("POST", "https://api.worldquantbrain.com/authentication", nil)
|
||
loginResp, err := client.Do(loginReq)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("登录请求失败: %w", err)
|
||
}
|
||
defer loginResp.Body.Close()
|
||
|
||
log.Printf("登录状态: %d", loginResp.StatusCode)
|
||
|
||
if loginResp.StatusCode == http.StatusCreated {
|
||
log.Println("登录成功!")
|
||
return client, nil
|
||
}
|
||
|
||
body, _ := io.ReadAll(loginResp.Body)
|
||
return nil, fmt.Errorf("登录失败: %s", string(body))
|
||
}
|
||
|
||
// SubmitAlpha 提交 alpha,自动重试直到成功或达到最大重试次数
|
||
// 返回 nil 表示提交成功,否则返回错误
|
||
func SubmitAlpha(alphaID string) error {
|
||
retryCount := 0
|
||
var client *http.Client
|
||
|
||
for retryCount < totalRetryCount {
|
||
// 如果没有有效 client,则重新登录
|
||
if client == nil {
|
||
var err error
|
||
client, err = Login()
|
||
if err != nil {
|
||
log.Printf("登录失败: %v, 10秒后重试 (重试次数: %d)", err, retryCount)
|
||
time.Sleep(10 * time.Second)
|
||
retryCount++
|
||
continue
|
||
}
|
||
}
|
||
|
||
url := fmt.Sprintf("https://api.worldquantbrain.com/alphas/%s/submit", alphaID)
|
||
log.Printf("请求 URL: %s", url)
|
||
|
||
// 发送提交请求
|
||
resp, err := client.Post(url, "application/json", nil)
|
||
if err != nil {
|
||
log.Printf("网络请求异常 (alpha=%s, retry=%d): %v", alphaID, retryCount, err)
|
||
retryCount++
|
||
time.Sleep(10 * time.Second)
|
||
continue
|
||
}
|
||
|
||
// 处理 400 特殊情形:已提交,进入轮询
|
||
if resp.StatusCode == http.StatusBadRequest {
|
||
bodyBytes, _ := io.ReadAll(resp.Body)
|
||
resp.Body.Close()
|
||
bodyStr := string(bodyBytes)
|
||
if strings.Contains(bodyStr, "The plain HTTP request was sent to HTTPS port") {
|
||
log.Println("Alpha 已提交,正在轮询状态...")
|
||
pollInterval := 1.0 // 秒
|
||
for {
|
||
time.Sleep(time.Duration(pollInterval) * time.Second)
|
||
fmt.Print(".")
|
||
// 重新 GET 查询状态
|
||
pollResp, err := client.Get(url)
|
||
if err != nil {
|
||
log.Printf("轮询请求失败: %v", err)
|
||
break
|
||
}
|
||
// 检查 Retry-After 头
|
||
if retryAfter := pollResp.Header.Get("Retry-After"); retryAfter != "" {
|
||
if f, err := strconv.ParseFloat(retryAfter, 64); err == nil && f > 0 {
|
||
pollInterval = max(f, 3.0)
|
||
} else {
|
||
pollInterval = 3.0
|
||
}
|
||
} else {
|
||
pollInterval = 3.0
|
||
}
|
||
// 当状态不再是 400 且不包含该特定消息时,退出轮询
|
||
if pollResp.StatusCode != http.StatusBadRequest {
|
||
resp = pollResp
|
||
break
|
||
}
|
||
bodyBytes2, _ := io.ReadAll(pollResp.Body)
|
||
pollResp.Body.Close()
|
||
if !strings.Contains(string(bodyBytes2), "The plain HTTP request was sent to HTTPS port") {
|
||
resp = pollResp
|
||
break
|
||
}
|
||
pollResp.Body.Close()
|
||
}
|
||
log.Printf("轮询结束,最终状态码: %d", resp.StatusCode)
|
||
} else {
|
||
// 非特殊 400,按普通错误处理
|
||
resp.Body.Close()
|
||
}
|
||
}
|
||
|
||
// 确保 resp 不为 nil(如果上面分支没有设置 resp,则跳过本次循环)
|
||
if resp == nil {
|
||
retryCount++
|
||
continue
|
||
}
|
||
|
||
// 根据状态码处理
|
||
switch resp.StatusCode {
|
||
case http.StatusTooManyRequests: // 429
|
||
log.Println("触发限流 (429),休眠 60 秒后重试")
|
||
resp.Body.Close()
|
||
time.Sleep(60 * time.Second)
|
||
retryCount++
|
||
continue
|
||
|
||
case http.StatusUnauthorized: // 401
|
||
log.Println("认证失效,重新登录")
|
||
resp.Body.Close()
|
||
client = nil
|
||
retryCount++
|
||
continue
|
||
|
||
case http.StatusNotFound: // 404
|
||
log.Printf("Alpha %s 不存在或超时,重试 (%d/%d)", alphaID, retryCount+1, totalRetryCount)
|
||
resp.Body.Close()
|
||
retryCount++
|
||
continue
|
||
|
||
case http.StatusForbidden: // 403
|
||
log.Printf("%s 提交失败 (403)", alphaID)
|
||
var failChecks []map[string]interface{}
|
||
var bodyMap map[string]interface{}
|
||
if err := json.NewDecoder(resp.Body).Decode(&bodyMap); err == nil {
|
||
if isObj, ok := bodyMap["is"].(map[string]interface{}); ok {
|
||
if checks, ok := isObj["checks"].([]interface{}); ok {
|
||
for _, c := range checks {
|
||
if ch, ok := c.(map[string]interface{}); ok {
|
||
if result, ok := ch["result"]; ok && result == "FAIL" {
|
||
failChecks = append(failChecks, ch)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
resp.Body.Close()
|
||
log.Printf("失败的检查项: %v", failChecks)
|
||
// 如果因为提交次数超限而失败,放弃重试
|
||
for _, ch := range failChecks {
|
||
if name, ok := ch["name"]; ok {
|
||
if name == "REGULAR_SUBMISSION" || name == "SUPER_SUBMISSION" {
|
||
return fmt.Errorf("提交次数超过限制: %v", failChecks)
|
||
}
|
||
}
|
||
}
|
||
return fmt.Errorf("提交失败,HTTP 403")
|
||
|
||
case http.StatusOK: // 200
|
||
log.Printf("%s 提交成功", alphaID)
|
||
resp.Body.Close()
|
||
return nil
|
||
|
||
default:
|
||
// 处理 5xx 错误
|
||
if resp.StatusCode >= 500 && resp.StatusCode < 600 {
|
||
log.Printf("服务器错误 %d,5 秒后重试", resp.StatusCode)
|
||
resp.Body.Close()
|
||
time.Sleep(5 * time.Second)
|
||
retryCount++
|
||
continue
|
||
}
|
||
// 其他非 2xx 状态码视为失败
|
||
bodyBytes, _ := io.ReadAll(resp.Body)
|
||
resp.Body.Close()
|
||
return fmt.Errorf("未处理的响应状态码 %d: %s", resp.StatusCode, string(bodyBytes))
|
||
}
|
||
}
|
||
|
||
return fmt.Errorf("达到最大重试次数 %d,提交失败", totalRetryCount)
|
||
}
|
||
|
||
// max 返回两个 float64 中的较大值
|
||
func max(a, b float64) float64 {
|
||
if a > b {
|
||
return a
|
||
}
|
||
return b
|
||
}
|
||
|
||
func main() {
|
||
// 使用示例
|
||
alphaID := "your_alpha_id_here"
|
||
if err := SubmitAlpha(alphaID); err != nil {
|
||
log.Fatalf("提交失败: %v", err)
|
||
}
|
||
log.Println("提交完成")
|
||
}
|