diff --git a/.gitignore b/.gitignore index 13a73bb..a8c5e5a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ # Build artifacts -server +/server *.tar # Runtime files diff --git a/cmd/server/app.go b/cmd/server/app.go new file mode 100644 index 0000000..213bcd5 --- /dev/null +++ b/cmd/server/app.go @@ -0,0 +1,107 @@ +package main + +import ( + "context" + "fmt" + "log" + "net/http" + + "carrot_bbs/internal/config" + "carrot_bbs/internal/grpc/runner" + "carrot_bbs/internal/router" + "carrot_bbs/internal/service" + + "gorm.io/gorm" +) + +// App 应用程序结构体 +type App struct { + Config *config.Config + DB *gorm.DB + Router *router.Router + PushService service.PushService + GRPCServer *runner.Server + Server *http.Server +} + +// NewApp 创建应用程序 +func NewApp( + cfg *config.Config, + db *gorm.DB, + r *router.Router, + pushService service.PushService, + grpcServer *runner.Server, +) *App { + return &App{ + Config: cfg, + DB: db, + Router: r, + PushService: pushService, + GRPCServer: grpcServer, + } +} + +// Start 启动应用程序 +func (a *App) Start(ctx context.Context) error { + // 1. 启动 gRPC 服务器(在 HTTP 服务器之前) + if a.GRPCServer != nil { + log.Println("Starting gRPC server...") + if err := a.GRPCServer.Start(); err != nil { + return fmt.Errorf("failed to start gRPC server: %w", err) + } + } + + // 2. 启动推送 Worker + a.PushService.StartPushWorker(ctx) + + // 3. 创建 HTTP 服务器 + addr := fmt.Sprintf("%s:%d", a.Config.Server.Host, a.Config.Server.Port) + log.Printf("Starting HTTP server at %s", addr) + + a.Server = &http.Server{ + Addr: addr, + Handler: a.Router.Engine(), + } + + // 4. 启动 HTTP 服务器 + go func() { + if err := a.Server.ListenAndServe(); err != nil && err != http.ErrServerClosed { + log.Fatalf("listen: %s\n", err) + } + }() + + return nil +} + +// Stop 停止应用程序 +func (a *App) Stop(ctx context.Context) error { + log.Println("Shutting down server...") + + // 1. 停止 HTTP 服务器 + if a.Server != nil { + log.Println("Stopping HTTP server...") + if err := a.Server.Shutdown(ctx); err != nil { + log.Printf("HTTP server forced to shutdown: %v", err) + } + } + + // 2. 停止 gRPC 服务器 + if a.GRPCServer != nil { + log.Println("Stopping gRPC server...") + a.GRPCServer.Stop() + } + + // 3. 停止推送 Worker + log.Println("Stopping push worker...") + a.PushService.StopPushWorker() + + // 4. 关闭数据库连接 + log.Println("Closing database connection...") + sqlDB, err := a.DB.DB() + if err == nil { + sqlDB.Close() + } + + log.Println("Server exited") + return nil +} diff --git a/cmd/server/main.go b/cmd/server/main.go new file mode 100644 index 0000000..b09fc64 --- /dev/null +++ b/cmd/server/main.go @@ -0,0 +1,36 @@ +package main + +import ( + "context" + "log" + "os" + "os/signal" + "syscall" + "time" +) + +func main() { + // 初始化应用程序(Wire 自动生成) + app, err := InitializeApp() + if err != nil { + log.Fatalf("failed to initialize app: %v", err) + } + + // 启动应用程序 + ctx := context.Background() + if err := app.Start(ctx); err != nil { + log.Fatalf("failed to start app: %v", err) + } + + // 优雅关闭 + quit := make(chan os.Signal, 1) + signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) + <-quit + + shutdownCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + + if err := app.Stop(shutdownCtx); err != nil { + log.Printf("Error during shutdown: %v", err) + } +} diff --git a/cmd/server/wire.go b/cmd/server/wire.go new file mode 100644 index 0000000..043c50b --- /dev/null +++ b/cmd/server/wire.go @@ -0,0 +1,74 @@ +//go:build wireinject +// +build wireinject + +package main + +import ( + "carrot_bbs/internal/handler" + "carrot_bbs/internal/router" + "carrot_bbs/internal/service" + appwire "carrot_bbs/internal/wire" + + "github.com/google/wire" +) + +// InitializeApp 初始化应用程序 +func InitializeApp() (*App, error) { + wire.Build( + appwire.AllSet, + NewApp, + ProvideRouter, + ) + return nil, nil +} + +// ProvideRouter 提供路由 +func ProvideRouter( + userHandler *handler.UserHandler, + postHandler *handler.PostHandler, + commentHandler *handler.CommentHandler, + messageHandler *handler.MessageHandler, + notificationHandler *handler.NotificationHandler, + uploadHandler *handler.UploadHandler, + jwtService *service.JWTService, + pushHandler *handler.PushHandler, + systemMessageHandler *handler.SystemMessageHandler, + groupHandler *handler.GroupHandler, + stickerHandler *handler.StickerHandler, + gorseHandler *handler.GorseHandler, + voteHandler *handler.VoteHandler, + scheduleHandler *handler.ScheduleHandler, + roleHandler *handler.RoleHandler, + adminUserHandler *handler.AdminUserHandler, + adminPostHandler *handler.AdminPostHandler, + adminCommentHandler *handler.AdminCommentHandler, + adminGroupHandler *handler.AdminGroupHandler, + adminDashboardHandler *handler.AdminDashboardHandler, + activityService service.UserActivityService, + casbinService service.CasbinService, +) *router.Router { + return router.New( + userHandler, + postHandler, + commentHandler, + messageHandler, + notificationHandler, + uploadHandler, + jwtService, + pushHandler, + systemMessageHandler, + groupHandler, + stickerHandler, + gorseHandler, + voteHandler, + scheduleHandler, + roleHandler, + adminUserHandler, + adminPostHandler, + adminCommentHandler, + adminGroupHandler, + adminDashboardHandler, + activityService, + casbinService, + ) +} diff --git a/cmd/server/wire_gen.go b/cmd/server/wire_gen.go new file mode 100644 index 0000000..294428a --- /dev/null +++ b/cmd/server/wire_gen.go @@ -0,0 +1,158 @@ +// Code generated by Wire. DO NOT EDIT. + +//go:generate go run -mod=mod github.com/google/wire/cmd/wire +//go:build !wireinject +// +build !wireinject + +package main + +import ( + "carrot_bbs/internal/handler" + "carrot_bbs/internal/repository" + "carrot_bbs/internal/router" + "carrot_bbs/internal/service" + "carrot_bbs/internal/wire" +) + +// Injectors from wire.go: + +// InitializeApp 初始化应用程序 +func InitializeApp() (*App, error) { + config, err := wire.ProvideConfig() + if err != nil { + return nil, err + } + db, err := wire.ProvideDB(config) + if err != nil { + return nil, err + } + userRepository := repository.NewUserRepository(db) + systemNotificationRepository := repository.NewSystemNotificationRepository(db) + pushRecordRepository := repository.NewPushRecordRepository(db) + deviceTokenRepository := repository.NewDeviceTokenRepository(db) + messageRepository := repository.NewMessageRepository(db) + hub := wire.ProvideSSEHub() + pushService := wire.ProvidePushService(pushRecordRepository, deviceTokenRepository, messageRepository, hub) + postRepository := repository.NewPostRepository(db) + client := wire.ProvideRedisClient(config) + cache := wire.ProvideCache(config, client) + systemMessageService := wire.ProvideSystemMessageService(systemNotificationRepository, pushService, userRepository, postRepository, cache) + emailClient := wire.ProvideEmailClient(config) + emailService := wire.ProvideEmailService(emailClient) + userService := wire.ProvideUserService(userRepository, systemMessageService, emailService, cache) + userActivityRepository := wire.ProvideUserActivityRepository(db, cache) + userActivityService := wire.ProvideUserActivityService(userActivityRepository) + userHandler := wire.ProvideUserHandler(userService, userActivityService) + gorseClient := wire.ProvideGorseClient(config) + openaiClient := wire.ProvideOpenAIClient(config) + postAIService := wire.ProvidePostAIService(openaiClient) + transactionManager := wire.ProvideTransactionManager(db) + postService := wire.ProvidePostService(postRepository, systemMessageService, gorseClient, postAIService, cache, transactionManager) + postHandler := wire.ProvidePostHandler(postService, userService) + commentRepository := repository.NewCommentRepository(db) + commentService := wire.ProvideCommentService(commentRepository, postRepository, systemMessageService, gorseClient, postAIService, cache) + commentHandler := handler.NewCommentHandler(commentService) + chatService := wire.ProvideChatService(db, messageRepository, userRepository, hub, cache) + messageService := wire.ProvideMessageService(db, messageRepository, cache) + groupRepository := repository.NewGroupRepository(db) + groupService := wire.ProvideGroupService(db, groupRepository, userRepository, messageRepository, hub, cache) + messageHandler := wire.ProvideMessageHandler(chatService, messageService, userService, groupService, hub) + notificationRepository := repository.NewNotificationRepository(db) + notificationService := wire.ProvideNotificationService(notificationRepository, cache) + notificationHandler := handler.NewNotificationHandler(notificationService) + s3Client, err := wire.ProvideS3Client(config) + if err != nil { + return nil, err + } + uploadService := wire.ProvideUploadService(s3Client, userService) + uploadHandler := handler.NewUploadHandler(uploadService) + jwtService := wire.ProvideJWTService(config) + pushHandler := handler.NewPushHandler(pushService) + systemMessageHandler := wire.ProvideSystemMessageHandler(systemMessageService, systemNotificationRepository, cache) + groupHandler := wire.ProvideGroupHandler(groupService, userService) + stickerRepository := repository.NewStickerRepository(db) + stickerService := wire.ProvideStickerService(stickerRepository) + stickerHandler := handler.NewStickerHandler(stickerService) + gorseHandler := wire.ProvideGorseHandler(config, db) + voteRepository := repository.NewVoteRepository(db) + voteService := wire.ProvideVoteService(voteRepository, postRepository, cache, postAIService, systemMessageService) + voteHandler := handler.NewVoteHandler(voteService, postService) + scheduleRepository := repository.NewScheduleRepository(db) + scheduleService := wire.ProvideScheduleService(scheduleRepository) + logger := wire.ProvideLogger() + server := wire.ProvideGRPCServer(config, logger) + taskManager := wire.ProvideTaskManager(server) + scheduleSyncService := wire.ProvideScheduleSyncService(taskManager, scheduleRepository, logger) + scheduleHandler := wire.ProvideScheduleHandler(scheduleService, scheduleSyncService) + roleRepository := wire.ProvideRoleRepository(db) + enforcer := wire.ProvideCasbinEnforcer(config, db) + casbinService := wire.ProvideCasbinService(enforcer, cache, roleRepository, config) + roleService := wire.ProvideRoleService(roleRepository, casbinService) + roleHandler := handler.NewRoleHandler(roleService) + adminUserService := wire.ProvideAdminUserService(userRepository) + adminUserHandler := handler.NewAdminUserHandler(adminUserService) + adminPostService := wire.ProvideAdminPostService(postRepository, cache) + adminPostHandler := handler.NewAdminPostHandler(adminPostService) + adminCommentService := wire.ProvideAdminCommentService(commentRepository, postRepository) + adminCommentHandler := handler.NewAdminCommentHandler(adminCommentService) + adminGroupService := wire.ProvideAdminGroupService(groupRepository, userRepository) + adminGroupHandler := handler.NewAdminGroupHandler(adminGroupService) + adminDashboardService := wire.ProvideAdminDashboardService(db, userRepository, postRepository, commentRepository, groupRepository, userActivityRepository) + adminDashboardHandler := handler.NewAdminDashboardHandler(adminDashboardService) + router := ProvideRouter(userHandler, postHandler, commentHandler, messageHandler, notificationHandler, uploadHandler, jwtService, pushHandler, systemMessageHandler, groupHandler, stickerHandler, gorseHandler, voteHandler, scheduleHandler, roleHandler, adminUserHandler, adminPostHandler, adminCommentHandler, adminGroupHandler, adminDashboardHandler, userActivityService, casbinService) + app := NewApp(config, db, router, pushService, server) + return app, nil +} + +// wire.go: + +// ProvideRouter 提供路由 +func ProvideRouter( + userHandler *handler.UserHandler, + postHandler *handler.PostHandler, + commentHandler *handler.CommentHandler, + messageHandler *handler.MessageHandler, + notificationHandler *handler.NotificationHandler, + uploadHandler *handler.UploadHandler, + jwtService *service.JWTService, + pushHandler *handler.PushHandler, + systemMessageHandler *handler.SystemMessageHandler, + groupHandler *handler.GroupHandler, + stickerHandler *handler.StickerHandler, + gorseHandler *handler.GorseHandler, + voteHandler *handler.VoteHandler, + scheduleHandler *handler.ScheduleHandler, + roleHandler *handler.RoleHandler, + adminUserHandler *handler.AdminUserHandler, + adminPostHandler *handler.AdminPostHandler, + adminCommentHandler *handler.AdminCommentHandler, + adminGroupHandler *handler.AdminGroupHandler, + adminDashboardHandler *handler.AdminDashboardHandler, + activityService service.UserActivityService, + casbinService service.CasbinService, +) *router.Router { + return router.New( + userHandler, + postHandler, + commentHandler, + messageHandler, + notificationHandler, + uploadHandler, + jwtService, + pushHandler, + systemMessageHandler, + groupHandler, + stickerHandler, + gorseHandler, + voteHandler, + scheduleHandler, + roleHandler, + adminUserHandler, + adminPostHandler, + adminCommentHandler, + adminGroupHandler, + adminDashboardHandler, + activityService, + casbinService, + ) +} diff --git a/cmd/test_casbin/main.go b/cmd/test_casbin/main.go deleted file mode 100644 index dabb4d5..0000000 --- a/cmd/test_casbin/main.go +++ /dev/null @@ -1,363 +0,0 @@ -package main - -import ( - "context" - "flag" - "fmt" - "log" - "os" - "strings" - - "carrot_bbs/internal/config" - "carrot_bbs/internal/model" - "carrot_bbs/internal/repository" - "carrot_bbs/internal/service" - - "github.com/casbin/casbin/v3" - gormadapter "github.com/casbin/gorm-adapter/v3" - "gorm.io/driver/postgres" - "gorm.io/gorm" -) - -func main() { - configPath := flag.String("config", "configs/config.yaml", "配置文件路径") - flag.Parse() - - fmt.Println("============================================") - fmt.Println("Casbin RBAC1 权限系统测试") - fmt.Println("============================================") - - // 加载配置 - cfg, err := config.Load(*configPath) - if err != nil { - log.Fatalf("Failed to load config: %v", err) - } - fmt.Println("✓ 配置加载成功") - - // 连接数据库 - dsn := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable", - cfg.Database.Postgres.Host, - cfg.Database.Postgres.Port, - cfg.Database.Postgres.User, - cfg.Database.Postgres.Password, - cfg.Database.Postgres.DBName, - ) - - db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{}) - if err != nil { - log.Fatalf("Failed to connect database: %v", err) - } - fmt.Println("✓ 数据库连接成功") - - // 自动迁移 - if err := db.AutoMigrate(&model.Role{}, &model.UserRole{}, &model.CasbinRule{}); err != nil { - log.Fatalf("Failed to migrate: %v", err) - } - fmt.Println("✓ 数据库迁移完成") - - // 初始化角色数据 - if err := initRoles(db); err != nil { - log.Printf("[WARNING] Failed to init roles: %v", err) - } - - // 初始化 Casbin 策略数据 - if err := initCasbinPolicies(db); err != nil { - log.Printf("[WARNING] Failed to init casbin policies: %v", err) - } - - // 创建 GORM 适配器 - adapter, err := gormadapter.NewAdapterByDB(db) - if err != nil { - log.Fatalf("Failed to create GORM adapter: %v", err) - } - - // 创建 Casbin Enforcer - enforcer, err := casbin.NewEnforcer(cfg.Casbin.ModelPath, adapter) - if err != nil { - log.Fatalf("Failed to create enforcer: %v", err) - } - fmt.Println("✓ Casbin enforcer 创建成功") - - // 创建角色仓储 - roleRepo := repository.NewRoleRepository(db) - - // 创建 Casbin 服务 - casbinSvc := service.NewCasbinService(enforcer, nil, roleRepo, &cfg.Casbin) - fmt.Println("✓ Casbin 服务创建成功") - - ctx := context.Background() - - // 测试权限检查 - fmt.Println("\n=== 测试权限检查 ===") - - testCases := []struct { - sub string - obj string - act string - expected bool - }{ - // banned 用户权限 - {"banned", "/api/v1/posts", "GET", true}, - {"banned", "/api/v1/posts", "POST", false}, - {"banned", "/api/v1/admin/users", "GET", false}, - - // user 用户权限 - {"user", "/api/v1/posts", "GET", true}, - {"user", "/api/v1/posts", "POST", true}, - {"user", "/api/v1/posts/123", "PUT", true}, - {"user", "/api/v1/posts/123", "DELETE", true}, - {"user", "/api/v1/comments", "POST", true}, - {"user", "/api/v1/users/me", "GET", true}, - {"user", "/api/v1/admin/users", "GET", false}, - - // moderator 权限 (继承 user) - {"moderator", "/api/v1/posts", "POST", true}, - {"moderator", "/api/v1/admin/posts/123", "DELETE", true}, - {"moderator", "/api/v1/admin/comments/456", "DELETE", true}, - {"moderator", "/api/v1/admin/audit", "GET", true}, - {"moderator", "/api/v1/admin/users", "GET", false}, - - // admin 权限 (继承 moderator) - {"admin", "/api/v1/posts", "POST", true}, - {"admin", "/api/v1/admin/posts/123", "DELETE", true}, - {"admin", "/api/v1/admin/users", "GET", true}, - {"admin", "/api/v1/admin/roles", "GET", true}, - {"admin", "/api/v1/some/unknown/path", "GET", false}, - - // super_admin 权限 (完全访问) - {"super_admin", "/api/v1/admin/anything", "DELETE", true}, - {"super_admin", "/api/v1/admin/users", "GET", true}, - {"super_admin", "/any/path", "ANY", true}, - } - - passed := 0 - failed := 0 - - for _, tc := range testCases { - allowed, err := casbinSvc.Enforce(ctx, tc.sub, tc.obj, tc.act) - if err != nil { - fmt.Printf("✗ 错误: %s -> %s %s: %v\n", tc.sub, tc.obj, tc.act, err) - failed++ - continue - } - - status := "✗ DENIED" - if allowed { - status = "✓ ALLOWED" - } - - // 检查结果是否符合预期 - resultMatch := "" - if allowed != tc.expected { - resultMatch = fmt.Sprintf(" (预期: %v)", tc.expected) - failed++ - } else { - passed++ - } - - fmt.Printf("%s: %s -> %s %s%s\n", status, tc.sub, tc.obj, tc.act, resultMatch) - } - - // 测试角色继承 - fmt.Println("\n=== 测试角色继承 ===") - - // moderator 应该继承 user 的权限 - allowed, _ := casbinSvc.Enforce(ctx, "moderator", "/api/v1/posts", "POST") - fmt.Printf("Moderator 继承 user 权限 - 可以发帖: %v\n", allowed) - - // admin 应该继承 moderator 的权限 - allowed, _ = casbinSvc.Enforce(ctx, "admin", "/api/v1/admin/posts/123", "DELETE") - fmt.Printf("Admin 继承 moderator 权限 - 可以删帖: %v\n", allowed) - - // 测试用户角色管理 - fmt.Println("\n=== 测试用户角色管理 ===") - - testUserID := "test_user_123" - - // 获取用户当前角色 - roles, err := casbinSvc.GetRolesForUser(ctx, testUserID) - if err != nil { - fmt.Printf("获取用户角色失败: %v\n", err) - } else { - fmt.Printf("用户 %s 当前角色: %v\n", testUserID, roles) - } - - // 添加角色 - err = casbinSvc.AddRoleForUser(ctx, testUserID, "moderator") - if err != nil { - fmt.Printf("添加角色失败: %v\n", err) - } else { - fmt.Printf("✓ 为用户 %s 添加 moderator 角色\n", testUserID) - } - - // 再次获取角色 - roles, err = casbinSvc.GetRolesForUser(ctx, testUserID) - if err != nil { - fmt.Printf("获取用户角色失败: %v\n", err) - } else { - fmt.Printf("用户 %s 当前角色: %v\n", testUserID, roles) - } - - // 检查用户是否有指定角色 - hasRole, err := casbinSvc.HasRoleForUser(ctx, testUserID, "moderator") - if err != nil { - fmt.Printf("检查角色失败: %v\n", err) - } else { - fmt.Printf("用户 %s 是否有 moderator 角色: %v\n", testUserID, hasRole) - } - - // 测试用户权限检查 - allowed, err = casbinSvc.EnforceForUser(ctx, testUserID, "/api/v1/admin/posts/123", "DELETE") - if err != nil { - fmt.Printf("用户权限检查失败: %v\n", err) - } else { - fmt.Printf("用户 %s 可以删除管理后台帖子: %v\n", testUserID, allowed) - } - - // 移除角色 - err = casbinSvc.DeleteRoleForUser(ctx, testUserID, "moderator") - if err != nil { - fmt.Printf("移除角色失败: %v\n", err) - } else { - fmt.Printf("✓ 移除用户 %s 的 moderator 角色\n", testUserID) - } - - // 清理测试数据 - cleanupTestData(db, testUserID) - - // 输出测试结果 - fmt.Println("\n============================================") - fmt.Printf("测试结果: %d 通过, %d 失败\n", passed, failed) - fmt.Println("============================================") - - if failed > 0 { - os.Exit(1) - } -} - -// initRoles 初始化角色数据 -func initRoles(db *gorm.DB) error { - roles := []model.Role{ - {Name: "banned", DisplayName: "被封禁用户", Description: "仅可浏览公开内容,无法交互", Priority: 0}, - {Name: "user", DisplayName: "普通用户", Description: "注册用户,拥有基本功能权限", ParentRole: strPtr("banned"), Priority: 10}, - {Name: "moderator", DisplayName: "版主", Description: "内容审核员,管理帖子和评论", ParentRole: strPtr("user"), Priority: 20}, - {Name: "admin", DisplayName: "管理员", Description: "系统管理员,管理用户和内容", ParentRole: strPtr("moderator"), Priority: 30}, - {Name: "super_admin", DisplayName: "超级管理员", Description: "系统最高权限者", ParentRole: strPtr("admin"), Priority: 40}, - } - - for _, role := range roles { - result := db.FirstOrCreate(&role, model.Role{Name: role.Name}) - if result.Error != nil { - return result.Error - } - } - - fmt.Println("✓ 角色数据初始化完成") - return nil -} - -// initCasbinPolicies 初始化 Casbin 策略数据 -func initCasbinPolicies(db *gorm.DB) error { - // 角色继承关系 (g 策略) - gPolicies := []model.CasbinRule{ - {Ptype: "g", V0: "user", V1: "banned"}, - {Ptype: "g", V0: "moderator", V1: "user"}, - {Ptype: "g", V0: "admin", V1: "moderator"}, - {Ptype: "g", V0: "super_admin", V1: "admin"}, - } - - // 权限策略 (p 策略) - pPolicies := []model.CasbinRule{ - // banned 用户权限 (仅允许 GET 请求) - {Ptype: "p", V0: "banned", V1: "/api/v1/posts", V2: "GET"}, - {Ptype: "p", V0: "banned", V1: "/api/v1/posts/*", V2: "GET"}, - {Ptype: "p", V0: "banned", V1: "/api/v1/comments/*", V2: "GET"}, - {Ptype: "p", V0: "banned", V1: "/api/v1/users/*", V2: "GET"}, - - // user 用户权限 (基本操作) - {Ptype: "p", V0: "user", V1: "/api/v1/posts", V2: "POST"}, - {Ptype: "p", V0: "user", V1: "/api/v1/posts/*", V2: "PUT"}, - {Ptype: "p", V0: "user", V1: "/api/v1/posts/*", V2: "DELETE"}, - {Ptype: "p", V0: "user", V1: "/api/v1/posts/*/like", V2: "POST"}, - {Ptype: "p", V0: "user", V1: "/api/v1/posts/*/like", V2: "DELETE"}, - {Ptype: "p", V0: "user", V1: "/api/v1/posts/*/favorite", V2: "POST"}, - {Ptype: "p", V0: "user", V1: "/api/v1/posts/*/favorite", V2: "DELETE"}, - {Ptype: "p", V0: "user", V1: "/api/v1/comments", V2: "POST"}, - {Ptype: "p", V0: "user", V1: "/api/v1/comments/*", V2: "PUT"}, - {Ptype: "p", V0: "user", V1: "/api/v1/comments/*", V2: "DELETE"}, - {Ptype: "p", V0: "user", V1: "/api/v1/users/me", V2: "*"}, - {Ptype: "p", V0: "user", V1: "/api/v1/users/me/*", V2: "*"}, - {Ptype: "p", V0: "user", V1: "/api/v1/conversations", V2: "*"}, - {Ptype: "p", V0: "user", V1: "/api/v1/conversations/*", V2: "*"}, - {Ptype: "p", V0: "user", V1: "/api/v1/messages/*", V2: "*"}, - {Ptype: "p", V0: "user", V1: "/api/v1/notifications", V2: "*"}, - {Ptype: "p", V0: "user", V1: "/api/v1/notifications/*", V2: "*"}, - {Ptype: "p", V0: "user", V1: "/api/v1/uploads/*", V2: "POST"}, - {Ptype: "p", V0: "user", V1: "/api/v1/groups", V2: "*"}, - {Ptype: "p", V0: "user", V1: "/api/v1/groups/*", V2: "*"}, - {Ptype: "p", V0: "user", V1: "/api/v1/stickers", V2: "*"}, - {Ptype: "p", V0: "user", V1: "/api/v1/schedule", V2: "*"}, - {Ptype: "p", V0: "user", V1: "/api/v1/schedule/*", V2: "*"}, - - // moderator 权限 (内容管理) - {Ptype: "p", V0: "moderator", V1: "/api/v1/admin/posts/*", V2: "*"}, - {Ptype: "p", V0: "moderator", V1: "/api/v1/admin/comments/*", V2: "*"}, - {Ptype: "p", V0: "moderator", V1: "/api/v1/admin/audit/*", V2: "GET"}, - - // admin 权限 (用户管理) - {Ptype: "p", V0: "admin", V1: "/api/v1/admin/users/*", V2: "*"}, - {Ptype: "p", V0: "admin", V1: "/api/v1/admin/roles", V2: "GET"}, - {Ptype: "p", V0: "admin", V1: "/api/v1/admin/roles/*", V2: "GET"}, - - // super_admin 权限 (完全访问) - {Ptype: "p", V0: "super_admin", V1: "/*", V2: "*"}, - } - - // 插入 g 策略 - for _, policy := range gPolicies { - var existing model.CasbinRule - result := db.Where("ptype = ? AND v0 = ? AND v1 = ?", policy.Ptype, policy.V0, policy.V1).First(&existing) - if result.Error == gorm.ErrRecordNotFound { - if err := db.Create(&policy).Error; err != nil { - return err - } - } - } - - // 插入 p 策略 - for _, policy := range pPolicies { - var existing model.CasbinRule - result := db.Where("ptype = ? AND v0 = ? AND v1 = ? AND v2 = ?", policy.Ptype, policy.V0, policy.V1, policy.V2).First(&existing) - if result.Error == gorm.ErrRecordNotFound { - if err := db.Create(&policy).Error; err != nil { - return err - } - } - } - - fmt.Println("✓ Casbin 策略数据初始化完成") - return nil -} - -// cleanupTestData 清理测试数据 -func cleanupTestData(db *gorm.DB, userID string) { - db.Where("user_id = ?", userID).Delete(&model.UserRole{}) - fmt.Printf("✓ 清理测试用户 %s 的数据\n", userID) -} - -// strPtr 返回字符串指针 -func strPtr(s string) *string { - return &s -} - -// 辅助函数:检查字符串是否包含通配符匹配 -func matchPattern(pattern, str string) bool { - if pattern == "/*" { - return true - } - if strings.HasSuffix(pattern, "/*") { - prefix := strings.TrimSuffix(pattern, "/*") - return strings.HasPrefix(str, prefix+"/") || str == prefix - } - return pattern == str -} diff --git a/configs/config.yaml b/configs/config.yaml index 363648d..3cab524 100644 --- a/configs/config.yaml +++ b/configs/config.yaml @@ -246,3 +246,4 @@ casbin: - /api/v1/auth/password/send-code - /api/v1/auth/password/reset - /api/v1/auth/refresh + - /api/v1/admin/setup-super-admin diff --git a/go.sum b/go.sum index 02e1658..45f7293 100644 --- a/go.sum +++ b/go.sum @@ -122,6 +122,8 @@ github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs= github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= +github.com/google/subcommands v1.2.0 h1:vWQspBTo2nEqTUFita5/KeEWlUL8kQObDFbub/EN9oE= +github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=