package main import ( "bytes" "fmt" "net/http" "strconv" "github.com/gin-gonic/gin" "golang.org/x/crypto/bcrypt" ) // SetupRouter 配置所有路由 func SetupRouter() *gin.Engine { r := gin.Default() // 静态文件(前端) r.Static("/static", "./static") // 健康检查 r.GET("/ping", func(c *gin.Context) { c.String(200, "frpc-console 后端已启动 🎉") }) // 前端入口 r.GET("/", func(c *gin.Context) { c.File("./static/index.html") }) // API 路由组 api := r.Group("/api") { // 登录(无需鉴权) api.POST("/login", loginHandler) // 需要鉴权的路由 auth := api.Group("/") auth.Use(AuthMiddleware()) { // 全局配置 auth.GET("/config", getConfigHandler) auth.PUT("/config", updateConfigHandler) // 隧道管理 auth.GET("/proxies", getProxiesHandler) auth.GET("/proxy/:id", getProxyHandler) auth.POST("/proxy", createProxyHandler) auth.PUT("/proxy/:id", updateProxyHandler) auth.DELETE("/proxy/:id", deleteProxyHandler) // frpc 控制 auth.POST("/frpc/reload", reloadFrpcHandler) auth.POST("/frpc/start", startFrpcHandler) auth.POST("/frpc/stop", stopFrpcHandler) auth.GET("/frpc/status", getFrpcStatusHandler) // 🟢 导入 TOML(新增) auth.POST("/import/toml", importTomlHandler) } } return r } // ========== 登录 ========== func loginHandler(c *gin.Context) { var req struct { Username string `json:"username"` Password string `json:"password"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"code": 1, "msg": "请求参数错误"}) return } user, err := GetUserByUsername(req.Username) if err != nil { c.JSON(http.StatusUnauthorized, gin.H{"code": 1, "msg": "用户名或密码错误"}) return } if err := bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(req.Password)); err != nil { c.JSON(http.StatusUnauthorized, gin.H{"code": 1, "msg": "用户名或密码错误"}) return } token, err := GenerateJWT(user.Username) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"code": 2, "msg": "生成Token失败"}) return } c.JSON(http.StatusOK, gin.H{ "code": 0, "msg": "登录成功", "data": gin.H{"token": token}, }) } // ========== 全局配置 ========== func getConfigHandler(c *gin.Context) { cfg, err := GetGlobalConfig() if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"code": 2, "msg": "读取配置失败"}) return } c.JSON(http.StatusOK, gin.H{"code": 0, "data": cfg}) } func updateConfigHandler(c *gin.Context) { var cfg GlobalConfig if err := c.ShouldBindJSON(&cfg); err != nil { c.JSON(http.StatusBadRequest, gin.H{"code": 1, "msg": "请求参数错误"}) return } // 强制 tcpMux 为 true cfg.TcpMux = true if err := UpdateGlobalConfig(&cfg); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"code": 2, "msg": "更新配置失败: " + err.Error()}) return } if err := GenerateFrpcConfig(); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"code": 2, "msg": "生成配置文件失败: " + err.Error()}) return } if err := ReloadFrpc(); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"code": 2, "msg": "热加载失败: " + err.Error()}) return } c.JSON(http.StatusOK, gin.H{"code": 0, "msg": "配置更新成功"}) } // ========== 隧道管理 ========== func getProxiesHandler(c *gin.Context) { proxies, err := GetProxies() if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"code": 2, "msg": "读取隧道列表失败"}) return } c.JSON(http.StatusOK, gin.H{"code": 0, "data": proxies}) } func getProxyHandler(c *gin.Context) { id, err := strconv.Atoi(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"code": 1, "msg": "无效的ID"}) return } p, err := GetProxy(id) if err != nil { c.JSON(http.StatusNotFound, gin.H{"code": 1, "msg": "隧道不存在"}) return } c.JSON(http.StatusOK, gin.H{"code": 0, "data": p}) } func createProxyHandler(c *gin.Context) { var p Proxy if err := c.ShouldBindJSON(&p); err != nil { c.JSON(http.StatusBadRequest, gin.H{"code": 1, "msg": "请求参数错误"}) return } p.Enabled = true if err := CreateProxy(&p); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"code": 2, "msg": "创建隧道失败: " + err.Error()}) return } if err := generateAndReload(); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"code": 2, "msg": "配置生效失败: " + err.Error()}) return } c.JSON(http.StatusOK, gin.H{"code": 0, "msg": "隧道创建成功", "data": gin.H{"id": p.ID}}) } func updateProxyHandler(c *gin.Context) { id, err := strconv.Atoi(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"code": 1, "msg": "无效的ID"}) return } var p Proxy if err := c.ShouldBindJSON(&p); err != nil { c.JSON(http.StatusBadRequest, gin.H{"code": 1, "msg": "请求参数错误"}) return } p.ID = id if err := UpdateProxy(&p); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"code": 2, "msg": "更新隧道失败: " + err.Error()}) return } if err := generateAndReload(); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"code": 2, "msg": "配置生效失败: " + err.Error()}) return } c.JSON(http.StatusOK, gin.H{"code": 0, "msg": "隧道更新成功"}) } func deleteProxyHandler(c *gin.Context) { id, err := strconv.Atoi(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"code": 1, "msg": "无效的ID"}) return } if err := DeleteProxy(id); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"code": 2, "msg": "删除隧道失败: " + err.Error()}) return } if err := generateAndReload(); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"code": 2, "msg": "配置生效失败: " + err.Error()}) return } c.JSON(http.StatusOK, gin.H{"code": 0, "msg": "隧道删除成功"}) } // ========== frpc 控制 ========== func reloadFrpcHandler(c *gin.Context) { if err := GenerateFrpcConfig(); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"code": 2, "msg": "生成配置失败: " + err.Error()}) return } if err := ReloadFrpc(); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"code": 2, "msg": "热加载失败: " + err.Error()}) return } c.JSON(http.StatusOK, gin.H{"code": 0, "msg": "热加载成功"}) } func startFrpcHandler(c *gin.Context) { if err := StartFrpc(); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"code": 2, "msg": "启动失败: " + err.Error()}) return } c.JSON(http.StatusOK, gin.H{"code": 0, "msg": "frpc 启动成功"}) } func stopFrpcHandler(c *gin.Context) { if err := StopFrpc(); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"code": 2, "msg": "停止失败: " + err.Error()}) return } c.JSON(http.StatusOK, gin.H{"code": 0, "msg": "frpc 已停止"}) } func getFrpcStatusHandler(c *gin.Context) { running, err := GetFrpcStatus() if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"code": 2, "msg": "查询状态失败"}) return } c.JSON(http.StatusOK, gin.H{"code": 0, "data": gin.H{"running": running}}) } // ========== 🟢 导入 TOML(新增) ========== func importTomlHandler(c *gin.Context) { file, err := c.FormFile("file") if err != nil { c.JSON(http.StatusBadRequest, gin.H{"code": 1, "msg": "请选择文件"}) return } f, err := file.Open() if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"code": 2, "msg": "读取文件失败"}) return } defer f.Close() buf := new(bytes.Buffer) if _, err := buf.ReadFrom(f); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"code": 2, "msg": "读取文件失败"}) return } parsed, err := ParseToml(buf.String()) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"code": 1, "msg": "解析 TOML 失败: " + err.Error()}) return } // 更新全局配置(保持当前 tcpMux 值) cfg := parsed.ToGlobalConfig() currentCfg, _ := GetGlobalConfig() if currentCfg != nil { cfg.TcpMux = currentCfg.TcpMux // 保持当前值不变 } cfg.TcpMux = true // 强制开启 if err := UpdateGlobalConfig(cfg); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"code": 2, "msg": "更新配置失败: " + err.Error()}) return } // 清空并导入隧道 if _, err := DB.Exec("DELETE FROM proxies"); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"code": 2, "msg": "清空隧道失败"}) return } proxies := parsed.ToProxies() for _, p := range proxies { if err := CreateProxy(&p); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"code": 2, "msg": "导入隧道失败: " + err.Error()}) return } } // 生成配置并热加载 if err := GenerateFrpcConfig(); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"code": 2, "msg": "生成配置失败: " + err.Error()}) return } if err := ReloadFrpc(); err != nil { // 导入成功但热加载失败,不影响整体结果 c.JSON(http.StatusOK, gin.H{ "code": 0, "msg": fmt.Sprintf("导入成功!共 %d 条隧道,但热加载失败: %s", len(proxies), err.Error()), }) return } c.JSON(http.StatusOK, gin.H{ "code": 0, "msg": fmt.Sprintf("导入成功!共 %d 条隧道", len(proxies)), }) } // ========== 辅助函数 ========== func generateAndReload() error { if err := GenerateFrpcConfig(); err != nil { return err } return ReloadFrpc() }