diff --git a/internal/common/errors.go b/internal/common/errors.go index a014b46..571c778 100644 --- a/internal/common/errors.go +++ b/internal/common/errors.go @@ -34,4 +34,5 @@ var ( ErrLoginBeforeDelete = errors.New("请先登录再注销") ErrNeedsConfirmRestore = errors.New("需要二次确认恢复账号") ErrOwnerCannotDelete = errors.New("站长不可自主注销,请先转让站长权限") + ErrRegistrationDisabled = errors.New("注册功能已关闭") ) diff --git a/internal/common/helper.go b/internal/common/helper.go index c2330fc..04ce89c 100644 --- a/internal/common/helper.go +++ b/internal/common/helper.go @@ -6,7 +6,7 @@ import ( "github.com/gin-gonic/gin" ) -// BuildPageData 构建页面模板数据,自动注入登录状态与 CSRF token +// BuildPageData 构建页面模板数据,自动注入登录状态、站点信息与 CSRF token func BuildPageData(c *gin.Context, extra gin.H) gin.H { data := gin.H{} for k, v := range extra { @@ -20,6 +20,8 @@ func BuildPageData(c *gin.Context, extra gin.H) gin.H { if token, exists := c.Get("csrf_token"); exists { data["CSRFToken"] = token } + // 注入站点信息(由 InjectSiteInfo 中间件设置) + injectSiteInfo(c, data) return data } @@ -48,3 +50,16 @@ func BuildAdminPageData(c *gin.Context, extra gin.H) gin.H { } return data } + +// injectSiteInfo 从 context 注入站点展示信息 +func injectSiteInfo(c *gin.Context, data gin.H) { + if brand, exists := c.Get("site_brand"); exists { + data["Brand"] = brand + } + if framework, exists := c.Get("site_framework"); exists { + data["Framework"] = framework + } + if copyrightStart, exists := c.Get("site_copyright_start"); exists { + data["CopyrightStart"] = copyrightStart + } +} diff --git a/internal/config/site_settings.go b/internal/config/site_settings.go index ad63b20..623ba3e 100644 --- a/internal/config/site_settings.go +++ b/internal/config/site_settings.go @@ -107,3 +107,26 @@ func (s *SiteSettings) IsAuditEnabled() bool { func (s *SiteSettings) IsAuditTypeEnabled(auditType string, defaultVal bool) bool { return s.GetBool("audit."+auditType, defaultVal) } + +// --- 站点信息设置 --- + +// SiteInfoDefaults 站点展示信息的默认值 +type SiteInfoDefaults struct { + Brand string // 品牌名,默认 "MetaLab" + Framework string // 框架名,默认 "MetaZone" + CopyrightStart string // 版权起始年份,默认 "2024" +} + +// SiteInfo 获取站点展示信息(优先 DB 设置,fallback 默认值) +func (s *SiteSettings) SiteInfo() SiteInfoDefaults { + return SiteInfoDefaults{ + Brand: s.Get("site.brand", "MetaLab"), + Framework: s.Get("site.framework", "MetaZone"), + CopyrightStart: s.Get("site.copyright_start", "2024"), + } +} + +// IsRegistrationEnabled 是否允许新用户注册 +func (s *SiteSettings) IsRegistrationEnabled() bool { + return s.GetBool("registration.enabled", true) +} diff --git a/internal/controller/auth_api_register.go b/internal/controller/auth_api_register.go index e961c24..b6ab990 100644 --- a/internal/controller/auth_api_register.go +++ b/internal/controller/auth_api_register.go @@ -51,6 +51,8 @@ func (ac *AuthController) Register(c *gin.Context) { common.Error(c, http.StatusConflict, "该邮箱已注册") case common.ErrWeakPassword: common.Error(c, http.StatusBadRequest, err.Error()) + case common.ErrRegistrationDisabled: + common.Error(c, http.StatusForbidden, "注册功能已关闭") default: common.Error(c, http.StatusInternalServerError, "注册失败,请稍后重试") } diff --git a/internal/controller/auth_controller.go b/internal/controller/auth_controller.go index bf3145a..fe1b7c7 100644 --- a/internal/controller/auth_controller.go +++ b/internal/controller/auth_controller.go @@ -23,7 +23,7 @@ func NewAuthController(authService authUseCase, tokenSvc tokenRefresher, limiter return &AuthController{authService: authService, tokenService: tokenSvc, rateLimiter: limiter, cfg: cfg} } -// RegisterPage 注册页面(已登录用户重定向到首页) +// RegisterPage 注册页面(已登录用户重定向到首页,注册关闭时重定向到登录页) func (ac *AuthController) RegisterPage(c *gin.Context) { if _, exists := c.Get("uid"); exists { c.Redirect(http.StatusFound, "/") diff --git a/internal/middleware/site_info.go b/internal/middleware/site_info.go new file mode 100644 index 0000000..8689480 --- /dev/null +++ b/internal/middleware/site_info.go @@ -0,0 +1,19 @@ +package middleware + +import ( + "metazone.cc/metalab/internal/config" + + "github.com/gin-gonic/gin" +) + +// InjectSiteInfo 注入站点展示信息到请求上下文(供 BuildPageData 使用) +// 应用于引擎级全局中间件,确保所有 SSR 页面都能读取 Brand/Framework/CopyrightStart +func InjectSiteInfo(siteSettings *config.SiteSettings) gin.HandlerFunc { + return func(c *gin.Context) { + info := siteSettings.SiteInfo() + c.Set("site_brand", info.Brand) + c.Set("site_framework", info.Framework) + c.Set("site_copyright_start", info.CopyrightStart) + c.Next() + } +} diff --git a/internal/router/deps_core.go b/internal/router/deps_core.go index 8ae1d4c..5fbf3c2 100644 --- a/internal/router/deps_core.go +++ b/internal/router/deps_core.go @@ -22,14 +22,14 @@ type dependencies struct { tokenCtrl *controller.AuthController } -func buildDepsCore(db *gorm.DB, cfg *config.Config) ( +func buildDepsCore(db *gorm.DB, cfg *config.Config, siteSettings *config.SiteSettings) ( *repository.UserRepo, *service.TokenService, *service.AuthService, *middleware.RateLimiter, *controller.AuthController, *service.AvatarService, *middleware.AuthMiddleware, *adminCtrl.AdminController, ) { userRepo := repository.NewUserRepo(db) tokenSvc := service.NewTokenService(cfg, userRepo) - authService := service.NewAuthService(userRepo, tokenSvc, cfg) + authService := service.NewAuthService(userRepo, tokenSvc, cfg, siteSettings) rateLimiter := middleware.NewRateLimiter() authCtrl := controller.NewAuthController(authService, tokenSvc, rateLimiter, cfg) avatarSvc := service.NewAvatarService(userRepo) diff --git a/internal/router/deps_extra.go b/internal/router/deps_extra.go index d8683e8..685abc0 100644 --- a/internal/router/deps_extra.go +++ b/internal/router/deps_extra.go @@ -11,7 +11,7 @@ import ( ) func buildDeps(db *gorm.DB, cfg *config.Config, siteSettings *config.SiteSettings) *dependencies { - userRepo, _, authService, _, authCtrl, avatarSvc, authMdw, adminController := buildDepsCore(db, cfg) + userRepo, _, authService, _, authCtrl, avatarSvc, authMdw, adminController := buildDepsCore(db, cfg, siteSettings) auditRepo := repository.NewAuditRepo(db) notificationRepo := repository.NewNotificationRepo(db) diff --git a/internal/router/router.go b/internal/router/router.go index 8c38a18..08cbd1f 100644 --- a/internal/router/router.go +++ b/internal/router/router.go @@ -11,6 +11,7 @@ import ( // Setup 注册所有路由 func Setup(r *gin.Engine, db *gorm.DB, cfg *config.Config, siteSettings *config.SiteSettings) { r.Use(middleware.SecurityHeaders()) + r.Use(middleware.InjectSiteInfo(siteSettings)) deps := buildDeps(db, cfg, siteSettings) diff --git a/internal/service/auth_service.go b/internal/service/auth_service.go index a8f989f..a1e9af9 100644 --- a/internal/service/auth_service.go +++ b/internal/service/auth_service.go @@ -18,11 +18,12 @@ type AuthService struct { userRepo userAuthStore tokenService tokenProvider cfg *config.Config + siteSettings *config.SiteSettings } // NewAuthService 构造函数 -func NewAuthService(userRepo userAuthStore, tokenSvc tokenProvider, cfg *config.Config) *AuthService { - return &AuthService{userRepo: userRepo, tokenService: tokenSvc, cfg: cfg} +func NewAuthService(userRepo userAuthStore, tokenSvc tokenProvider, cfg *config.Config, siteSettings *config.SiteSettings) *AuthService { + return &AuthService{userRepo: userRepo, tokenService: tokenSvc, cfg: cfg, siteSettings: siteSettings} } var pwLetter = regexp.MustCompile(`[a-zA-Z]`) @@ -56,6 +57,11 @@ func validatePassword(pw string) error { // rememberMe=true: refresh Cookie 持久化(30天);false: session cookie(关浏览器即清除) // regIP: 注册 IP 地址 func (s *AuthService) Register(req model.RegisterRequest, regIP string) (string, string, *model.User, error) { + // 0. 检查注册开关 + if !s.siteSettings.IsRegistrationEnabled() { + return "", "", nil, common.ErrRegistrationDisabled + } + // 1. 密码强度 if err := validatePassword(req.Password); err != nil { return "", "", nil, err diff --git a/templates/MetaLab-2026/html/layout/footer.html b/templates/MetaLab-2026/html/layout/footer.html index 2083568..8317806 100644 --- a/templates/MetaLab-2026/html/layout/footer.html +++ b/templates/MetaLab-2026/html/layout/footer.html @@ -1,5 +1,5 @@ diff --git a/templates/MetaLab-2026/html/layout/header.html b/templates/MetaLab-2026/html/layout/header.html index 81fc2d7..5be7973 100644 --- a/templates/MetaLab-2026/html/layout/header.html +++ b/templates/MetaLab-2026/html/layout/header.html @@ -4,7 +4,7 @@ {{if .CSRFToken}}{{end}} -
管理审核开关与站点全局配置
+管理站点基本配置、审核开关与注册策略
+