feat: 日志中心 + GRE协议支持 + 流量监控 + 部署脚本

- 新增日志中心功能 (api/logapi.go + Logs.vue)
- 新增GRE协议服务支持 (service/gre/)
- 新增流量监控组件 (TrafficChart.vue)
- 新增L2TP/IPSec测试用例
- 新增日志轮转服务 (logrotate.go)
- 新增部署脚本 (scripts/install.sh, uninstall.sh, service文件)
- 更新i18n国际化文件
- 优化后端API和前端路由
This commit is contained in:
opsx-tools committed 2026-07-25 08:50:34 +08:00
1 parent 7de1f70905
commit a14d9e62e6
25 files changed
+2285 -25

No files matched your search

+148
View File
@@ -0,0 +1,148 @@
// Package service 提供日志轮转工具
package service
import (
"fmt"
"os"
"path/filepath"
"strconv"
"sync"
)
// RotateConfig 配置日志轮转参数
type RotateConfig struct {
MaxSize int64 // 单个日志文件最大字节数
MaxFiles int // 最多保留的轮转文件数
}
// DefaultRotateConfig 从环境变量读取轮转配置
func DefaultRotateConfig(envPrefix string, defaultMaxSize int64, defaultMaxFiles int) RotateConfig {
cfg := RotateConfig{
MaxSize: defaultMaxSize,
MaxFiles: defaultMaxFiles,
}
if v := os.Getenv(envPrefix + "_MAX_SIZE"); v != "" {
if n, err := parseSize(v); err == nil && n > 0 {
cfg.MaxSize = n
}
}
if v := os.Getenv(envPrefix + "_MAX_FILES"); v != "" {
if n, err := strconv.Atoi(v); err == nil && n > 0 {
cfg.MaxFiles = n
}
}
return cfg
}
// parseSize 解析带单位的字节数(如 "50MB", "20M", "1048576")
func parseSize(s string) (int64, error) {
suffixes := map[string]int64{
"KB": 1024,
"MB": 1024 * 1024,
"GB": 1024 * 1024 * 1024,
"K": 1024,
"M": 1024 * 1024,
"G": 1024 * 1024 * 1024,
}
for suffix, mult := range suffixes {
if len(s) > len(suffix) && s[len(s)-len(suffix):] == suffix {
n, err := strconv.ParseInt(s[:len(s)-len(suffix)], 10, 64)
if err != nil {
return 0, err
}
return n * mult, nil
}
}
return strconv.ParseInt(s, 10, 64)
}
// RotateIfNeeded 检查日志文件大小并在需要时轮转
func RotateIfNeeded(logPath string, cfg RotateConfig) error {
info, err := os.Stat(logPath)
if err != nil {
if os.IsNotExist(err) {
return nil
}
return err
}
if info.Size() < cfg.MaxSize {
return nil
}
return rotateFiles(logPath, cfg.MaxFiles)
}
// rotateFiles 执行日志轮转:logPath -> logPath.1, logPath.N-1 -> delete
func rotateFiles(logPath string, maxFiles int) error {
// 删除最旧的文件
oldest := fmt.Sprintf("%s.%d", logPath, maxFiles)
os.Remove(oldest) // 忽略不存在的错误
// 逐个重命名:N-1 -> N, N-2 -> N-1, ... 1 -> 2
for i := maxFiles - 1; i >= 1; i-- {
src := fmt.Sprintf("%s.%d", logPath, i)
dst := fmt.Sprintf("%s.%d", logPath, i+1)
if _, err := os.Stat(src); err == nil {
os.Rename(src, dst)
}
}
// 当前文件 -> .1
if _, err := os.Stat(logPath); err == nil {
return os.Rename(logPath, logPath+".1")
}
return nil
}
// RotatingWriter 是一个带轮转功能的 io.Writer
type RotatingWriter struct {
mu sync.Mutex
path string
cfg RotateConfig
f *os.File
}
// NewRotatingWriter 创建一个带轮转功能的写入器
func NewRotatingWriter(path string, cfg RotateConfig) (*RotatingWriter, error) {
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
return nil, err
}
rw := &RotatingWriter{path: path, cfg: cfg}
if err := rw.open(); err != nil {
return nil, err
}
return rw, nil
}
func (w *RotatingWriter) open() error {
// 先尝试轮转
RotateIfNeeded(w.path, w.cfg)
f, err := os.OpenFile(w.path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o644)
if err != nil {
return err
}
w.f = f
return nil
}
func (w *RotatingWriter) Write(p []byte) (int, error) {
w.mu.Lock()
defer w.mu.Unlock()
n, err := w.f.Write(p)
if err != nil {
return n, err
}
// 每次写入后检查大小(懒轮转)
if info, e := w.f.Stat(); e == nil && info.Size() >= w.cfg.MaxSize {
w.f.Close()
w.open()
}
return n, nil
}
func (w *RotatingWriter) Close() error {
w.mu.Lock()
defer w.mu.Unlock()
if w.f != nil {
return w.f.Close()
}
return nil
}