Initial commit: proxy management platform

This commit is contained in:
2026-06-10 21:52:17 +00:00
commit 1a00a87024
47 changed files with 6747 additions and 0 deletions
+192
View File
@@ -0,0 +1,192 @@
package main
import (
"context"
"log"
"os"
"os/signal"
"syscall"
"time"
"proxy-platform/internal/agent"
"proxy-platform/internal/config"
"proxy-platform/internal/socks5"
"proxy-platform/internal/unlock"
"proxy-platform/internal/warp"
"go.uber.org/zap"
)
func main() {
// 加载配置
cfg, err := config.LoadAgent("configs/agent.yaml")
if err != nil {
log.Fatalf("加载配置失败: %v", err)
}
// 初始化日志
logger, err := zap.NewProduction()
if err != nil {
log.Fatalf("初始化日志失败: %v", err)
}
defer logger.Sync()
logger.Info("节点 Agent 启动",
zap.String("node_id", cfg.Agent.NodeID),
zap.String("name", cfg.Agent.Name),
)
// 初始化 WARP 客户端
warpClient := warp.NewClient(
cfg.WARP.SOCKS5Port,
cfg.WARP.RefreshCooldown,
cfg.WARP.MaxRefreshRetries,
cfg.WARP.RefreshRetryDelayMin,
cfg.WARP.RefreshRetryDelayMax,
logger,
)
// 连接 WARP
if cfg.WARP.Enabled {
logger.Info("连接 WARP...")
if err := warpClient.Connect(context.Background()); err != nil {
logger.Fatal("连接 WARP 失败", zap.Error(err))
}
logger.Info("WARP 连接成功")
// 获取当前 IP
ip, err := warpClient.GetCurrentIP(context.Background())
if err != nil {
logger.Error("获取 IP 失败", zap.Error(err))
} else {
logger.Info("当前 IP", zap.String("ip", ip))
}
}
// 初始化解锁检测器
detector := unlock.NewDetector(
"127.0.0.1",
cfg.WARP.SOCKS5Port,
30,
logger,
)
// 初始化 Agent 客户端
agentClient := agent.NewClient(
cfg.Scheduler.Host,
cfg.Scheduler.APIKey,
cfg.Agent.NodeID,
cfg.Agent.Name,
cfg.Agent.Region,
time.Duration(cfg.Scheduler.HeartbeatInterval)*time.Second,
time.Duration(cfg.Scheduler.ReportInterval)*time.Second,
logger,
)
// 初始化 SOCKS5 服务器(供调度中心连接)
socks5Server := socks5.NewServer(
cfg.SOCKS5.Host,
cfg.SOCKS5.Port,
cfg.SOCKS5.MaxConnections,
30,
nil, // 无需认证
&LocalBackendSelector{warpClient: warpClient},
logger,
)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// 启动 SOCKS5 服务器
go func() {
if err := socks5Server.Start(ctx); err != nil {
logger.Error("SOCKS5 服务器错误", zap.Error(err))
}
}()
// 启动心跳上报
go agentClient.StartHeartbeat(ctx)
// 启动解锁检测
go startUnlockCheck(ctx, detector, agentClient, logger, time.Duration(cfg.Unlock.CheckInterval)*time.Second)
// 等待中断信号
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
logger.Info("正在关闭 Agent...")
cancel()
// 断开 WARP
if cfg.WARP.Enabled {
warpClient.Disconnect(context.Background())
}
logger.Info("Agent 已关闭")
}
// LocalBackendSelector 本地后端选择器
type LocalBackendSelector struct {
warpClient *warp.Client
}
func (s *LocalBackendSelector) SelectBackend(ctx context.Context, targetHost string, targetPort int, services []string) (string, int, error) {
// 本地处理,通过 WARP 出口
// 返回 WARP 的 SOCKS5 端口
return "127.0.0.1", 40000, nil // WARP SOCKS5 端口
}
func (s *LocalBackendSelector) ReleaseBackend(host string, port int, bytesIn, bytesOut int64) {
// 本地处理,无需释放
}
// startUnlockCheck 启动解锁检测
func startUnlockCheck(ctx context.Context, detector *unlock.Detector, client *agent.Client, logger *zap.Logger, interval time.Duration) {
ticker := time.NewTicker(interval)
defer ticker.Stop()
// 立即执行一次
doUnlockCheck(ctx, detector, client, logger)
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
doUnlockCheck(ctx, detector, client, logger)
}
}
}
func doUnlockCheck(ctx context.Context, detector *unlock.Detector, client *agent.Client, logger *zap.Logger) {
logger.Info("开始解锁检测...")
services := make([]unlock.ServiceConfig, len(unlock.DefaultServices))
copy(services, unlock.DefaultServices)
results := detector.CheckAll(ctx, services)
unlocks := make(map[string]struct {
Unlocked bool `json:"unlocked"`
Region string `json:"region"`
})
for service, result := range results {
unlocks[service] = struct {
Unlocked bool `json:"unlocked"`
Region string `json:"region"`
}{
Unlocked: result.Unlocked,
Region: result.Region,
}
logger.Info("解锁检测结果",
zap.String("service", service),
zap.Bool("unlocked", result.Unlocked),
zap.String("region", result.Region),
)
}
// 上报解锁状态
client.ReportUnlock(ctx, unlocks)
}
+345
View File
@@ -0,0 +1,345 @@
package main
import (
"context"
"fmt"
"log"
"net/http"
"os"
"os/signal"
"sync"
"syscall"
"time"
"proxy-platform/internal/config"
"proxy-platform/internal/handler"
"proxy-platform/internal/models"
"proxy-platform/internal/repository"
"proxy-platform/internal/socks5"
"proxy-platform/internal/scheduler"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
func main() {
// 加载配置
cfg, err := config.Load("configs/scheduler.yaml")
if err != nil {
log.Fatalf("加载配置失败: %v", err)
}
// 初始化日志
logger, err := zap.NewProduction()
if err != nil {
log.Fatalf("初始化日志失败: %v", err)
}
defer logger.Sync()
// 连接数据库
db, err := gorm.Open(postgres.Open(cfg.Database.DSN()), &gorm.Config{})
if err != nil {
logger.Fatal("连接数据库失败", zap.Error(err))
}
// 自动迁移
if err := db.AutoMigrate(
&models.User{},
&models.Node{},
&models.NodeGroup{},
&models.UnlockStatus{},
&models.IPChangeLog{},
&models.ConnectionLog{},
&models.IPRefreshRule{},
); err != nil {
logger.Fatal("数据库迁移失败", zap.Error(err))
}
logger.Info("数据库迁移完成")
// 初始化仓库
repos := repository.NewRepositories(db)
// 初始化认证器
auth := NewSimpleAuthenticator(repos.User)
// 初始化节点缓存
cache := NewMemoryNodeCache()
// 初始化节点选择器
selector := scheduler.NewSelector(repos.Node, cache, scheduler.StrategyLeastLatency, logger)
// 初始化后端选择器
backendSelector := NewBackendSelector(repos.Node, selector, logger)
// 启动 SOCKS5 服务器
socks5Server := socks5.NewServer(
cfg.SOCKS5.Host,
cfg.SOCKS5.Port,
cfg.SOCKS5.MaxConnections,
cfg.SOCKS5.Timeout,
auth,
backendSelector,
logger,
)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// 启动 SOCKS5 服务器
go func() {
if err := socks5Server.Start(ctx); err != nil {
logger.Error("SOCKS5 服务器错误", zap.Error(err))
}
}()
// 启动健康检查
healthChecker := scheduler.NewHealthChecker(repos.Node, cache, logger)
go startHealthCheck(ctx, repos.Node, healthChecker, logger, time.Duration(cfg.Scheduler.HealthCheckInterval)*time.Second)
// 启动 API 服务器
apiServer := NewAPIServer(cfg, repos, logger)
go func() {
if err := apiServer.Start(); err != nil && err != http.ErrServerClosed {
logger.Error("API 服务器错误", zap.Error(err))
}
}()
logger.Info("调度中心启动完成",
zap.String("api", fmt.Sprintf("%s:%d", cfg.Server.Host, cfg.Server.Port)),
zap.String("socks5", fmt.Sprintf("%s:%d", cfg.SOCKS5.Host, cfg.SOCKS5.Port)),
)
// 等待中断信号
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
logger.Info("正在关闭服务器...")
cancel()
// 关闭 API 服务器
ctx2, cancel2 := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel2()
apiServer.Shutdown(ctx2)
logger.Info("服务器已关闭")
}
// SimpleAuthenticator 简单认证器
type SimpleAuthenticator struct {
userRepo *repository.UserRepository
}
func NewSimpleAuthenticator(userRepo *repository.UserRepository) *SimpleAuthenticator {
return &SimpleAuthenticator{userRepo: userRepo}
}
func (a *SimpleAuthenticator) Authenticate(username, password string) (uint, bool) {
user, err := a.userRepo.FindByUsername(username)
if err != nil {
return 0, false
}
// TODO: 实现密码验证
// 这里简化处理,实际应该使用 bcrypt 验证
if user.PasswordHash == password && user.Status == "active" {
return user.ID, true
}
return 0, false
}
// BackendSelector 后端节点选择器
type BackendSelector struct {
nodeRepo *repository.NodeRepository
selector *scheduler.Selector
logger *zap.Logger
}
func NewBackendSelector(nodeRepo *repository.NodeRepository, selector *scheduler.Selector, logger *zap.Logger) *BackendSelector {
return &BackendSelector{
nodeRepo: nodeRepo,
selector: selector,
logger: logger,
}
}
func (s *BackendSelector) SelectBackend(ctx context.Context, targetHost string, targetPort int, services []string) (string, int, error) {
node, err := s.selector.Select(ctx, targetHost, targetPort, services)
if err != nil {
return "", 0, err
}
return node.Host, node.Port, nil
}
func (s *BackendSelector) ReleaseBackend(host string, port int, bytesIn, bytesOut int64) {
// TODO: 更新节点统计信息
s.logger.Info("释放后端节点",
zap.String("host", host),
zap.Int("port", port),
zap.Int64("bytes_in", bytesIn),
zap.Int64("bytes_out", bytesOut),
)
}
// MemoryNodeCache 内存节点缓存
type MemoryNodeCache struct {
stats map[string]*models.NodeStats
mu sync.RWMutex
}
func NewMemoryNodeCache() *MemoryNodeCache {
return &MemoryNodeCache{
stats: make(map[string]*models.NodeStats),
}
}
func (c *MemoryNodeCache) GetStats(nodeID string) (*models.NodeStats, bool) {
c.mu.RLock()
defer c.mu.RUnlock()
stats, ok := c.stats[nodeID]
return stats, ok
}
func (c *MemoryNodeCache) SetStats(nodeID string, stats *models.NodeStats) {
c.mu.Lock()
defer c.mu.Unlock()
c.stats[nodeID] = stats
}
func (c *MemoryNodeCache) GetAllStats() map[string]*models.NodeStats {
c.mu.RLock()
defer c.mu.RUnlock()
result := make(map[string]*models.NodeStats)
for k, v := range c.stats {
result[k] = v
}
return result
}
// startHealthCheck 启动健康检查
func startHealthCheck(ctx context.Context, nodeRepo *repository.NodeRepository, checker *scheduler.HealthChecker, logger *zap.Logger, interval time.Duration) {
ticker := time.NewTicker(interval)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
nodes, err := nodeRepo.ListOnline()
if err != nil {
logger.Error("获取节点列表失败", zap.Error(err))
continue
}
for _, node := range nodes {
if err := checker.Check(ctx, &node); err != nil {
logger.Warn("节点健康检查失败",
zap.String("node_id", node.NodeID),
zap.Error(err),
)
}
}
}
}
}
// APIServer API 服务器
type APIServer struct {
cfg *config.Config
repos *repository.Repositories
logger *zap.Logger
server *http.Server
router *gin.Engine
}
func NewAPIServer(cfg *config.Config, repos *repository.Repositories, logger *zap.Logger) *APIServer {
gin.SetMode(cfg.Server.Mode)
router := gin.New()
server := &APIServer{
cfg: cfg,
repos: repos,
logger: logger,
router: router,
server: &http.Server{
Addr: fmt.Sprintf("%s:%d", cfg.Server.Host, cfg.Server.Port),
Handler: router,
},
}
// 注册路由
server.setupRoutes()
return server
}
func (s *APIServer) setupRoutes() {
// 健康检查
s.router.GET("/health", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"status": "ok"})
})
// API 路由组
api := s.router.Group("/api/v1")
{
// 用户相关
users := api.Group("/users")
{
users.GET("", handler.ListUsers(s.repos.User))
users.POST("", handler.CreateUser(s.repos.User))
users.GET("/:id", handler.GetUser(s.repos.User))
users.PUT("/:id", handler.UpdateUser(s.repos.User))
users.DELETE("/:id", handler.DeleteUser(s.repos.User))
}
// 节点相关
nodes := api.Group("/nodes")
{
nodes.GET("", handler.ListNodes(s.repos.Node))
nodes.POST("", handler.CreateNode(s.repos.Node))
nodes.GET("/:id", handler.GetNode(s.repos.Node))
nodes.PUT("/:id", handler.UpdateNode(s.repos.Node))
nodes.DELETE("/:id", handler.DeleteNode(s.repos.Node))
nodes.POST("/:id/refresh-ip", handler.RefreshNodeIP(s.repos.Node, s.logger))
}
// Agent 相关
agent := api.Group("/agent")
{
agent.POST("/heartbeat", handler.AgentHeartbeat(s.repos.Node, s.logger))
agent.POST("/unlock/report", handler.ReportUnlockStatus(s.repos.UnlockStatus, s.logger))
agent.POST("/ip/change/result", handler.ReportIPChange(s.repos.Node, s.repos.IPChangeLog, s.logger))
}
// 规则相关
rules := api.Group("/rules")
{
rules.GET("", handler.ListRules(s.repos.IPRefreshRule))
rules.POST("", handler.CreateRule(s.repos.IPRefreshRule))
rules.GET("/:id", handler.GetRule(s.repos.IPRefreshRule))
rules.PUT("/:id", handler.UpdateRule(s.repos.IPRefreshRule))
rules.DELETE("/:id", handler.DeleteRule(s.repos.IPRefreshRule))
}
// 统计相关
stats := api.Group("/stats")
{
stats.GET("/overview", handler.GetOverview(s.repos))
stats.GET("/traffic", handler.GetTrafficStats(s.repos.ConnectionLog))
}
}
}
func (s *APIServer) Start() error {
return s.server.ListenAndServe()
}
func (s *APIServer) Shutdown(ctx context.Context) {
s.server.Shutdown(ctx)
}