// 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" ) // 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.Next() } // PublicFirstPageAuth 列表接口「首页公开、翻页需登录」鉴权中间件。 // // 仅当分页参数 page 缺失或 <= 1 时放行(无需 token),其余页要求有效的 // Authorization: Bearer ,否则返回 401。用于让列表首屏对游客/SEO 可见, // 同时保留翻页 / 批量枚举的登录门槛(防爬虫直接 dump 全量)。 func PublicFirstPageAuth(manager *jwt.Manager) gin.HandlerFunc { return func(c *gin.Context) { if pageStr := c.Query("page"); pageStr != "" { if n, err := strconv.Atoi(pageStr); err == nil && n > 1 { requireAuth(c, manager) // 非首页:走完整 JWT 校验 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 }