Files
regplatformm/internal/handler/proxy_handler.go
T
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

243 lines
6.5 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 handler
import (
"context"
"fmt"
"net"
"net/http"
"net/url"
"strconv"
"time"
"github.com/gin-gonic/gin"
"github.com/xiaolajiaoyyds/regplatformm/internal/middleware"
"github.com/xiaolajiaoyyds/regplatformm/internal/model"
"golang.org/x/net/proxy"
"gorm.io/gorm"
)
// ProxyHandler 用户代理管理
type ProxyHandler struct {
db *gorm.DB
}
// NewProxyHandler 创建代理处理器
func NewProxyHandler(db *gorm.DB) *ProxyHandler {
return &ProxyHandler{db: db}
}
type createProxyReq struct {
Name string `json:"name" binding:"max=100"`
Protocol string `json:"protocol" binding:"required,oneof=http https socks5"`
Host string `json:"host" binding:"required,max=200"`
Port int `json:"port" binding:"required,min=1,max=65535"`
Username string `json:"username" binding:"max=100"`
Password string `json:"password" binding:"max=200"`
}
// List 获取当前用户的代理列表(GET /api/proxies)
func (h *ProxyHandler) List(c *gin.Context) {
user := middleware.GetUser(c)
var proxies []model.UserProxy
h.db.Where("user_id = ?", user.ID).Order("created_at DESC").Find(&proxies)
// 脱敏密码
for i := range proxies {
if proxies[i].Password != "" {
proxies[i].Password = "******"
}
}
c.JSON(http.StatusOK, proxies)
}
// Create 添加代理(POST /api/proxies)
func (h *ProxyHandler) Create(c *gin.Context) {
user := middleware.GetUser(c)
var req createProxyReq
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"detail": err.Error()})
return
}
// 限制每用户最多 20 个代理
var count int64
h.db.Model(&model.UserProxy{}).Where("user_id = ?", user.ID).Count(&count)
if count >= 20 {
c.JSON(http.StatusBadRequest, gin.H{"detail": "最多保存 20 个代理"})
return
}
p := model.UserProxy{
UserID: user.ID,
Name: req.Name,
Protocol: req.Protocol,
Host: req.Host,
Port: req.Port,
Username: req.Username,
Password: req.Password,
}
if err := h.db.Create(&p).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"detail": "保存失败"})
return
}
if p.Password != "" {
p.Password = "******"
}
c.JSON(http.StatusOK, p)
}
// Update 更新代理(PUT /api/proxies/:id)
func (h *ProxyHandler) Update(c *gin.Context) {
user := middleware.GetUser(c)
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"detail": "无效的 ID"})
return
}
var p model.UserProxy
if err := h.db.Where("id = ? AND user_id = ?", id, user.ID).First(&p).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"detail": "代理不存在"})
return
}
var req createProxyReq
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"detail": err.Error()})
return
}
p.Name = req.Name
p.Protocol = req.Protocol
p.Host = req.Host
p.Port = req.Port
p.Username = req.Username
if req.Password != "******" {
p.Password = req.Password
}
h.db.Save(&p)
if p.Password != "" {
p.Password = "******"
}
c.JSON(http.StatusOK, p)
}
// Delete 删除代理(DELETE /api/proxies/:id)
func (h *ProxyHandler) Delete(c *gin.Context) {
user := middleware.GetUser(c)
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"detail": "无效的 ID"})
return
}
result := h.db.Where("id = ? AND user_id = ?", id, user.ID).Delete(&model.UserProxy{})
if result.RowsAffected == 0 {
c.JSON(http.StatusNotFound, gin.H{"detail": "代理不存在"})
return
}
c.JSON(http.StatusOK, gin.H{"message": "已删除"})
}
// Test 测试代理连通性(POST /api/proxies/test)
// 支持两种方式:传 proxy_id 测试已保存的,或直接传代理信息测试
func (h *ProxyHandler) Test(c *gin.Context) {
user := middleware.GetUser(c)
var req struct {
ProxyID uint `json:"proxy_id"`
Protocol string `json:"protocol"`
Host string `json:"host"`
Port int `json:"port"`
Username string `json:"username"`
Password string `json:"password"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"detail": err.Error()})
return
}
var proxyURL string
if req.ProxyID > 0 {
var p model.UserProxy
if err := h.db.Where("id = ? AND user_id = ?", req.ProxyID, user.ID).First(&p).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"detail": "代理不存在"})
return
}
proxyURL = p.URL()
} else if req.Host != "" {
auth := ""
if req.Username != "" {
auth = req.Username + ":" + req.Password + "@"
}
proxyURL = fmt.Sprintf("%s://%s%s:%d", req.Protocol, auth, req.Host, req.Port)
} else {
c.JSON(http.StatusBadRequest, gin.H{"detail": "请提供代理信息"})
return
}
ok, latency, err := testProxy(proxyURL)
if ok {
c.JSON(http.StatusOK, gin.H{"ok": true, "latency_ms": latency, "message": fmt.Sprintf("连接成功(延迟 %dms)", latency)})
} else {
msg := "连接失败"
if err != nil {
msg = "连接失败: " + err.Error()
}
c.JSON(http.StatusOK, gin.H{"ok": false, "message": msg})
}
}
// testProxy 测试代理连通性,返回是否成功、延迟毫秒、错误
func testProxy(proxyURL string) (bool, int64, error) {
start := time.Now()
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
parsed, err := url.Parse(proxyURL)
if err != nil {
return false, 0, fmt.Errorf("代理地址格式错误: %w", err)
}
switch parsed.Scheme {
case "socks5":
auth := &proxy.Auth{}
if parsed.User != nil {
auth.User = parsed.User.Username()
auth.Password, _ = parsed.User.Password()
} else {
auth = nil
}
dialer, err := proxy.SOCKS5("tcp", parsed.Host, auth, proxy.Direct)
if err != nil {
return false, 0, fmt.Errorf("SOCKS5 初始化失败: %w", err)
}
var conn net.Conn
if cd, ok := dialer.(proxy.ContextDialer); ok {
conn, err = cd.DialContext(ctx, "tcp", "httpbin.org:80")
} else {
conn, err = dialer.Dial("tcp", "httpbin.org:80")
}
if err != nil {
return false, 0, fmt.Errorf("SOCKS5 连接失败: %w", err)
}
conn.Close()
case "http", "https":
transport := &http.Transport{
Proxy: http.ProxyURL(parsed),
}
client := &http.Client{Transport: transport, Timeout: 10 * time.Second}
req, _ := http.NewRequestWithContext(ctx, "GET", "http://httpbin.org/ip", nil)
resp, err := client.Do(req)
if err != nil {
return false, 0, fmt.Errorf("HTTP 代理连接失败: %w", err)
}
resp.Body.Close()
default:
return false, 0, fmt.Errorf("不支持的协议: %s", parsed.Scheme)
}
latency := time.Since(start).Milliseconds()
return true, latency, nil
}