fix: 提供 SQLite 迁移故障恢复开关

This commit is contained in:
Logan.Li committed 2026-07-30 23:49:38 +08:00
1 parent 99b5d94dc2
commit f22e575124
3 files changed
+21 -8

No files matched your search

+7
View File
@@ -436,6 +436,13 @@ WebSocket 无法设置自定义 header → 后端 `JWTAuth` 支持 `?token=` que
- 生产模式:MySQL + Redis。
- 告警引擎使用 Redis 分布式锁(SetNX + 60s TTL)保证多副本仅 Leader 执行检查。
### 7.4.1 数据保留与清理(强制)
- 对监控指标、Agent 原始上报、日志和审计等时序/高频数据,必须实现可配置的保留期与定时清理;禁止无限累积。
- 清理任务不能只调用 `DELETE`:必须记录并校验受影响行数,处理关联的明细/索引数据,并将执行结果写入系统任务状态,确保清理可审计、失败可发现。
- SQLite 的 `DELETE` 仅释放可复用页,不会缩小 `sqlite3.db` 文件。生产维护窗口内必须在备份成功、应用停写后执行 `PRAGMA wal_checkpoint(TRUNCATE)` 和 `VACUUM`,确认 WAL 与主库空间实际回收;不得在高峰期或存在活跃写入时执行 `VACUUM`。
- 大表不得在每次应用启动时无条件触发全表重建。结构变更必须使用可追踪、幂等的迁移,并评估 SQLite 下 GORM `AutoMigrate` 的重建与 WAL 写放大风险。
### 7.5 健康检查
- 推荐探针:`GET /version`(无 DB 依赖)。
+3
View File
@@ -266,6 +266,9 @@ WebSocket 无法设自定义 header → 后端 JWTAuth 支持 `?token=` query pa
3. **新增 handler 不绑 model** — 用独立 request struct
4. **GORM 指针字段必须解引用** — `*field` 后再用
5. **Update handler 禁止复用 CreateRequest** — 用 `map[string]interface{}` 接收
6. **高频数据必须有保留策略** — 监控、Agent 上报、日志和审计数据必须可配置保留期、定时清理并记录影响行数和任务结果;不能无限累积。
7. **SQLite 清理必须回收文件空间** — `DELETE` 后文件不会自动变小。备份成功且应用停写的维护窗口内,执行 `PRAGMA wal_checkpoint(TRUNCATE)` 与 `VACUUM`,并验证主库/WAL 体积;高峰期禁止 `VACUUM`。
8. **避免启动期重建大表** — SQLite 下 GORM `AutoMigrate` 可能通过重建表完成结构变更并放大 WAL 写入;大表结构变更必须可追踪、幂等,不得每次启动无条件重建。
### ⛔ React 前端规范
+11 -8
View File
@@ -12,20 +12,20 @@ import (
"syscall"
"time"
"ezops/controller"
"ezops/router"
"ezops/config"
"ezops/controller"
"ezops/model"
"ezops/seed"
"ezops/pkg/appdir"
"ezops/pkg/auditcleaner"
"ezops/pkg/billingsync"
"ezops/pkg/database"
snmppkg "ezops/pkg/snmp"
"ezops/pkg/cronscheduler"
"ezops/pkg/systask"
"ezops/pkg/database"
"ezops/pkg/logger"
ezredis "ezops/pkg/redis"
snmppkg "ezops/pkg/snmp"
"ezops/pkg/systask"
"ezops/router"
"ezops/seed"
"github.com/gin-gonic/gin"
"github.com/spf13/viper"
@@ -45,6 +45,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, "跳过启动时的数据库自动迁移(仅用于已完成迁移的故障恢复)")
// 数据库参数
flagDBDriver = flag.String("db-driver", "", "数据库驱动: sqlite, mysql, postgres (默认: sqlite)")
@@ -144,8 +145,10 @@ func main() {
log.Fatal("数据库连接失败", zap.Error(err))
}
// 自动迁移
if err := database.AutoMigrate(db); err != nil {
// 自动迁移;大 SQLite 库异常迁移时可显式跳过,先恢复已存在的服务。 // Logan.Li
if *flagSkipAutoMigrate {
log.Warn("已跳过数据库自动迁移,仅限已完成迁移的故障恢复场景")
} else if err := database.AutoMigrate(db); err != nil {
log.Fatal("数据库迁移失败", zap.Error(err))
}