package handler import ( "fmt" "github.com/gin-gonic/gin" "github.com/gotunnel/internal/server/db" // removed router import "github.com/gotunnel/internal/server/router/dto" ) // ClientHandler 客户端处理器 type ClientHandler struct { app AppInterface } // NewClientHandler 创建客户端处理器 func NewClientHandler(app AppInterface) *ClientHandler { return &ClientHandler{app: app} } // List 获取客户端列表 // @Summary 获取所有客户端 // @Description 返回所有注册客户端的列表及其在线状态 // @Tags 客户端 // @Produce json // @Security Bearer // @Success 200 {object} Response{data=[]dto.ClientListItem} // @Router /api/clients [get] func (h *ClientHandler) List(c *gin.Context) { clients, err := h.app.GetClientStore().GetAllClients() if err != nil { InternalError(c, err.Error()) return } statusMap := h.app.GetServer().GetAllClientStatus() result := make([]dto.ClientListItem, 0, len(clients)) for _, client := range clients { item := dto.ClientListItem{ ID: client.ID, Nickname: client.Nickname, RuleCount: len(client.Rules), } if status, ok := statusMap[client.ID]; ok { item.Online = status.Online item.LastPing = status.LastPing item.RemoteAddr = status.RemoteAddr } result = append(result, item) } Success(c, result) } // Create 创建客户端 // @Summary 创建新客户端 // @Description 创建一个新的客户端配置 // @Tags 客户端 // @Accept json // @Produce json // @Security Bearer // @Param request body dto.CreateClientRequest true "客户端信息" // @Success 200 {object} Response // @Failure 400 {object} Response // @Failure 409 {object} Response // @Router /api/clients [post] func (h *ClientHandler) Create(c *gin.Context) { var req dto.CreateClientRequest if !BindJSON(c, &req) { return } // 验证客户端 ID 格式 if !validateClientID(req.ID) { BadRequest(c, "invalid client id: must be 1-64 alphanumeric characters, underscore or hyphen") return } // 检查客户端是否已存在 exists, _ := h.app.GetClientStore().ClientExists(req.ID) if exists { Conflict(c, "client already exists") return } client := &db.Client{ID: req.ID, Rules: req.Rules} if err := h.app.GetClientStore().CreateClient(client); err != nil { InternalError(c, err.Error()) return } Success(c, gin.H{"status": "ok"}) } // Get 获取单个客户端 // @Summary 获取客户端详情 // @Description 获取指定客户端的详细信息 // @Tags 客户端 // @Produce json // @Security Bearer // @Param id path string true "客户端ID" // @Success 200 {object} Response{data=dto.ClientResponse} // @Failure 404 {object} Response // @Router /api/client/{id} [get] func (h *ClientHandler) Get(c *gin.Context) { clientID := c.Param("id") client, err := h.app.GetClientStore().GetClient(clientID) if err != nil { NotFound(c, "client not found") return } online, lastPing, remoteAddr := h.app.GetServer().GetClientStatus(clientID) resp := dto.ClientResponse{ ID: client.ID, Nickname: client.Nickname, Rules: client.Rules, Plugins: client.Plugins, Online: online, LastPing: lastPing, RemoteAddr: remoteAddr, } Success(c, resp) } // Update 更新客户端 // @Summary 更新客户端配置 // @Description 更新指定客户端的配置信息 // @Tags 客户端 // @Accept json // @Produce json // @Security Bearer // @Param id path string true "客户端ID" // @Param request body dto.UpdateClientRequest true "更新内容" // @Success 200 {object} Response // @Failure 400 {object} Response // @Failure 404 {object} Response // @Router /api/client/{id} [put] func (h *ClientHandler) Update(c *gin.Context) { clientID := c.Param("id") var req dto.UpdateClientRequest if !BindJSON(c, &req) { return } client, err := h.app.GetClientStore().GetClient(clientID) if err != nil { NotFound(c, "client not found") return } client.Nickname = req.Nickname client.Rules = req.Rules if req.Plugins != nil { client.Plugins = req.Plugins } if err := h.app.GetClientStore().UpdateClient(client); err != nil { InternalError(c, err.Error()) return } Success(c, gin.H{"status": "ok"}) } // Delete 删除客户端 // @Summary 删除客户端 // @Description 删除指定的客户端配置 // @Tags 客户端 // @Produce json // @Security Bearer // @Param id path string true "客户端ID" // @Success 200 {object} Response // @Failure 404 {object} Response // @Router /api/client/{id} [delete] func (h *ClientHandler) Delete(c *gin.Context) { clientID := c.Param("id") exists, _ := h.app.GetClientStore().ClientExists(clientID) if !exists { NotFound(c, "client not found") return } if err := h.app.GetClientStore().DeleteClient(clientID); err != nil { InternalError(c, err.Error()) return } Success(c, gin.H{"status": "ok"}) } // PushConfig 推送配置到客户端 // @Summary 推送配置 // @Description 将配置推送到在线客户端 // @Tags 客户端 // @Produce json // @Security Bearer // @Param id path string true "客户端ID" // @Success 200 {object} Response // @Failure 400 {object} Response // @Router /api/client/{id}/push [post] func (h *ClientHandler) PushConfig(c *gin.Context) { clientID := c.Param("id") online, _, _ := h.app.GetServer().GetClientStatus(clientID) if !online { ClientNotOnline(c) return } if err := h.app.GetServer().PushConfigToClient(clientID); err != nil { InternalError(c, err.Error()) return } Success(c, gin.H{"status": "ok"}) } // Disconnect 断开客户端连接 // @Summary 断开连接 // @Description 强制断开客户端连接 // @Tags 客户端 // @Produce json // @Security Bearer // @Param id path string true "客户端ID" // @Success 200 {object} Response // @Router /api/client/{id}/disconnect [post] func (h *ClientHandler) Disconnect(c *gin.Context) { clientID := c.Param("id") if err := h.app.GetServer().DisconnectClient(clientID); err != nil { InternalError(c, err.Error()) return } Success(c, gin.H{"status": "ok"}) } // Restart 重启客户端 // @Summary 重启客户端 // @Description 发送重启命令到客户端 // @Tags 客户端 // @Produce json // @Security Bearer // @Param id path string true "客户端ID" // @Success 200 {object} Response // @Router /api/client/{id}/restart [post] func (h *ClientHandler) Restart(c *gin.Context) { clientID := c.Param("id") if err := h.app.GetServer().RestartClient(clientID); err != nil { InternalError(c, err.Error()) return } SuccessWithMessage(c, gin.H{"status": "ok"}, "client restart initiated") } // InstallPlugins 安装插件到客户端 // @Summary 安装插件 // @Description 将指定插件安装到客户端 // @Tags 客户端 // @Accept json // @Produce json // @Security Bearer // @Param id path string true "客户端ID" // @Param request body dto.InstallPluginsRequest true "插件列表" // @Success 200 {object} Response // @Failure 400 {object} Response // @Router /api/client/{id}/install-plugins [post] func (h *ClientHandler) InstallPlugins(c *gin.Context) { clientID := c.Param("id") online, _, _ := h.app.GetServer().GetClientStatus(clientID) if !online { ClientNotOnline(c) return } var req dto.InstallPluginsRequest if !BindJSON(c, &req) { return } if err := h.app.GetServer().InstallPluginsToClient(clientID, req.Plugins); err != nil { InternalError(c, err.Error()) return } Success(c, gin.H{"status": "ok"}) } // PluginAction 客户端插件操作 // @Summary 插件操作 // @Description 对客户端插件执行操作(stop/restart/config/delete) // @Tags 客户端 // @Accept json // @Produce json // @Security Bearer // @Param id path string true "客户端ID" // @Param pluginName path string true "插件名称" // @Param action path string true "操作类型" Enums(stop, restart, config, delete) // @Param request body dto.ClientPluginActionRequest false "操作参数" // @Success 200 {object} Response // @Failure 400 {object} Response // @Router /api/client/{id}/plugin/{pluginName}/{action} [post] func (h *ClientHandler) PluginAction(c *gin.Context) { clientID := c.Param("id") pluginName := c.Param("pluginName") action := c.Param("action") var req dto.ClientPluginActionRequest c.ShouldBindJSON(&req) // 忽略错误,使用默认值 if req.RuleName == "" { req.RuleName = pluginName } var err error switch action { case "stop": err = h.app.GetServer().StopClientPlugin(clientID, pluginName, req.RuleName) case "restart": err = h.app.GetServer().RestartClientPlugin(clientID, pluginName, req.RuleName) case "config": if req.Config == nil { BadRequest(c, "config required") return } err = h.app.GetServer().UpdateClientPluginConfig(clientID, pluginName, req.RuleName, req.Config, req.Restart) case "delete": err = h.deleteClientPlugin(clientID, pluginName) default: BadRequest(c, "unknown action: "+action) return } if err != nil { InternalError(c, err.Error()) return } Success(c, gin.H{ "status": "ok", "action": action, "plugin": pluginName, }) } func (h *ClientHandler) deleteClientPlugin(clientID, pluginName string) error { client, err := h.app.GetClientStore().GetClient(clientID) if err != nil { return fmt.Errorf("client not found") } var newPlugins []db.ClientPlugin found := false for _, p := range client.Plugins { if p.Name == pluginName { found = true continue } newPlugins = append(newPlugins, p) } if !found { return fmt.Errorf("plugin %s not found", pluginName) } client.Plugins = newPlugins return h.app.GetClientStore().UpdateClient(client) } // validateClientID 验证客户端 ID 格式 func validateClientID(id string) bool { if len(id) < 1 || len(id) > 64 { return false } for _, c := range id { if !((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_' || c == '-') { return false } } return true }