frps端同步frpc2.4-LTS的全套代码库的设计逻辑

This commit is contained in:
2026-08-01 19:31:03 +08:00
parent ccdd5b0725
commit 3214215032
10 changed files with 988 additions and 455 deletions
+80 -127
View File
@@ -4,12 +4,12 @@ import (
"bytes"
"embed"
"encoding/json"
"io"
"io/fs" // 新增
"io/fs"
"net"
"net/http"
"os"
"strings"
"strconv"
"text/template"
"time"
"github.com/gin-gonic/gin"
"golang.org/x/crypto/bcrypt"
@@ -21,9 +21,11 @@ var staticFS embed.FS
func SetupRouter() *gin.Engine {
r := gin.Default()
// 从 embed 读取前端静态文件
staticSubFS, _ := fs.Sub(staticFS, "static")
r.StaticFS("/static", http.FS(staticSubFS))
// 根路由
r.GET("/", func(c *gin.Context) {
content, err := staticFS.ReadFile("static/index.html")
if err != nil {
@@ -33,25 +35,27 @@ func SetupRouter() *gin.Engine {
c.Data(http.StatusOK, "text/html; charset=utf-8", content)
})
// 健康检查
r.GET("/ping", func(c *gin.Context) {
c.String(200, "frps-console 后端已启动 🎉")
})
api := r.Group("/api")
{
// ---- 公开路由(不需要认证) ----
api.GET("/check/users", checkUsersHandler)
api.POST("/register", registerHandler)
api.POST("/login", loginHandler)
api.GET("/ping", pingHandler) // 新增:frps 延迟检测
// ---- 需要认证的路由 ----
auth := api.Group("/")
auth.Use(AuthMiddleware())
{
auth.GET("/config", getConfigHandler)
auth.PUT("/config", updateConfigHandler)
auth.GET("/frps/log", getFrpsLogHandler)
// frps 特有:客户端列表(从 Dashboard API 读取)
// frps 特有:从 Dashboard API 读取客户端和代理列表
auth.GET("/clients", getClientsHandler)
auth.GET("/proxies", getProxiesHandler)
@@ -59,6 +63,7 @@ func SetupRouter() *gin.Engine {
auth.POST("/frps/start", startFrpsHandler)
auth.POST("/frps/stop", stopFrpsHandler)
auth.GET("/frps/status", getFrpsStatusHandler)
auth.GET("/frps/log", getFrpsLogHandler)
auth.POST("/import/toml", importTomlHandler)
auth.GET("/export/toml", ExportTomlHandler)
@@ -70,9 +75,7 @@ func SetupRouter() *gin.Engine {
return r
}
// ============================================================
// 认证 Handler(与 frpc-console 相同,略)
// ============================================================
// ========== 认证 Handler(与 frpc 相同) ==========
func checkUsersHandler(c *gin.Context) {
count, err := CountUsers()
@@ -220,9 +223,7 @@ func changePasswordHandler(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"code": 0, "msg": "密码修改成功"})
}
// ============================================================
// 配置 Handler
// ============================================================
// ========== 配置 Handlerfrps 专用) ==========
func getConfigHandler(c *gin.Context) {
cfg, err := GetGlobalConfig()
@@ -239,6 +240,8 @@ func updateConfigHandler(c *gin.Context) {
c.JSON(http.StatusBadRequest, gin.H{"code": 1, "msg": "请求参数错误"})
return
}
// 强制 tcpMux = truefrps 推荐)
cfg.TcpMux = true
if err := UpdateGlobalConfig(&cfg); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"code": 2, "msg": "更新配置失败: " + err.Error()})
@@ -258,11 +261,8 @@ func updateConfigHandler(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"code": 0, "msg": "配置更新成功"})
}
// ============================================================
// 客户端/代理列表(从 frps Dashboard API 读取)
// ============================================================
// ========== frps Dashboard API 读取 ==========
// FrpsDashboardClient frps Dashboard API 返回的客户端信息
type FrpsDashboardClient struct {
ID int `json:"id"`
Name string `json:"name"`
@@ -273,7 +273,6 @@ type FrpsDashboardClient struct {
ConnTime string `json:"connTime"`
}
// FrpsDashboardProxy frps Dashboard API 返回的代理信息
type FrpsDashboardProxy struct {
Name string `json:"name"`
Type string `json:"type"`
@@ -300,9 +299,8 @@ func getProxiesHandler(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"code": 0, "data": proxies})
}
// fetchClientsFromDashboard 从 frps Dashboard API 读取客户端列表
func fetchClientsFromDashboard() ([]FrpsDashboardClient, error) {
// frps Dashboard 默认在 127.0.0.1:7500,后续可配置
// TODO: 从配置读取 Dashboard 地址(当前硬编码)
resp, err := http.Get("http://127.0.0.1:7500/api/v2/clients")
if err != nil {
return nil, err
@@ -318,7 +316,6 @@ func fetchClientsFromDashboard() ([]FrpsDashboardClient, error) {
return result.Data, nil
}
// fetchProxiesFromDashboard 从 frps Dashboard API 读取代理列表
func fetchProxiesFromDashboard() ([]FrpsDashboardProxy, error) {
resp, err := http.Get("http://127.0.0.1:7500/api/v2/proxies")
if err != nil {
@@ -335,9 +332,7 @@ func fetchProxiesFromDashboard() ([]FrpsDashboardProxy, error) {
return result.Data, nil
}
// ============================================================
// frps 进程管理 Handler
// ============================================================
// ========== frps 进程管理 Handler ==========
func reloadFrpsHandler(c *gin.Context) {
if err := GenerateFrpsConfig(); err != nil {
@@ -376,9 +371,62 @@ func getFrpsStatusHandler(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"code": 0, "data": gin.H{"running": running}})
}
// ============================================================
// 导入/导出 TOML
// ============================================================
// ========== 日志 Handler ==========
func getFrpsLogHandler(c *gin.Context) {
// readTailLog 在 frps.go 中定义,读取 ./data/frps.log
lines, err := readTailLog("./data/frps.log", 200)
if err != nil {
c.JSON(http.StatusOK, gin.H{
"code": 0,
"data": gin.H{
"lines": []string{},
"total": 0,
"error": err.Error(),
},
})
return
}
c.JSON(http.StatusOK, gin.H{
"code": 0,
"data": gin.H{
"lines": lines,
"total": len(lines),
},
})
}
// ========== Ping Handler(检测 frps 服务连通性) ==========
func pingHandler(c *gin.Context) {
target := c.Query("target")
if target == "" {
c.JSON(http.StatusBadRequest, gin.H{"code": 1, "msg": "缺少 target 参数"})
return
}
cfg, err := GetGlobalConfig()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"code": 2, "msg": "读取配置失败"})
return
}
port := cfg.BindPort // frps 使用 bindPort
address := net.JoinHostPort(target, strconv.Itoa(port))
start := time.Now()
conn, err := net.DialTimeout("tcp", address, 5*time.Second)
if err != nil {
c.JSON(http.StatusOK, gin.H{"code": 1, "msg": "ping 失败", "latency": -1})
return
}
conn.Close()
latency := time.Since(start).Milliseconds()
c.JSON(http.StatusOK, gin.H{"code": 0, "latency": latency})
}
// ========== 导入/导出 TOML ==========
func importTomlHandler(c *gin.Context) {
file, err := c.FormFile("file")
@@ -407,11 +455,15 @@ func importTomlHandler(c *gin.Context) {
}
cfg := parsed.ToGlobalConfig()
cfg.TcpMux = true
if err := UpdateGlobalConfig(cfg); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"code": 2, "msg": "更新配置失败: " + err.Error()})
return
}
// frps 没有隧道表,无需清空 proxies,但可以清空 clients 表(如果有)
// 目前 clients 表只做记录,可忽略
if err := GenerateFrpsConfig(); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"code": 2, "msg": "生成配置失败: " + err.Error()})
return
@@ -435,6 +487,7 @@ func ExportTomlHandler(c *gin.Context) {
return
}
// 使用 frps 模板
tmpl, err := template.New("frps").Parse(FrpsTemplateContent)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"code": 2, "msg": "解析模板失败: " + err.Error()})
@@ -451,103 +504,3 @@ func ExportTomlHandler(c *gin.Context) {
c.Header("Content-Disposition", "attachment; filename=frps.toml")
c.String(http.StatusOK, buf.String())
}
// ============================================================
// 日志读取
// ============================================================
func getFrpsLogHandler(c *gin.Context) {
lines, err := readTailLog("./frps.log", 200)
if err != nil {
c.JSON(http.StatusOK, gin.H{
"code": 0,
"data": gin.H{
"lines": []string{},
"total": 0,
"error": err.Error(),
},
})
return
}
c.JSON(http.StatusOK, gin.H{
"code": 0,
"data": gin.H{
"lines": lines,
"total": len(lines),
},
})
}
// readTailLog 读取文件末尾 n 行
func readTailLog(filePath string, n int) ([]string, error) {
file, err := os.Open(filePath)
if err != nil {
return nil, err
}
defer file.Close()
info, err := file.Stat()
if err != nil {
return nil, err
}
fileSize := info.Size()
if fileSize == 0 {
return []string{}, nil
}
const chunkSize = 4096
var lines []string
var leftover []byte
offset := fileSize
for len(lines) < n && offset > 0 {
readSize := chunkSize
if offset < int64(chunkSize) {
readSize = int(offset)
}
offset -= int64(readSize)
buf := make([]byte, readSize)
_, err := file.ReadAt(buf, offset)
if err != nil && err != io.EOF {
return nil, err
}
data := append(buf, leftover...)
leftover = nil
start := 0
for i := len(data) - 1; i >= 0; i-- {
if data[i] == '\n' {
if i+1 < len(data) {
line := string(data[i+1:])
if line != "" {
lines = append([]string{line}, lines...)
if len(lines) >= n {
break
}
}
}
start = i
}
}
if len(lines) < n && start > 0 {
leftover = data[:start]
}
}
if len(lines) < n && len(leftover) > 0 {
parts := strings.Split(string(leftover), "\n")
for i := len(parts) - 1; i >= 0; i-- {
if parts[i] != "" {
lines = append([]string{parts[i]}, lines...)
if len(lines) >= n {
break
}
}
}
}
return lines, nil
}