package database import ( "errors" "fmt" "log" "time" "photowall/internal/config" "photowall/internal/model" "photowall/pkg/hash" "gorm.io/driver/mysql" "gorm.io/gorm" "gorm.io/gorm/logger" "gorm.io/gorm/schema" ) var DB *gorm.DB // Init 连接 MySQL,自动建表,创建平台管理员,写入种子数据。 func Init() error { cfg := config.C dsn := cfg.DSN() db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{ Logger: logger.Default.LogMode(logger.Warn), NamingStrategy: schema.NamingStrategy{ TablePrefix: "yz_pw_", // 统一表前缀 SingularTable: false, }, NowFunc: func() time.Time { return time.Now().Local() }, }) if err != nil { return fmt.Errorf("connect mysql: %w", err) } sqlDB, err := db.DB() if err != nil { return err } sqlDB.SetMaxOpenConns(50) sqlDB.SetMaxIdleConns(10) sqlDB.SetConnMaxLifetime(time.Hour) DB = db // 数据迁移:旧版 bcrypt 密码(60字符) 超过新版 md5(32字符) 列宽, // 在 AutoMigrate 缩列前先清空旧密码,后续 ensureAdmin 会重新设置。 // 注意:salt 列此时可能还不存在,只更新 password_hash。 db.Exec("UPDATE yz_pw_users SET password_hash = '' WHERE LENGTH(password_hash) > 32") // 自动迁移 if err := autoMigrate(db); err != nil { return fmt.Errorf("auto migrate: %w", err) } // 创建平台管理员 if err := ensureAdmin(db); err != nil { return fmt.Errorf("ensure admin: %w", err) } // 写入种子地区数据 if err := seedRegions(db); err != nil { return fmt.Errorf("seed regions: %w", err) } log.Println("[database] mysql connected, tables migrated, admin & seeds ready") return nil } func autoMigrate(db *gorm.DB) error { return db.AutoMigrate( &model.User{}, &model.UserContact{}, &model.EducationHistory{}, &model.Region{}, &model.School{}, &model.College{}, &model.Class{}, &model.ClassMember{}, &model.ClassPhoto{}, &model.Invitation{}, &model.AuditLog{}, &model.SmsCode{}, &model.PasswordReset{}, &model.SystemConfig{}, &model.Menu{}, &model.UploadFile{}, &model.SensitiveWord{}, ) } func ensureAdmin(db *gorm.DB) error { cfg := config.C var admin model.User err := db.Where("role = ?", "admin").First(&admin).Error if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { return err } salt, pwdHash := hash.Password(cfg.AdminPassword) if errors.Is(err, gorm.ErrRecordNotFound) { // 不存在则创建 admin = model.User{ Username: cfg.AdminUsername, PasswordHash: pwdHash, Salt: salt, Nickname: "平台管理员", Role: "admin", Bio: "系统默认平台管理员,负责班级审核与巡查", } return db.Create(&admin).Error } // 已存在:用户名/密码方式变更时同步更新(兼容旧 bcrypt 数据) needUpdate := false if admin.Username != cfg.AdminUsername { admin.Username = cfg.AdminUsername needUpdate = true } // bcrypt 哈希长度 60,md5 长度 32;salt 为空说明是旧数据 if len(admin.PasswordHash) == 60 || admin.Salt == "" { admin.PasswordHash = pwdHash admin.Salt = salt needUpdate = true } if needUpdate { return db.Save(&admin).Error } return nil }