mengstack-api/internal/app/app.go
MengStack Dev f8c374a624 feat(rbac): M3 RBAC 权限模型完整实现
- 4 表设计: permissions, roles, role_permissions(m2m), user_roles
- 多租户隔离: 角色和用户角色分配按 tenant_id 隔离
- 权限并集: 多角色用户取权限并集,无继承
- 系统角色保护: is_system 角色不可修改/删除
- 种子数据: 7 个默认权限 + 3 个系统角色(super_admin/tenant_admin/member)
- 权限中间件: RequirePermission 支持按路由粒度控制
- API 路由: /api/v1/rbac/ 下完整 CRUD 端点
- fx 注册: RBAC Module 集成到 app.go
2026-10-03 00:54:26 +08:00

107 lines
2.5 KiB
Go

package app
import (
"context"
"fmt"
"net/http"
"time"
"mengstack/internal/app/cache"
"mengstack/internal/app/database"
"mengstack/internal/app/health"
"mengstack/internal/app/middleware"
"mengstack/internal/config"
"mengstack/internal/logger"
authinterfaces "mengstack/internal/modules/auth/interfaces"
rbacinterfaces "mengstack/internal/modules/rbac/interfaces"
"github.com/gin-gonic/gin"
"github.com/redis/go-redis/v9"
"go.uber.org/fx"
"go.uber.org/zap"
"gorm.io/gorm"
swaggerFiles "github.com/swaggo/files"
ginSwagger "github.com/swaggo/gin-swagger"
)
var Module = fx.Module("app",
fx.Provide(newEngine),
fx.Provide(newServer),
fx.Invoke(registerLifecycle),
)
func newEngine(
cfg *config.Config,
log *zap.Logger,
db *gorm.DB,
rdb *redis.Client,
authHandler *authinterfaces.Handler,
authMW gin.HandlerFunc,
rbacHandler *rbacinterfaces.Handler,
) *gin.Engine {
ginMode := "release"
if cfg.Server.Mode == "debug" || cfg.Server.Mode == "dev" {
ginMode = "debug"
}
gin.SetMode(ginMode)
r := gin.New()
r.Use(middleware.RequestID())
r.Use(middleware.RequestLogger())
r.Use(middleware.CORS())
r.Use(gin.Recovery())
healthHandler := health.NewHandler(db, rdb, log)
r.GET("/health", healthHandler.Handle)
r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
authinterfaces.SetupRoutes(r, authHandler, authMW)
rbacinterfaces.SetupRoutes(r, rbacHandler, authMW)
return r
}
func newServer(cfg *config.Config, engine *gin.Engine) *http.Server {
return &http.Server{
Addr: fmt.Sprintf(":%d", cfg.Server.Port),
Handler: engine,
ReadTimeout: 15 * time.Second,
WriteTimeout: 15 * time.Second,
IdleTimeout: 60 * time.Second,
}
}
func registerLifecycle(lc fx.Lifecycle, srv *http.Server, log *zap.Logger) {
lc.Append(fx.Hook{
OnStart: func(ctx context.Context) error {
log.Info("server starting", zap.String("addr", srv.Addr))
go func() {
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Error("server error", zap.Error(err))
}
}()
return nil
},
OnStop: func(ctx context.Context) error {
log.Info("server shutting down")
return srv.Shutdown(ctx)
},
})
}
func NewApp() *fx.App {
return fx.New(
fx.Provide(
func() (*config.Config, error) { return config.Load() },
func(cfg *config.Config) (*zap.Logger, error) { return logger.New(cfg.Log) },
),
database.Module,
cache.Module,
authinterfaces.Module,
rbacinterfaces.Module,
Module,
)
}