// Package middleware Gin 中间件集合。 package middleware import ( "net/http" "strconv" "strings" "fashionapi/internal/pkg/jwt" "fashionapi/internal/pkg/response" "github.com/gin-gonic/gin" ) // gin.Context 中存放身份信息的键。 const ( ContextUserID = "userID" ContextUsername = "username" // ContextTier 存放用户等级(free/vip)。图片质量模型已统一,当前无消费方,仅作兼容保留。 ContextTier = "tier" ) // bearerPrefix Authorization 头的令牌前缀。 const bearerPrefix = "Bearer " // Auth JWT 鉴权中间件:解析 Authorization: Bearer ,把身份写入 Context。 // // 令牌签发器由外部注入,避免像原实现那样在每个请求里重新加载一次配置。 func Auth(manager *jwt.Manager) gin.HandlerFunc { return func(c *gin.Context) { requireAuth(c, manager) } } // requireAuth 校验 Bearer token 并把身份信息写入 Context;失败则 401 中断。 func requireAuth(c *gin.Context, manager *jwt.Manager) { header := c.GetHeader("Authorization") if !strings.HasPrefix(header, bearerPrefix) { response.AbortError(c, http.StatusUnauthorized, "missing token") return } claims, err := manager.Parse(strings.TrimPrefix(header, bearerPrefix)) if err != nil { response.AbortError(c, http.StatusUnauthorized, "invalid token") return } c.Set(ContextUserID, claims.UserID) c.Set(ContextUsername, claims.Username) c.Set(ContextTier, claims.Tier) c.Next() } // PublicFirstPageAuth 列表接口「首页公开、翻页需登录」鉴权中间件。 // // 行为: // - 始终尝试解析 Authorization: Bearer (若存在且有效则写入 Context), // 供下游 handler 判断登录态(如未登录预览截断)。 // - 仅当 page>1 时才强制要求有效 token,否则返回 401;首页(page 缺失或 <= 1) // 无 token 也能放行,让游客/SEO 看到首屏。 // // 这样既让列表首屏对游客可见,又保留翻页 / 批量枚举的登录门槛(防爬虫 dump 全量)。 func PublicFirstPageAuth(manager *jwt.Manager) gin.HandlerFunc { return func(c *gin.Context) { // 解析并写入登录态(若存在有效 token),但不强制。 if header := c.GetHeader("Authorization"); strings.HasPrefix(header, bearerPrefix) { if claims, err := manager.Parse(strings.TrimPrefix(header, bearerPrefix)); err == nil { c.Set(ContextUserID, claims.UserID) c.Set(ContextUsername, claims.Username) c.Set(ContextTier, claims.Tier) } } // 非首页:必须有有效 token,否则 401。 if pageStr := c.Query("page"); pageStr != "" { if n, err := strconv.Atoi(pageStr); err == nil && n > 1 { if _, ok := c.Get(ContextUserID); !ok { response.AbortError(c, http.StatusUnauthorized, "missing or invalid token") return } } } c.Next() // 首页(page 缺失或 <= 1)放行 } } // UserIDFrom 从 Context 取出当前用户 id。 func UserIDFrom(c *gin.Context) (uint32, bool) { v, exists := c.Get(ContextUserID) if !exists { return 0, false } id, ok := v.(uint32) if !ok || id == 0 { return 0, false } return id, true }