99 lines
3.1 KiB
Go
99 lines
3.1 KiB
Go
// 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 <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
|
||
}
|