Files
SamWaf/wafdb/migrations_log.go
2025-11-25 09:07:21 +08:00

263 lines
7.7 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 wafdb
import (
"SamWaf/common/zlog"
"SamWaf/innerbean"
"SamWaf/model"
"fmt"
"time"
"github.com/go-gormigrate/gormigrate/v2"
"gorm.io/gorm"
)
// RunLogDBMigrations 执行日志数据库迁移(完全兼容老用户)
func RunLogDBMigrations(db *gorm.DB) error {
zlog.Info("开始执行log数据库迁移检查...")
// 检测表和索引的存在情况
tablesExist := checkLogTablesExist(db)
indexesExist := checkLogIndexesExist(db)
zlog.Info("数据库状态检测",
"表是否存在", tablesExist,
"索引是否完整", indexesExist)
m := gormigrate.New(db, gormigrate.DefaultOptions, []*gormigrate.Migration{
// 迁移1: 创建表(如果不存在)
{
ID: "202511140001_initial_log_tables",
Migrate: func(tx *gorm.DB) error {
if tablesExist {
zlog.Info("迁移 202511140001: 表已存在,执行结构同步")
// 表已存在,只做结构同步(安全操作,不会删除字段/数据)
if err := tx.AutoMigrate(
&innerbean.WebLog{},
&model.AccountLog{},
&model.WafSysLog{},
&model.OneKeyMod{},
); err != nil {
return fmt.Errorf("同步表结构失败: %w", err)
}
zlog.Info("表结构同步成功(数据完整保留)")
} else {
zlog.Info("迁移 202511140001: 创建新表")
// 表不存在,创建所有表
if err := tx.AutoMigrate(
&innerbean.WebLog{},
&model.AccountLog{},
&model.WafSysLog{},
&model.OneKeyMod{},
); err != nil {
return fmt.Errorf("创建log表失败: %w", err)
}
zlog.Info("log表创建成功")
}
return nil
},
Rollback: func(tx *gorm.DB) error {
if tablesExist {
// 如果是老数据库,不执行删除操作(保护数据)
zlog.Info("回滚 202511140001: 检测到已存在数据,跳过表删除(保护用户数据)")
return nil
}
// 新数据库可以安全删除
zlog.Info("回滚 202511140001: 删除表")
return tx.Migrator().DropTable(
&innerbean.WebLog{},
&model.AccountLog{},
&model.WafSysLog{},
&model.OneKeyMod{},
)
},
},
// 迁移2: 创建索引(幂等操作)
{
ID: "202511140002_create_log_indexes",
Migrate: func(tx *gorm.DB) error {
if indexesExist {
zlog.Info("迁移 202511140002: 索引已完整,跳过创建")
return nil
}
zlog.Info("迁移 202511140002: 开始创建索引")
return createLogIndexes(tx)
},
Rollback: func(tx *gorm.DB) error {
zlog.Info("回滚 202511140002: 删除索引")
return dropLogIndexes(tx)
},
},
// 迁移3: 创建通知日志表
{
ID: "202511240001_add_notify_log_table",
Migrate: func(tx *gorm.DB) error {
zlog.Info("迁移 202511240001: 创建通知日志表")
// 创建通知日志表
if err := tx.AutoMigrate(
&model.NotifyLog{},
); err != nil {
return fmt.Errorf("创建通知日志表失败: %w", err)
}
zlog.Info("通知日志表创建成功")
return nil
},
Rollback: func(tx *gorm.DB) error {
zlog.Info("回滚 202511240001: 删除通知日志表")
return tx.Migrator().DropTable(
&model.NotifyLog{},
)
},
},
})
// 执行迁移
if err := m.Migrate(); err != nil {
errMsg := fmt.Sprintf("log数据库迁移失败: %v", err)
zlog.Error("迁移执行错误", "error", err.Error())
return fmt.Errorf("%s", errMsg)
}
zlog.Info("log数据库迁移成功完成")
return nil
}
// checkLogTablesExist 检查所有log表是否存在
func checkLogTablesExist(db *gorm.DB) bool {
tables := []interface{}{
&innerbean.WebLog{},
&model.AccountLog{},
&model.WafSysLog{},
&model.OneKeyMod{},
}
for _, table := range tables {
if !db.Migrator().HasTable(table) {
return false
}
}
return true
}
// checkLogIndexesExist 检查所有log索引是否存在
func checkLogIndexesExist(db *gorm.DB) bool {
// 需要检查的索引列表(表名, 索引名)
indexes := []struct {
TableName string
IndexName string
}{
{"web_logs", "idx_web_logs_task_flag_time"},
{"web_logs", "idx_web_time_tenant_user_code"},
{"web_logs", "idx_req_uuid_web_logs"},
{"web_logs", "idx_tenant_usercode_web_logs"},
{"web_logs", "idx_web_time_desc_tenant_user_code"},
{"web_logs", "idx_web_time_desc_tenant_user_code_ip"},
{"web_logs", "idx_web_guest_id_entification"},
}
for _, idx := range indexes {
if !checkIndexExists(db, idx.TableName, idx.IndexName) {
zlog.Info("索引不存在", "table", idx.TableName, "index", idx.IndexName)
return false
}
}
return true
}
// createLogIndexes 创建所有log索引幂等操作
func createLogIndexes(tx *gorm.DB) error {
zlog.Info("开始创建log索引可能需要几分钟...")
startTime := time.Now()
indexes := []struct {
Name string
SQL string
}{
{
Name: "idx_web_logs_task_flag_time",
SQL: "CREATE INDEX IF NOT EXISTS idx_web_logs_task_flag_time ON web_logs (task_flag, unix_add_time)",
},
{
Name: "idx_web_time_tenant_user_code",
SQL: "CREATE INDEX IF NOT EXISTS idx_web_time_tenant_user_code ON web_logs (unix_add_time, tenant_id, user_code)",
},
{
Name: "idx_req_uuid_web_logs",
SQL: "CREATE INDEX IF NOT EXISTS idx_req_uuid_web_logs ON web_logs (REQ_UUID, tenant_id, user_code)",
},
{
Name: "idx_tenant_usercode_web_logs",
SQL: "CREATE INDEX IF NOT EXISTS idx_tenant_usercode_web_logs ON web_logs (tenant_id, user_code)",
},
{
Name: "idx_web_time_desc_tenant_user_code",
SQL: "CREATE INDEX IF NOT EXISTS idx_web_time_desc_tenant_user_code ON web_logs (unix_add_time desc, tenant_id, user_code)",
},
{
Name: "idx_web_time_desc_tenant_user_code_ip",
SQL: "CREATE INDEX IF NOT EXISTS idx_web_time_desc_tenant_user_code_ip ON web_logs (unix_add_time desc, tenant_id, user_code, src_ip)",
},
{
Name: "idx_web_guest_id_entification",
SQL: "CREATE INDEX IF NOT EXISTS idx_web_guest_id_entification ON web_logs (guest_id_entification, day, is_bot, host_code)",
},
}
for _, idx := range indexes {
zlog.Info("开始创建索引", "index", idx.Name, "sql", idx.SQL)
indexStartTime := time.Now()
if err := tx.Exec(idx.SQL).Error; err != nil {
// 记录详细的错误信息
errMsg := fmt.Sprintf("创建索引失败 %s: %v (错误类型: %T)", idx.Name, err, err)
zlog.Error("索引创建失败详情", "index", idx.Name, "error", err.Error(), "sql", idx.SQL)
return fmt.Errorf("%s", errMsg)
}
indexDuration := time.Since(indexStartTime)
zlog.Info("索引创建成功", "index", idx.Name, "耗时", indexDuration.String())
}
duration := time.Since(startTime)
zlog.Info("所有log索引创建完成", "耗时", duration.String())
return nil
}
// dropLogIndexes 删除所有log索引
func dropLogIndexes(tx *gorm.DB) error {
zlog.Info("开始删除log索引")
indexes := []string{
"idx_web_logs_task_flag_time",
"idx_web_time_tenant_user_code",
"idx_req_uuid_web_logs",
"idx_tenant_usercode_web_logs",
"idx_web_time_desc_tenant_user_code",
"idx_web_time_desc_tenant_user_code_ip",
"idx_web_guest_id_entification",
}
for _, indexName := range indexes {
if err := tx.Exec(fmt.Sprintf("DROP INDEX IF EXISTS %s", indexName)).Error; err != nil {
zlog.Warn("删除索引失败(可能不存在)", "index", indexName, "error", err)
} else {
zlog.Info("索引删除成功", "index", indexName)
}
}
zlog.Info("所有log索引删除完成")
return nil
}
// RollbackLogDBMigration 回滚到指定版本
func RollbackLogDBMigration(db *gorm.DB, migrationID string) error {
zlog.Info("准备回滚log迁移", "target_version", migrationID)
m := gormigrate.New(db, gormigrate.DefaultOptions, []*gormigrate.Migration{})
if err := m.RollbackTo(migrationID); err != nil {
return fmt.Errorf("回滚失败: %w", err)
}
zlog.Info("回滚成功完成", "version", migrationID)
return nil
}