Files
Logan.Li f3a4fb569f
AIOps CI / Backend (test, vet, build) (push) Canceled after 0s
AIOps CI / Agent (test, vet, build) (push) Canceled after 0s
AIOps CI / Frontend (typecheck, lint, build) (push) Canceled after 0s
AIOps CI / API integration (SQLite + redka) (push) Canceled after 0s
fix(deploy): 全新部署链路健壮性(0004 幂等 + 迁移前置 + 空库守卫)
- 0004_metric_aggregate_unique 改为幂等:新库基线已由模型 uniqueIndex 建出
  uniq_metric_agg,再次 CREATE 会以 1061 重复键名中断 migrate up,
  导致 0005_agent_settings 建不出来(全新部署实测复现)
- 新增 database.IndexExists:跨 SQLite/MySQL/PostgreSQL 判断索引是否存在
- 新增 database.EnsureSchemaInitialized:服务启动遇到空库时明确失败并给出
  执行 aiops migrate up 的指引,避免「服务起来了但页面全是空数据」
- run.sh docker up/upgrade/update 先跑一次 aiops migrate up 再启动服务

// Logan.Li
2026-09-14 09:02:42 +00:00

86 lines
2.6 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package database
import (
"fmt"
"strings"
"gorm.io/gorm"
)
// DriverName 从 GORM 连接获取驱动名(sqlite / mysql / postgres)// Logan.Li
func DriverName(db *gorm.DB) string {
if db == nil || db.Dialector == nil {
return "mysql"
}
name := strings.ToLower(db.Dialector.Name())
switch {
case strings.Contains(name, "sqlite"):
return "sqlite"
case strings.Contains(name, "postgres"):
return "postgres"
default:
return "mysql"
}
}
// HourBucketExpr 按小时分桶表达式(跨 SQLite / MySQL)// Logan.Li
func HourBucketExpr(driver string) string {
switch driver {
case "sqlite":
return "cast(strftime('%s', report_time) as integer) / 3600"
default:
return "UNIX_TIMESTAMP(report_time) DIV 3600"
}
}
// CleanupAgentReportsSQL 清理 7 天前 Agent 上报 // Logan.Li
func CleanupAgentReportsSQL(driver string) string {
switch driver {
case "sqlite":
return "DELETE FROM monitor_agent_reports WHERE report_time < datetime('now', '-7 days')"
default:
return "DELETE FROM monitor_agent_reports WHERE report_time < DATE_SUB(NOW(), INTERVAL 7 DAY)"
}
}
// CleanupSNMPMetricsSQL 清理超期 SNMP 采集指标 // Logan.Li
func CleanupSNMPMetricsSQL(driver string, retentionDays int) string {
if retentionDays <= 0 {
retentionDays = 7
}
switch driver {
case "sqlite":
return fmt.Sprintf(
"DELETE FROM monitor_snmp_samples WHERE timestamp < CAST(strftime('%%s', 'now', '-%d days') AS INTEGER)",
retentionDays,
)
default:
return fmt.Sprintf(
"DELETE FROM monitor_snmp_samples WHERE timestamp < UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL %d DAY))",
retentionDays,
)
}
}
// IndexExists 判断某表上是否已存在指定名称的索引(跨 SQLite / MySQL / PostgreSQL)。
// 供版本化迁移做幂等判断:新库基线已由模型(uniqueIndex 标签)建出同名索引时,
// 迁移不应再建一次,否则 MySQL 会以 1061 重复键名中断整个 migrate up。 // Logan.Li
func IndexExists(db *gorm.DB, table string, index string) (bool, error) {
var (
query string
count int64
)
switch DriverName(db) {
case "sqlite":
query = "SELECT COUNT(*) FROM sqlite_master WHERE type = 'index' AND tbl_name = ? AND name = ?"
case "postgres":
query = "SELECT COUNT(*) FROM pg_indexes WHERE schemaname = current_schema() AND tablename = ? AND indexname = ?"
default:
query = "SELECT COUNT(*) FROM information_schema.statistics WHERE table_schema = DATABASE() AND table_name = ? AND index_name = ?"
}
if err := db.Raw(query, table, index).Scan(&count).Error; err != nil {
return false, fmt.Errorf("检查索引 %s.%s 是否存在失败: %w", table, index, err)
}
return count > 0, nil
}