package model import ( "fmt" "log" "os" "time" "go.uber.org/zap" "gorm.io/driver/postgres" "gorm.io/driver/sqlite" "gorm.io/gorm" "gorm.io/gorm/logger" "carrot_bbs/internal/config" ) // NewDB 创建数据库连接(用于 Wire 依赖注入) func NewDB(cfg *config.DatabaseConfig) (*gorm.DB, error) { var err error var db *gorm.DB gormLogger := logger.New( log.New(os.Stdout, "\r\n", log.LstdFlags), logger.Config{ SlowThreshold: time.Duration(cfg.SlowThresholdMs) * time.Millisecond, LogLevel: parseGormLogLevel(cfg.LogLevel), IgnoreRecordNotFoundError: cfg.IgnoreRecordNotFound, ParameterizedQueries: cfg.ParameterizedQueries, Colorful: false, }, ) // 根据数据库类型选择驱动 switch cfg.Type { case "sqlite": db, err = gorm.Open(sqlite.Open(cfg.SQLite.Path), &gorm.Config{ Logger: gormLogger, }) case "postgres", "postgresql": dsn := cfg.Postgres.DSN() db, err = gorm.Open(postgres.Open(dsn), &gorm.Config{ Logger: gormLogger, }) default: // 默认使用PostgreSQL dsn := cfg.Postgres.DSN() db, err = gorm.Open(postgres.Open(dsn), &gorm.Config{ Logger: gormLogger, }) } if err != nil { return nil, fmt.Errorf("failed to connect to database: %w", err) } // 配置连接池(SQLite不支持连接池配置,跳过) if cfg.Type != "sqlite" { sqlDB, err := db.DB() if err != nil { return nil, fmt.Errorf("failed to get database instance: %w", err) } sqlDB.SetMaxIdleConns(cfg.MaxIdleConns) sqlDB.SetMaxOpenConns(cfg.MaxOpenConns) } // 自动迁移 if err := autoMigrate(db); err != nil { return nil, fmt.Errorf("failed to auto migrate: %w", err) } zap.L().Info("Database connected and migrated successfully", zap.String("type", cfg.Type), ) return db, nil } func parseGormLogLevel(level string) logger.LogLevel { switch level { case "silent": return logger.Silent case "error": return logger.Error case "warn": return logger.Warn case "info": return logger.Info default: return logger.Warn } } // autoMigrate 自动迁移数据库表 func autoMigrate(db *gorm.DB) error { err := db.AutoMigrate( // 用户相关 &User{}, // 帖子相关 &Post{}, &PostImage{}, // 评论相关 &Comment{}, &CommentLike{}, // 消息相关(使用雪花算法ID和seq机制) // 已读位置存储在 ConversationParticipant.LastReadSeq 中 &Conversation{}, &ConversationParticipant{}, &Message{}, // 系统通知(独立表,每个用户只能看到自己的通知) &SystemNotification{}, // 通知 &Notification{}, // 推送中心相关 &PushRecord{}, // 推送记录 &DeviceToken{}, // 设备Token // 社交 &Follow{}, &UserBlock{}, &PostLike{}, &Favorite{}, // 投票 &VoteOption{}, &UserVote{}, // 敏感词和审核 &SensitiveWord{}, &AuditLog{}, // 日志相关 &OperationLog{}, // 操作日志 &LoginLog{}, // 登录日志 &DataChangeLog{}, // 数据变更日志 // 群组相关 &Group{}, &GroupMember{}, &GroupAnnouncement{}, &GroupJoinRequest{}, // 自定义表情 &UserSticker{}, // 课表 &ScheduleCourse{}, // 用户活跃相关 &UserActiveLog{}, &UserActivityStat{}, // 角色和权限相关 &Role{}, &UserRole{}, &CasbinRule{}, ) if err != nil { return err } // AutoMigrate 不会删除列:清理已废弃的 posts.hot_score if err := dropPostsHotScoreColumnIfExists(db); err != nil { return fmt.Errorf("drop legacy posts.hot_score: %w", err) } // 初始化角色种子数据 if err := seedRoles(db); err != nil { return fmt.Errorf("failed to seed roles: %w", err) } // 初始化权限策略种子数据 if err := seedPermissions(db); err != nil { return fmt.Errorf("failed to seed permissions: %w", err) } return nil } // postWithHotScore 仅用于 Migrator 检测/删除旧列(Post 模型已移除 HotScore) type postWithHotScore struct { HotScore float64 `gorm:"column:hot_score"` } func (postWithHotScore) TableName() string { return "posts" } func dropPostsHotScoreColumnIfExists(db *gorm.DB) error { m := db.Migrator() if !m.HasTable(&Post{}) { return nil } if !m.HasColumn(&postWithHotScore{}, "HotScore") { return nil } return m.DropColumn(&postWithHotScore{}, "HotScore") } // seedRoles 初始化角色种子数据 func seedRoles(db *gorm.DB) error { // 检查是否已有角色数据 var count int64 if err := db.Model(&Role{}).Count(&count).Error; err != nil { return err } // 如果已有数据,跳过种子初始化 if count > 0 { return nil } // 预定义角色数据 roles := []Role{ { Name: RoleSuperAdmin, DisplayName: "超级管理员", Description: "拥有系统最高权限,可以管理所有用户和内容", Priority: 100, }, { Name: RoleAdmin, DisplayName: "管理员", Description: "系统管理员,可以管理用户和内容", Priority: 80, }, { Name: RoleModerator, DisplayName: "版主", Description: "内容审核员,可以审核和管理内容", Priority: 60, }, { Name: RoleUser, DisplayName: "普通用户", Description: "注册用户,拥有基本权限", Priority: 40, }, { Name: RoleBanned, DisplayName: "被封禁用户", Description: "被禁止访问的用户", Priority: 0, }, } // 批量插入角色 if err := db.Create(&roles).Error; err != nil { return err } zap.L().Info("Seeded roles successfully", zap.Int("count", len(roles)), ) return nil } // seedPermissions 初始化权限策略种子数据 func seedPermissions(db *gorm.DB) error { // 检查是否已有权限策略数据 var count int64 if err := db.Model(&CasbinRule{}).Where("ptype = ?", "p").Count(&count).Error; err != nil { return err } // 如果已有数据,跳过种子初始化 if count > 0 { return nil } // 预定义权限策略数据 // p = sub, obj, act (角色, 资源, 操作) permissions := []CasbinRule{ // 超级管理员 - 拥有所有权限 {Ptype: "p", V0: RoleSuperAdmin, V1: "/*", V2: "*"}, // 管理员 - 拥有管理权限 {Ptype: "p", V0: RoleAdmin, V1: "/api/v1/admin/*", V2: "*"}, {Ptype: "p", V0: RoleAdmin, V1: "/api/v1/users/*", V2: "GET"}, {Ptype: "p", V0: RoleAdmin, V1: "/api/v1/posts/*", V2: "*"}, {Ptype: "p", V0: RoleAdmin, V1: "/api/v1/comments/*", V2: "*"}, {Ptype: "p", V0: RoleAdmin, V1: "/api/v1/groups/*", V2: "*"}, // 版主 - 拥有内容审核权限 {Ptype: "p", V0: RoleModerator, V1: "/api/v1/posts/*", V2: "GET"}, {Ptype: "p", V0: RoleModerator, V1: "/api/v1/posts/*", V2: "DELETE"}, {Ptype: "p", V0: RoleModerator, V1: "/api/v1/comments/*", V2: "GET"}, {Ptype: "p", V0: RoleModerator, V1: "/api/v1/comments/*", V2: "DELETE"}, {Ptype: "p", V0: RoleModerator, V1: "/api/v1/groups/*", V2: "GET"}, // 普通用户 - 拥有基本权限 {Ptype: "p", V0: RoleUser, V1: "/api/v1/posts", V2: "GET"}, {Ptype: "p", V0: RoleUser, V1: "/api/v1/posts/*", V2: "GET"}, {Ptype: "p", V0: RoleUser, V1: "/api/v1/posts", V2: "POST"}, {Ptype: "p", V0: RoleUser, V1: "/api/v1/comments/*", V2: "GET"}, {Ptype: "p", V0: RoleUser, V1: "/api/v1/comments", V2: "POST"}, {Ptype: "p", V0: RoleUser, V1: "/api/v1/users/me", V2: "GET"}, {Ptype: "p", V0: RoleUser, V1: "/api/v1/users/me", V2: "PUT"}, {Ptype: "p", V0: RoleUser, V1: "/api/v1/groups", V2: "GET"}, {Ptype: "p", V0: RoleUser, V1: "/api/v1/groups/*", V2: "GET"}, // 被封禁用户 - 无权限 } // 批量插入权限策略 if err := db.Create(&permissions).Error; err != nil { return err } zap.L().Info("Seeded permissions successfully", zap.Int("count", len(permissions)), ) return nil } // CloseDB 关闭数据库连接 func CloseDB(db *gorm.DB) { if sqlDB, err := db.DB(); err == nil { sqlDB.Close() } }