Files
proxy-platform/internal/handler/handler.go
T

625 lines
16 KiB
Go

package handler
import (
"net/http"
"strconv"
"time"
"proxy-platform/internal/models"
"proxy-platform/internal/repository"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
"golang.org/x/crypto/bcrypt"
)
// ListUsers 获取用户列表
func ListUsers(repo *repository.UserRepository) gin.HandlerFunc {
return func(c *gin.Context) {
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
offset := (page - 1) * pageSize
users, total, err := repo.List(offset, pageSize)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{
"data": users,
"total": total,
"page": page,
"page_size": pageSize,
})
}
}
// CreateUser 创建用户
func CreateUser(repo *repository.UserRepository) gin.HandlerFunc {
return func(c *gin.Context) {
var req struct {
Username string `json:"username" binding:"required"`
Password string `json:"password" binding:"required"`
TrafficQuota int64 `json:"traffic_quota"`
ExpireDays int `json:"expire_days"`
NodeGroupID *uint `json:"node_group_id"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// 密码加密
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "密码加密失败"})
return
}
user := &models.User{
Username: req.Username,
PasswordHash: string(hashedPassword),
TrafficQuota: req.TrafficQuota,
NodeGroupID: req.NodeGroupID,
Status: "active",
}
if req.ExpireDays > 0 {
expireAt := time.Now().AddDate(0, 0, req.ExpireDays)
user.ExpireAt = &expireAt
}
if err := repo.Create(user); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusCreated, user)
}
}
// GetUser 获取用户
func GetUser(repo *repository.UserRepository) gin.HandlerFunc {
return func(c *gin.Context) {
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的 ID"})
return
}
user, err := repo.FindByID(uint(id))
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "用户不存在"})
return
}
c.JSON(http.StatusOK, user)
}
}
// UpdateUser 更新用户
func UpdateUser(repo *repository.UserRepository) gin.HandlerFunc {
return func(c *gin.Context) {
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的 ID"})
return
}
user, err := repo.FindByID(uint(id))
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "用户不存在"})
return
}
var req struct {
Password *string `json:"password"`
TrafficQuota *int64 `json:"traffic_quota"`
ExpireDays *int `json:"expire_days"`
NodeGroupID *uint `json:"node_group_id"`
Status *string `json:"status"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if req.Password != nil {
hashedPassword, _ := bcrypt.GenerateFromPassword([]byte(*req.Password), bcrypt.DefaultCost)
user.PasswordHash = string(hashedPassword)
}
if req.TrafficQuota != nil {
user.TrafficQuota = *req.TrafficQuota
}
if req.ExpireDays != nil {
expireAt := time.Now().AddDate(0, 0, *req.ExpireDays)
user.ExpireAt = &expireAt
}
if req.NodeGroupID != nil {
user.NodeGroupID = req.NodeGroupID
}
if req.Status != nil {
user.Status = *req.Status
}
if err := repo.Update(user); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, user)
}
}
// DeleteUser 删除用户
func DeleteUser(repo *repository.UserRepository) gin.HandlerFunc {
return func(c *gin.Context) {
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的 ID"})
return
}
if err := repo.Delete(uint(id)); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"message": "删除成功"})
}
}
// ListNodes 获取节点列表
func ListNodes(repo *repository.NodeRepository) gin.HandlerFunc {
return func(c *gin.Context) {
nodes, err := repo.List()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"data": nodes})
}
}
// CreateNode 创建节点
func CreateNode(repo *repository.NodeRepository) gin.HandlerFunc {
return func(c *gin.Context) {
var req struct {
NodeID string `json:"node_id" binding:"required"`
Name string `json:"name" binding:"required"`
Host string `json:"host" binding:"required"`
Port int `json:"port"`
Region string `json:"region"`
Country string `json:"country"`
Weight int `json:"weight"`
MaxConnections int `json:"max_connections"`
NodeGroupID *uint `json:"node_group_id"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if req.Port == 0 {
req.Port = 1080
}
if req.Weight == 0 {
req.Weight = 100
}
if req.MaxConnections == 0 {
req.MaxConnections = 1000
}
node := &models.Node{
NodeID: req.NodeID,
Name: req.Name,
Host: req.Host,
Port: req.Port,
Region: req.Region,
Country: req.Country,
Weight: req.Weight,
MaxConnections: req.MaxConnections,
NodeGroupID: req.NodeGroupID,
Status: "offline",
WARPStatus: "disconnected",
}
if err := repo.Create(node); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusCreated, node)
}
}
// GetNode 获取节点
func GetNode(repo *repository.NodeRepository) gin.HandlerFunc {
return func(c *gin.Context) {
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的 ID"})
return
}
node, err := repo.FindByID(uint(id))
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "节点不存在"})
return
}
c.JSON(http.StatusOK, node)
}
}
// UpdateNode 更新节点
func UpdateNode(repo *repository.NodeRepository) gin.HandlerFunc {
return func(c *gin.Context) {
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的 ID"})
return
}
node, err := repo.FindByID(uint(id))
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "节点不存在"})
return
}
var req struct {
Name *string `json:"name"`
Host *string `json:"host"`
Port *int `json:"port"`
Weight *int `json:"weight"`
MaxConnections *int `json:"max_connections"`
Status *string `json:"status"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if req.Name != nil {
node.Name = *req.Name
}
if req.Host != nil {
node.Host = *req.Host
}
if req.Port != nil {
node.Port = *req.Port
}
if req.Weight != nil {
node.Weight = *req.Weight
}
if req.MaxConnections != nil {
node.MaxConnections = *req.MaxConnections
}
if req.Status != nil {
node.Status = *req.Status
}
if err := repo.Update(node); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, node)
}
}
// DeleteNode 删除节点
func DeleteNode(repo *repository.NodeRepository) gin.HandlerFunc {
return func(c *gin.Context) {
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的 ID"})
return
}
if err := repo.Delete(uint(id)); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"message": "删除成功"})
}
}
// RefreshNodeIP 刷新节点 IP
func RefreshNodeIP(repo *repository.NodeRepository, logger *zap.Logger) gin.HandlerFunc {
return func(c *gin.Context) {
id := c.Param("id")
logger.Info("收到刷新 IP 请求", zap.String("node_id", id))
// TODO: 发送刷新指令到 Agent
c.JSON(http.StatusOK, gin.H{
"message": "刷新指令已发送",
"node_id": id,
})
}
}
// AgentHeartbeat Agent 心跳
func AgentHeartbeat(repo *repository.NodeRepository, logger *zap.Logger) gin.HandlerFunc {
return func(c *gin.Context) {
var req struct {
NodeID string `json:"node_id"`
Online bool `json:"online"`
WARPConnected bool `json:"warp_connected"`
CurrentIP string `json:"current_ip"`
IPRegion string `json:"ip_region"`
Connections int `json:"connections"`
TrafficUsedGB float64 `json:"traffic_used_gb"`
Unlocks map[string]bool `json:"unlocks"`
CPUUsage float64 `json:"cpu_usage"`
MemoryUsage float64 `json:"memory_usage"`
NetworkInMbps float64 `json:"network_in_mbps"`
NetworkOutMbps float64 `json:"network_out_mbps"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
logger.Info("收到心跳",
zap.String("node_id", req.NodeID),
zap.Bool("online", req.Online),
zap.String("ip", req.CurrentIP),
)
// 更新节点状态
warpStatus := "disconnected"
if req.WARPConnected {
warpStatus = "connected"
}
status := "offline"
if req.Online {
status = "online"
}
if err := repo.UpdateStatus(req.NodeID, status, warpStatus); err != nil {
logger.Error("更新节点状态失败", zap.Error(err))
}
if req.CurrentIP != "" {
if err := repo.UpdateIP(req.NodeID, req.CurrentIP, req.IPRegion); err != nil {
logger.Error("更新节点 IP 失败", zap.Error(err))
}
}
if err := repo.UpdateConnections(req.NodeID, req.Connections); err != nil {
logger.Error("更新节点连接数失败", zap.Error(err))
}
// 返回指令(如果有)
commands := []interface{}{}
c.JSON(http.StatusOK, gin.H{
"message": "心跳已接收",
"commands": commands,
})
}
}
// ReportUnlockStatus 上报解锁状态
func ReportUnlockStatus(repo *repository.UnlockStatusRepository, logger *zap.Logger) gin.HandlerFunc {
return func(c *gin.Context) {
var req struct {
NodeID string `json:"node_id"`
Unlocks map[string]struct {
Unlocked bool `json:"unlocked"`
Region string `json:"region"`
} `json:"unlocks"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
logger.Info("收到解锁状态上报",
zap.String("node_id", req.NodeID),
zap.Any("unlocks", req.Unlocks),
)
// 查找节点
node, err := (&repository.NodeRepository{}).FindByNodeID(req.NodeID)
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "节点不存在"})
return
}
// 更新解锁状态
for service, status := range req.Unlocks {
if err := repo.Upsert(node.ID, service, status.Unlocked, status.Region); err != nil {
logger.Error("更新解锁状态失败",
zap.String("service", service),
zap.Error(err),
)
}
}
c.JSON(http.StatusOK, gin.H{"message": "解锁状态已更新"})
}
}
// ReportIPChange 上报 IP 变更
func ReportIPChange(nodeRepo *repository.NodeRepository, logRepo *repository.IPChangeLogRepository, logger *zap.Logger) gin.HandlerFunc {
return func(c *gin.Context) {
var req struct {
NodeID string `json:"node_id"`
OldIP string `json:"old_ip"`
NewIP string `json:"new_ip"`
Success bool `json:"success"`
Reason string `json:"reason"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
logger.Info("收到 IP 变更上报",
zap.String("node_id", req.NodeID),
zap.String("old_ip", req.OldIP),
zap.String("new_ip", req.NewIP),
zap.Bool("success", req.Success),
)
// 查找节点
node, err := nodeRepo.FindByNodeID(req.NodeID)
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "节点不存在"})
return
}
// 记录日志
log := &models.IPChangeLog{
NodeID: node.ID,
OldIP: req.OldIP,
NewIP: req.NewIP,
Reason: req.Reason,
Success: req.Success,
}
logRepo.Create(log)
// 更新节点 IP
if req.Success && req.NewIP != "" {
nodeRepo.UpdateIP(req.NodeID, req.NewIP, "")
}
c.JSON(http.StatusOK, gin.H{"message": "IP 变更已记录"})
}
}
// ListRules 获取规则列表
func ListRules(repo *repository.IPRefreshRuleRepository) gin.HandlerFunc {
return func(c *gin.Context) {
rules, err := repo.List()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"data": rules})
}
}
// CreateRule 创建规则
func CreateRule(repo *repository.IPRefreshRuleRepository) gin.HandlerFunc {
return func(c *gin.Context) {
var req models.IPRefreshRule
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if err := repo.Create(&req); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusCreated, req)
}
}
// GetRule 获取规则
func GetRule(repo *repository.IPRefreshRuleRepository) gin.HandlerFunc {
return func(c *gin.Context) {
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的 ID"})
return
}
rule, err := repo.FindByID(uint(id))
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "规则不存在"})
return
}
c.JSON(http.StatusOK, rule)
}
}
// UpdateRule 更新规则
func UpdateRule(repo *repository.IPRefreshRuleRepository) gin.HandlerFunc {
return func(c *gin.Context) {
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的 ID"})
return
}
rule, err := repo.FindByID(uint(id))
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "规则不存在"})
return
}
if err := c.ShouldBindJSON(rule); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if err := repo.Update(rule); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, rule)
}
}
// DeleteRule 删除规则
func DeleteRule(repo *repository.IPRefreshRuleRepository) gin.HandlerFunc {
return func(c *gin.Context) {
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的 ID"})
return
}
if err := repo.Delete(uint(id)); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"message": "删除成功"})
}
}
// GetOverview 获取概览统计
func GetOverview(repos *repository.Repositories) gin.HandlerFunc {
return func(c *gin.Context) {
// TODO: 实现统计逻辑
c.JSON(http.StatusOK, gin.H{
"total_nodes": 0,
"online_nodes": 0,
"total_users": 0,
"active_users": 0,
"today_traffic": 0,
"total_traffic": 0,
})
}
}
// GetTrafficStats 获取流量统计
func GetTrafficStats(repo *repository.ConnectionLogRepository) gin.HandlerFunc {
return func(c *gin.Context) {
// TODO: 实现统计逻辑
c.JSON(http.StatusOK, gin.H{
"data": []interface{}{},
})
}
}