frps端同步frpc2.4-LTS的全套代码库的设计逻辑
This commit is contained in:
@@ -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
|
||||
// ============================================================
|
||||
// ========== 配置 Handler(frps 专用) ==========
|
||||
|
||||
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 = true(frps 推荐)
|
||||
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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user