package database import ( "backend/config" "backend/internal/model" "fmt" "strings" "time" "gorm.io/driver/sqlite" "gorm.io/gorm" "gorm.io/gorm/logger" ) // Init 初始化数据库连接并执行迁移(当前交付形态以 SQLite 为准) // Logan.Li func Init(cfg *config.Config) (*gorm.DB, error) { logLevel := getLogLevel(cfg.Mode) driver := strings.ToLower(strings.TrimSpace(cfg.DBDriver)) if driver != "" && driver != "sqlite" && driver != "sqlite3" { return nil, fmt.Errorf("当前仅支持 SQLite(收到 DB_DRIVER=%s);MySQL/PostgreSQL 尚未接入", cfg.DBDriver) } dsn := cfg.DBName if !strings.Contains(dsn, "?") { dsn += "?_journal_mode=WAL&_busy_timeout=5000&_synchronous=NORMAL&_foreign_keys=on" } fmt.Printf("[数据库] 使用 SQLite(WAL): %s\n", cfg.DBName) db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{ Logger: logger.Default.LogMode(logLevel), }) if err != nil { return nil, fmt.Errorf("SQLite 连接失败: %v", err) } sqlDB, err := db.DB() if err != nil { return nil, err } sqlDB.SetMaxOpenConns(1) // SQLite 写串行化,WAL 下读不阻塞 // Logan.Li sqlDB.SetMaxIdleConns(1) sqlDB.SetConnMaxLifetime(time.Hour) return migrateDatabase(db) } // getLogLevel 根据运行模式返回日志级别 // Logan.Li func getLogLevel(mode string) logger.LogLevel { if mode == "release" { return logger.Warn } return logger.Info } // migrateDatabase 执行数据库迁移 // Logan.Li func migrateDatabase(db *gorm.DB) (*gorm.DB, error) { err := db.AutoMigrate( // 认证与系统 &model.User{}, // 管理员用户 // Logan.Li &model.Setting{}, // 系统设置 // Logan.Li &model.AuditLog{}, // 操作日志 // Logan.Li &model.LicenseTemplate{}, // License 模板 // Logan.Li &model.PaymentGatewayConfig{}, // 支付网关配置 // Logan.Li // 核心授权域 &model.Product{}, // 产品 // Logan.Li &model.Customer{}, // 客户 // Logan.Li &model.CustomerProduct{}, // 客户-产品关联 // Logan.Li &model.License{}, // License // Logan.Li &model.Activation{}, // 激活记录 // Logan.Li // 定价与订单 &model.Plan{}, // 定价计划 // Logan.Li &model.Order{}, // 订单 // Logan.Li // 防重复购买 &model.CustomerEntitlement{}, // 客户权益 // Logan.Limodel.CustomerEntitlement{}, // 客户权益 // Logan.Li // 节点互联 &model.PeerNode{}, // 双向同步对端节点 // Logan.Li ) if err != nil { return nil, fmt.Errorf("数据库迁移失败: %v", err) } // 初始化种子数据(默认管理员、默认设置、示例数据) // Logan.Li if err := SeedDefaults(db); err != nil { return nil, fmt.Errorf("种子数据初始化失败: %v", err) } if err := backfillCustomerProducts(db); err != nil { return nil, fmt.Errorf("客户产品关联回填失败: %v", err) } return db, nil } // backfillCustomerProducts 用历史 License/权益回填客户-产品关联 // Logan.Li func backfillCustomerProducts(db *gorm.DB) error { type pair struct { CustomerID uint ProductID uint } var pairs []pair if err := db.Model(&model.License{}). Select("DISTINCT customer_id, product_id"). Where("customer_id > 0 AND product_id > 0"). Scan(&pairs).Error; err != nil { return err } var entPairs []pair _ = db.Model(&model.CustomerEntitlement{}). Select("DISTINCT customer_id, product_id"). Where("customer_id > 0 AND product_id > 0"). Scan(&entPairs) pairs = append(pairs, entPairs...) seen := map[[2]uint]struct{}{} for _, p := range pairs { k := [2]uint{p.CustomerID, p.ProductID} if _, ok := seen[k]; ok { continue } seen[k] = struct{}{} var n int64 db.Model(&model.CustomerProduct{}). Where("customer_id = ? AND product_id = ?", p.CustomerID, p.ProductID). Count(&n) if n > 0 { continue } if err := db.Create(&model.CustomerProduct{CustomerID: p.CustomerID, ProductID: p.ProductID}).Error; err != nil { return err } } return nil }