需要测试相关功能

This commit is contained in:
2026-07-28 20:47:41 +08:00
parent 6904e4993f
commit ef6c75aef9
5 changed files with 231 additions and 260 deletions
+155 -141
View File
@@ -49,7 +49,7 @@ func SetupRouter() *gin.Engine {
api.GET("/check/users", checkUsersHandler)
api.POST("/register", registerHandler)
api.POST("/login", loginHandler)
api.GET("/ping", pingHandler) // ← 移到这里
api.GET("/ping", pingHandler)
// ---- 需要认证的路由 ----
auth := api.Group("/")
@@ -80,7 +80,7 @@ func SetupRouter() *gin.Engine {
return r
}
// ========== 所有 Handler ==========
// ========== 认证 Handler ==========
func checkUsersHandler(c *gin.Context) {
count, err := CountUsers()
@@ -148,36 +148,6 @@ func registerHandler(c *gin.Context) {
})
}
// Ping 延迟检测
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.ServerPort
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})
}
func loginHandler(c *gin.Context) {
var req struct {
Username string `json:"username"`
@@ -258,6 +228,8 @@ func changePasswordHandler(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"code": 0, "msg": "密码修改成功"})
}
// ========== 配置 Handler ==========
func getConfigHandler(c *gin.Context) {
cfg, err := GetGlobalConfig()
if err != nil {
@@ -293,6 +265,8 @@ func updateConfigHandler(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"code": 0, "msg": "配置更新成功"})
}
// ========== 隧道 Handler ==========
func getProxiesHandler(c *gin.Context) {
proxies, err := GetProxies()
if err != nil {
@@ -383,6 +357,8 @@ func deleteProxyHandler(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"code": 0, "msg": "隧道删除成功"})
}
// ========== frpc 进程管理 Handler ==========
func reloadFrpcHandler(c *gin.Context) {
if err := GenerateFrpcConfig(); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"code": 2, "msg": "生成配置失败: " + err.Error()})
@@ -420,6 +396,144 @@ func getFrpcStatusHandler(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"code": 0, "data": gin.H{"running": running}})
}
// ========== 日志 Handler ==========
func getFrpcLogHandler(c *gin.Context) {
lines, err := readTailLog("./frpc.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
}
// ========== Ping Handler ==========
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.ServerPort
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")
if err != nil {
@@ -505,14 +619,22 @@ func ExportTomlHandler(c *gin.Context) {
}
}
// 构建与 GenerateFrpcConfig 一致的数据结构
data := struct {
*GlobalConfig
Proxies []Proxy
Proxies []Proxy
WireProtocolLine string
}{
GlobalConfig: cfg,
Proxies: activeProxies,
}
if cfg.WireProtocolV2 {
data.WireProtocolLine = `wireProtocol = "v2"`
} else {
data.WireProtocolLine = ""
}
tmpl, err := template.New("frpc").Parse(FrpcTemplateContent)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"code": 2, "msg": "解析模板失败: " + err.Error()})
@@ -530,114 +652,6 @@ func ExportTomlHandler(c *gin.Context) {
c.String(http.StatusOK, buf.String())
}
// ========== 日志读取 ==========
func getFrpcLogHandler(c *gin.Context) {
// 读取 ./frpc.log,最多返回 200 行(最新的 200 行)
lines, err := readTailLog("./frpc.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 {
// 从 leftover 中提取行
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
}
func generateAndReload() error {
if err := GenerateFrpcConfig(); err != nil {
return err