625 lines
16 KiB
Go
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{}{},
|
|
})
|
|
}
|
|
} |