Files
xiaolajiaoyyds 6f71df4849 Initial open-source release
多平台账号批量注册系统,支持 OpenAI / Grok / Kiro / Gemini 平台的全自动注册。

Features:
- 用户名/密码注册登录(首个用户自动管理员)
- JWT 认证 + bcrypt 密码加密
- 积分系统与多用户管理
- HuggingFace Space 弹性 Worker 节点管理
- Cloudflare Worker 负载均衡
- 实时日志推送(SSE/WebSocket)
- 管理后台(用户管理、系统设置、代理池、卡池)

Tech Stack: Go 1.25 / Gin / GORM / PostgreSQL / Vue 3 / Vite / TailwindCSS
2026-03-22 03:44:42 +08:00

115 lines
3.1 KiB
Go
Raw Permalink 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 worker
import (
"bufio"
"context"
"fmt"
"net"
"net/http"
"net/url"
"os"
"time"
)
// envProxyURL 读取环境变量中的代理地址(HTTPS_PROXY 优先于 HTTP_PROXY)
func envProxyURL() *url.URL {
raw := os.Getenv("HTTPS_PROXY")
if raw == "" {
raw = os.Getenv("HTTP_PROXY")
}
if raw == "" {
raw = os.Getenv("https_proxy")
}
if raw == "" {
raw = os.Getenv("http_proxy")
}
if raw == "" {
return nil
}
u, err := url.Parse(raw)
if err != nil {
return nil
}
return u
}
// proxyChainDialer 创建通过 firstProxy(HTTP CONNECT)建立隧道的 DialContext,
// 用于实现代理链:App → firstProxy(Clash) → 目标地址(后端代理)
//
// 配合 transport.Proxy = http.ProxyURL(backendProxy) 使用时,链路为:
// App → Clash(CONNECT) → backendProxy → 最终目标
func proxyChainDialer(firstProxy *url.URL) func(ctx context.Context, network, addr string) (net.Conn, error) {
return func(ctx context.Context, network, addr string) (net.Conn, error) {
// 1. TCP 连接到第一级代理(Clash)
d := net.Dialer{Timeout: 15 * time.Second}
conn, err := d.DialContext(ctx, "tcp", firstProxy.Host)
if err != nil {
return nil, fmt.Errorf("连接代理 %s 失败: %w", firstProxy.Host, err)
}
// 2. 发送 HTTP CONNECT 请求,建立到目标地址的隧道
req := &http.Request{
Method: "CONNECT",
URL: &url.URL{Opaque: addr},
Host: addr,
Header: make(http.Header),
}
if err := req.Write(conn); err != nil {
conn.Close()
return nil, fmt.Errorf("发送 CONNECT 失败: %w", err)
}
// 3. 读取 CONNECT 响应
resp, err := http.ReadResponse(bufio.NewReader(conn), req)
if err != nil {
conn.Close()
return nil, fmt.Errorf("CONNECT 响应读取失败: %w", err)
}
resp.Body.Close()
if resp.StatusCode != http.StatusOK {
conn.Close()
return nil, fmt.Errorf("CONNECT 返回 %d", resp.StatusCode)
}
return conn, nil
}
}
// applyProxy 为 http.Transport 设置代理策略,支持三层回退:
// 1. 有请求代理 + 有环境代理 → 代理链(环境代理 → 请求代理 → 目标)
// 2. 有请求代理,无环境代理 → 直连请求代理
// 3. 无请求代理 → 回退到环境变量代理
// 4. 都没有 → 直连
func applyProxy(transport *http.Transport, proxy *ProxyEntry) {
envProxy := envProxyURL()
// 解析请求级代理
var reqProxyURL *url.URL
if proxy != nil {
proxyStr := proxy.HTTPS
if proxyStr == "" {
proxyStr = proxy.HTTP
}
if proxyStr != "" {
if u, err := url.Parse(proxyStr); err == nil {
reqProxyURL = u
}
}
}
switch {
case reqProxyURL != nil && envProxy != nil:
// 代理链:App → Clash(envProxy) → 后端代理(reqProxy) → 目标
transport.DialContext = proxyChainDialer(envProxy)
transport.Proxy = http.ProxyURL(reqProxyURL)
case reqProxyURL != nil:
// 仅后端代理(无 Clash)
transport.Proxy = http.ProxyURL(reqProxyURL)
default:
// 无请求代理 → 环境变量回退(有 Clash 走 Clash,没有就直连)
transport.Proxy = http.ProxyFromEnvironment
}
}