Files
photowall/go/internal/database/database.go
T
2026-09-03 17:55:42 +08:00

151 lines
3.8 KiB
Go
Raw 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 (
"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
if !cfg.DBAutoMigrate {
log.Println("[database] mysql connected (auto migrate skipped)")
return nil
}
log.Println("[database] running auto migration and seeding...")
// 增量保护:确保花名册新表及 row_count 字段迁移
_ = db.AutoMigrate(&model.ClassRoster{})
if !db.Migrator().HasColumn(&model.Class{}, "row_count") {
_ = db.Migrator().AddColumn(&model.Class{}, "row_count")
}
// 数据迁移:旧版 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)
}
// 历史数据回填:school_types 多学段字段由原单一 school_type 初始化
db.Exec("UPDATE yz_pw_schools SET school_types = school_type WHERE (school_types IS NULL OR school_types = '') AND school_type <> ''")
// 创建平台管理员
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.ClassRoster{},
&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
}