2026-03-09 21:28:58 +08:00
|
|
|
|
package model
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"fmt"
|
|
|
|
|
|
"log"
|
|
|
|
|
|
"os"
|
|
|
|
|
|
"time"
|
|
|
|
|
|
|
2026-03-17 00:47:17 +08:00
|
|
|
|
"go.uber.org/zap"
|
2026-03-09 21:28:58 +08:00
|
|
|
|
"gorm.io/driver/postgres"
|
|
|
|
|
|
"gorm.io/driver/sqlite"
|
|
|
|
|
|
"gorm.io/gorm"
|
|
|
|
|
|
"gorm.io/gorm/logger"
|
|
|
|
|
|
|
|
|
|
|
|
"carrot_bbs/internal/config"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2026-03-13 09:38:18 +08:00
|
|
|
|
// NewDB 创建数据库连接(用于 Wire 依赖注入)
|
|
|
|
|
|
func NewDB(cfg *config.DatabaseConfig) (*gorm.DB, error) {
|
2026-03-09 21:28:58 +08:00
|
|
|
|
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 {
|
2026-03-13 09:38:18 +08:00
|
|
|
|
return nil, fmt.Errorf("failed to connect to database: %w", err)
|
2026-03-09 21:28:58 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 配置连接池(SQLite不支持连接池配置,跳过)
|
|
|
|
|
|
if cfg.Type != "sqlite" {
|
2026-03-13 09:38:18 +08:00
|
|
|
|
sqlDB, err := db.DB()
|
2026-03-09 21:28:58 +08:00
|
|
|
|
if err != nil {
|
2026-03-13 09:38:18 +08:00
|
|
|
|
return nil, fmt.Errorf("failed to get database instance: %w", err)
|
2026-03-09 21:28:58 +08:00
|
|
|
|
}
|
|
|
|
|
|
sqlDB.SetMaxIdleConns(cfg.MaxIdleConns)
|
|
|
|
|
|
sqlDB.SetMaxOpenConns(cfg.MaxOpenConns)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 自动迁移
|
2026-03-13 09:38:18 +08:00
|
|
|
|
if err := autoMigrate(db); err != nil {
|
|
|
|
|
|
return nil, fmt.Errorf("failed to auto migrate: %w", err)
|
2026-03-09 21:28:58 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-17 00:47:17 +08:00
|
|
|
|
zap.L().Info("Database connected and migrated successfully",
|
|
|
|
|
|
zap.String("type", cfg.Type),
|
|
|
|
|
|
)
|
2026-03-13 09:38:18 +08:00
|
|
|
|
return db, nil
|
2026-03-09 21:28:58 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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{},
|
|
|
|
|
|
|
|
|
|
|
|
// 群组相关
|
|
|
|
|
|
&Group{},
|
|
|
|
|
|
&GroupMember{},
|
|
|
|
|
|
&GroupAnnouncement{},
|
|
|
|
|
|
&GroupJoinRequest{},
|
|
|
|
|
|
|
|
|
|
|
|
// 自定义表情
|
|
|
|
|
|
&UserSticker{},
|
2026-03-12 08:38:14 +08:00
|
|
|
|
|
|
|
|
|
|
// 课表
|
|
|
|
|
|
&ScheduleCourse{},
|
2026-03-14 02:09:38 +08:00
|
|
|
|
|
|
|
|
|
|
// 用户活跃相关
|
|
|
|
|
|
&UserActiveLog{},
|
|
|
|
|
|
&UserActivityStat{},
|
|
|
|
|
|
|
|
|
|
|
|
// 角色和权限相关
|
|
|
|
|
|
&Role{},
|
|
|
|
|
|
&UserRole{},
|
|
|
|
|
|
&CasbinRule{},
|
2026-03-09 21:28:58 +08:00
|
|
|
|
)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-14 18:01:55 +08:00
|
|
|
|
// 初始化角色种子数据
|
|
|
|
|
|
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
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-17 00:47:17 +08:00
|
|
|
|
zap.L().Info("Seeded roles successfully",
|
|
|
|
|
|
zap.Int("count", len(roles)),
|
|
|
|
|
|
)
|
2026-03-14 18:01:55 +08:00
|
|
|
|
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
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-17 00:47:17 +08:00
|
|
|
|
zap.L().Info("Seeded permissions successfully",
|
|
|
|
|
|
zap.Int("count", len(permissions)),
|
|
|
|
|
|
)
|
2026-03-09 21:28:58 +08:00
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// CloseDB 关闭数据库连接
|
2026-03-13 09:38:18 +08:00
|
|
|
|
func CloseDB(db *gorm.DB) {
|
|
|
|
|
|
if sqlDB, err := db.DB(); err == nil {
|
2026-03-09 21:28:58 +08:00
|
|
|
|
sqlDB.Close()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|