chore: 统一本地服务器与远程仓库源码

- 合并 main 与 master 历史并以服务器结构为基线
- 保留迁移、数据保留、AWS 账单与部署安全能力
- 建立 SDD 规格和 CI 验证闭环
This commit is contained in:
Logan.Li committed 2026-08-05 20:03:25 +08:00
commit fd60361d93
460 files changed
+37492 -23871

No files matched your search

+111 -48
View File
@@ -2,6 +2,7 @@ package main
import (
"context"
"encoding/json"
"flag"
"fmt"
"net/http"
@@ -14,6 +15,7 @@ import (
"ezops/config"
"ezops/controller"
"ezops/migrations"
"ezops/model"
"ezops/pkg/appdir"
"ezops/pkg/auditcleaner"
@@ -22,7 +24,7 @@ import (
"ezops/pkg/database"
"ezops/pkg/logger"
ezredis "ezops/pkg/redis"
snmppkg "ezops/pkg/snmp"
"ezops/pkg/retention"
"ezops/pkg/systask"
"ezops/router"
"ezops/seed"
@@ -45,7 +47,7 @@ var (
flagConfig = flag.String("config", "", "配置文件路径 (默认: config.yaml)")
flagMode = flag.String("mode", "", "运行模式: local (SQLite+redka), development, production, release")
flagLogLevel = flag.String("log-level", "", "日志级别: debug, info, warn, error")
flagSkipAutoMigrate = flag.Bool("skip-auto-migrate", false, "跳过启动时的数据库自动迁移(仅用于已完成迁移的故障恢复)")
flagSkipAutoMigrate = flag.Bool("skip-auto-migrate", false, "已废弃:服务启动不再执行自动迁移") // Logan.Li
// 数据库参数
flagDBDriver = flag.String("db-driver", "", "数据库驱动: sqlite, mysql, postgres (默认: sqlite)")
@@ -83,7 +85,6 @@ var (
// @license.name Apache 2.0
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
// @host localhost:8080
// @BasePath /api/v1
// @securityDefinitions.apikey Bearer
@@ -106,6 +107,9 @@ func main() {
case "version", "-v", "--version":
printVersion()
os.Exit(0)
case "migrate":
runMigrationCLI(os.Args[2:])
return
}
}
@@ -145,11 +149,9 @@ func main() {
log.Fatal("数据库连接失败", zap.Error(err))
}
// 自动迁移;大 SQLite 库异常迁移时可显式跳过,先恢复已存在的服务。 // Logan.Li
// 数据库结构只能通过 `aiops migrate up` 显式变更,避免启动期重建表和 WAL 写放大。 // Logan.Li
if *flagSkipAutoMigrate {
log.Warn("已跳过数据库自动迁移,仅限已完成迁移的故障恢复场景")
} else if err := database.AutoMigrate(db); err != nil {
log.Fatal("数据库迁移失败", zap.Error(err))
log.Warn("--skip-auto-migrate 已废弃;服务启动始终不会执行数据库迁移")
}
// 初始化种子数据
@@ -222,55 +224,31 @@ func main() {
})
defer cronSched.Stop()
// 定期清理旧的 agent 上报数据(保留 7 天,防止表膨胀影响查询性能)// Logan.Li
// 每日分批清理 Agent、指标、SNMP、拨测与旧日志,执行结果写入系统任务状态。 // Logan.Li
go func() {
ticker := time.NewTicker(24 * time.Hour)
defer ticker.Stop()
runAgentCleanup := func() {
runRetentionCleanup := func() {
start := time.Now()
cleanupSQL := database.CleanupAgentReportsSQL(cfg.Database.Driver)
r := db.Exec(cleanupSQL)
msg := fmt.Sprintf("清理 %d 条记录", r.RowsAffected)
status := "success"
if r.Error != nil {
status = "failed"
msg = r.Error.Error()
log.Warn("Agent 上报清理失败", zap.Error(r.Error))
} else if r.RowsAffected > 0 {
log.Info("清理过期的 agent 上报数据", zap.Int64("rows", r.RowsAffected))
result, err := retention.Run(db, cfg.Retention)
if err != nil {
systask.Record("retention_cleanup", "failed", err.Error(), time.Since(start))
log.Warn("高频数据保留清理失败", zap.Error(err))
return
}
systask.Record("agent_report_cleanup", status, msg, time.Since(start))
message, marshalErr := json.Marshal(result)
if marshalErr != nil {
systask.Record("retention_cleanup", "failed", marshalErr.Error(), time.Since(start))
log.Warn("高频数据保留结果序列化失败", zap.Error(marshalErr))
return
}
systask.Record("retention_cleanup", "success", string(message), time.Since(start))
log.Info("高频数据保留清理完成", zap.Int64("rows", result.Total))
}
time.Sleep(2 * time.Minute)
runAgentCleanup()
runRetentionCleanup()
for range ticker.C {
runAgentCleanup()
}
}()
// 定期清理超期 SNMP 采集指标(保留 7 天) // Logan.Li
go func() {
ticker := time.NewTicker(24 * time.Hour)
defer ticker.Stop()
runSNMPCleanup := func() {
start := time.Now()
cleanupSQL := database.CleanupSNMPMetricsSQL(cfg.Database.Driver, snmppkg.MetricsRetentionDays)
r := db.Exec(cleanupSQL)
msg := fmt.Sprintf("清理 %d 条 SNMP 指标(保留 %d 天)", r.RowsAffected, snmppkg.MetricsRetentionDays)
status := "success"
if r.Error != nil {
status = "failed"
msg = r.Error.Error()
log.Warn("SNMP 指标清理失败", zap.Error(r.Error))
} else if r.RowsAffected > 0 {
log.Info("清理过期的 SNMP 指标", zap.Int64("rows", r.RowsAffected))
}
systask.Record("snmp_metrics_cleanup", status, msg, time.Since(start))
}
time.Sleep(3 * time.Minute)
runSNMPCleanup()
for range ticker.C {
runSNMPCleanup()
runRetentionCleanup()
}
}()
@@ -378,6 +356,83 @@ func main() {
log.Info("服务已退出")
}
// runMigrationCLI 执行独立的数据库迁移命令,不启动 HTTP 服务或定时任务。 // Logan.Li
func runMigrationCLI(args []string) {
action, flagArgs, err := parseMigrationArgs(args)
if err != nil {
fmt.Fprintf(os.Stderr, "迁移命令错误: %v\n用法: aiops migrate <up|status|baseline> [全局参数]\n", err)
os.Exit(2)
}
// 复用既有全局配置参数解析,确保 migrate 与服务使用同一数据库连接。 // Logan.Li
os.Args = append([]string{os.Args[0]}, flagArgs...)
if err := flag.CommandLine.Parse(flagArgs); err != nil {
fmt.Fprintf(os.Stderr, "迁移参数错误: %v\n", err)
os.Exit(2)
}
cfg := loadConfig()
db, err := database.New(cfg.Database)
if err != nil {
fmt.Fprintf(os.Stderr, "数据库连接失败: %v\n", err)
os.Exit(1)
}
switch action {
case "up":
applied, err := migrations.Up(db)
if err != nil {
fmt.Fprintf(os.Stderr, "迁移失败: %v\n", err)
os.Exit(1)
}
if len(applied) == 0 {
fmt.Println("迁移已是最新状态")
return
}
fmt.Printf("已应用迁移: %s\n", strings.Join(applied, ", "))
case "baseline":
if err := migrations.Baseline(db); err != nil {
fmt.Fprintf(os.Stderr, "基线登记失败: %v\n", err)
os.Exit(1)
}
fmt.Printf("已登记基线: %s(未修改业务表)\n", migrations.BaselineVersion)
case "status":
rows, err := migrations.Status(db)
if err != nil {
fmt.Fprintf(os.Stderr, "读取迁移状态失败: %v\n", err)
os.Exit(1)
}
for _, row := range rows {
status := "pending"
appliedAt := "-"
if row.Applied {
status = "applied"
appliedAt = row.AppliedAt.Format(time.RFC3339)
}
fmt.Printf("%-30s %-8s %-25s %s\n", row.Version, status, appliedAt, row.Description)
}
}
}
// parseMigrationArgs 提取动作并保留其他全局参数,允许 `migrate up --config ...`。 // Logan.Li
func parseMigrationArgs(args []string) (string, []string, error) {
if len(args) == 0 {
return "", nil, fmt.Errorf("缺少动作")
}
var action string
flagArgs := make([]string, 0, len(args)-1)
for _, arg := range args {
if action == "" && (arg == "up" || arg == "status" || arg == "baseline") {
action = arg
continue
}
flagArgs = append(flagArgs, arg)
}
if action == "" {
return "", nil, fmt.Errorf("动作必须是 up、status 或 baseline")
}
return action, flagArgs, nil
}
// loadConfig 加载配置(CLI flags 优先于 config.yaml)// Logan.Li
func loadConfig() *config.Config {
viper.SetConfigName("config")
@@ -414,6 +469,14 @@ func loadConfig() *config.Config {
viper.SetDefault("jwt.secret", "") // 生产环境必须通过配置或 JWT_SECRET 环境变量设置
viper.SetDefault("jwt.expire_hour", 24)
viper.SetDefault("agent.enforce_auth", false)
viper.SetDefault("retention.agent_reports_days", 7)
viper.SetDefault("retention.metric_history_days", 30)
viper.SetDefault("retention.metric_aggregate_days", 365)
viper.SetDefault("retention.snmp_samples_days", 30)
viper.SetDefault("retention.site_results_days", 30)
viper.SetDefault("retention.log_entries_days", 30)
viper.SetDefault("retention.cleanup_batch_size", 10000)
viper.SetDefault("websocket.read_buffer_size", 1024)
viper.SetDefault("websocket.write_buffer_size", 1024)