// Package router 装配 gin 引擎与各模块路由。 // 路由分层:/api/ma(小程序,靠请求头 app_id 鉴权)、/api/admin(后台)。 package router import ( "context" "log/slog" "os" "time" "github.com/gin-gonic/gin" "gorm.io/gorm" adminhdl "portal-mini-go/internal/handler/admin" miniapphdl "portal-mini-go/internal/handler/admin/miniapp" rbachdl "portal-mini-go/internal/handler/admin/rbac" userhdl "portal-mini-go/internal/handler/admin/user" app1hdl "portal-mini-go/internal/handler/miniapp/app1" filehdl "portal-mini-go/internal/handler/file" "portal-mini-go/internal/middleware" "portal-mini-go/internal/pkg/config" "portal-mini-go/internal/pkg/response" "portal-mini-go/internal/pkg/session" "portal-mini-go/internal/pkg/storage" "portal-mini-go/internal/pkg/wechat" adminrepo "portal-mini-go/internal/repository/admin" miniapprepo "portal-mini-go/internal/repository/miniapp" "portal-mini-go/internal/repository/user" filerepo "portal-mini-go/internal/repository/file" authsvc "portal-mini-go/internal/service/admin/auth" miniappsvc "portal-mini-go/internal/service/admin/miniapp" adminrbac "portal-mini-go/internal/service/admin/rbac" usersvc "portal-mini-go/internal/service/admin/user" app1svc "portal-mini-go/internal/service/miniapp/app1" filesvc "portal-mini-go/internal/service/file" ) // New 构造并装配整个 HTTP 服务。 func New(cfg *config.Config, db *gorm.DB, store session.Store) *gin.Engine { if cfg.Server.Mode == "release" { gin.SetMode(gin.ReleaseMode) } r := gin.New() r.Use(middleware.Recovery(), middleware.Trace(), middleware.ErrorHandler()) r.GET("/healthz", func(c *gin.Context) { response.OK(c, gin.H{"status": "ok"}) }) // 文件存储:依据配置构造(当前支持本地磁盘,minIO 等预留)。 fileStore, err := storage.New(cfg.Upload) if err != nil { slog.Error("init storage", slog.Any("err", err)) os.Exit(1) } // 本地存储静态目录对外可访问(与 file.URL 拼接的 baseURL 对齐)。 r.Static(cfg.Upload.BaseURL, cfg.Upload.LocalPath) // 共享的文件服务实例,admin 与小程序端复用。 fileRepo := filerepo.New(db) fileSvc := filesvc.New(fileRepo, fileStore, cfg.Upload) fileHdl := filehdl.NewHandler(fileSvc) // 共享基础设施:微信客户端、用户仓储、小程序仓储。 wx := wechat.NewClient() userRepo := user.New(db) miniRepo := miniapprepo.New(db) // 小程序分组:先校验请求头 app_id 身份,再挂载各小程序业务路由。 ma := r.Group("/api/ma") ma.Use(middleware.MiniAppAuth(miniRepo)) { app1Svc := app1svc.New(userRepo, miniRepo, wx, store) app1hdl.Register(ma, app1hdl.NewHandler(app1Svc), middleware.MiniUserAuth(store)) // 文件上传服务:小程序端已登录用户可上传/管理文件。 maAuth := ma.Group("") maAuth.Use(middleware.MiniUserAuth(store)) filehdl.RegisterMiniAppUpload(maAuth, fileHdl) } // 后台 RBAC 基础设施:仓储、缓存、权限 Provider、种子初始化。 adminUserRepo := adminrepo.NewUserRepository(db) adminRoleRepo := adminrepo.NewRoleRepository(db) adminPermRepo := adminrepo.NewPermissionRepository(db) permCache := adminrbac.NewMemoryCache(5 * time.Minute) provider := adminrbac.NewProvider(adminUserRepo, adminRoleRepo, adminPermRepo, permCache) if err := provider.Seed(context.Background(), seedDefault("ADMIN_SEED_USERNAME", "admin"), seedDefault("ADMIN_SEED_PASSWORD", "Admin@123456"), ); err != nil { slog.Error("rbac seed", slog.Any("err", err)) } // 后台管理:登录为公开接口,其余均经 JWT 鉴权 + 权限加载(注入角色/权限集到 context)。 ad := r.Group("/api/admin") { authSvc := authsvc.New(adminUserRepo, provider, cfg.Admin.JWTSecret, parseTTL(cfg.Admin.TokenTTL)) adminHandler := adminhdl.NewHandler(authSvc) // 登录公开,不挂 JWT 鉴权中间件。 adminhdl.RegisterLogin(ad, adminHandler) // 鉴权后分组:AdminAuth + PermissionLoader。 authAd := ad.Group("") authAd.Use(middleware.AdminAuth(cfg.Admin.JWTSecret)) authAd.Use(middleware.PermissionLoader(provider)) { adminhdl.Register(authAd, adminHandler) // RBAC 管理接口(自身受 PermAdminManage / PermRoleManage / PermPermManage 守卫)。 rbacHandler := rbachdl.NewHandler( adminrbac.NewAdminService(adminUserRepo, provider), adminrbac.NewRoleService(adminRoleRepo, provider), adminrbac.NewPermissionService(adminPermRepo, provider), ) rbachdl.Register(authAd, rbacHandler) // 小程序管理:被后台纳管的微信小程序配置(增删改查)。 miniSvc := miniappsvc.New(miniRepo) miniapphdl.Register(authAd, miniapphdl.NewHandler(miniSvc)) // 用户管理:各小程序端用户(列表、详情、编辑、禁用/启用)。 userSvc := usersvc.New(userRepo) userhdl.Register(authAd, userhdl.NewHandler(userSvc)) // 文件上传服务:后台已认证管理员可上传/管理文件(admin 与 ma 双入口复用同一 Service)。 filehdl.RegisterAdminUpload(authAd, fileHdl) } } return r } // seedDefault 读取环境变量,缺失时回退到默认值(仅用于首次种子初始化)。 func seedDefault(key, def string) string { if v := os.Getenv(key); v != "" { return v } return def } func parseTTL(s string) time.Duration { if d, err := time.ParseDuration(s); err == nil { return d } return 24 * time.Hour }