Files
toom1996 74ba700598 update
2026-09-07 00:01:48 +08:00

118 lines
3.5 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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"
)
// TierFree / TierVIP 用户等级枚举值。
const (
TierFree = "free"
TierVIP = "vip"
)
// IsVIP 判断等级是否为 VIP。
func IsVIP(tier string) bool { return tier == TierVIP }
// bearerPrefix Authorization 头的令牌前缀。
const bearerPrefix = "Bearer "
// Auth JWT 鉴权中间件:解析 Authorization: Bearer <token>,把身份写入 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 <token>(若存在且有效则写入 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
}
// TierFrom 从 Context 取出当前用户等级;未登录(无 tier)回落 free。
func TierFrom(c *gin.Context) string {
if v, exists := c.Get(ContextTier); exists {
if t, ok := v.(string); ok && t != "" {
return t
}
}
return TierFree
}