48e507783e
- 新增 SubscribePromo 响应结构,套餐列表每项追加 promo 字段 - 新增 promo.go 促销候选规则查询与资格评估逻辑 - 重构 authMiddleware 提取 authenticateRequest,新增 OptionalAuthMiddleware - /v1/public/subscribe/list 改为可选鉴权,未登录仅展示 campaign 类型促销 - /node/list、/group/list 保持强制鉴权不变 - 每个规格只返回最高优先级命中的规则,promo_price 为单价,expires_at 为秒级时间戳 - 新增单测覆盖 campaign/new_user 命中、活动窗口判定等场景 Co-authored-by: multica-agent <github@multica.ai>
126 lines
4.1 KiB
Go
126 lines
4.1 KiB
Go
package middleware
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/perfect-panel/server/pkg/constant"
|
|
|
|
"github.com/perfect-panel/server/pkg/logger"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/perfect-panel/server/internal/config"
|
|
"github.com/perfect-panel/server/internal/svc"
|
|
"github.com/perfect-panel/server/pkg/jwt"
|
|
"github.com/perfect-panel/server/pkg/result"
|
|
"github.com/perfect-panel/server/pkg/tool"
|
|
"github.com/perfect-panel/server/pkg/xerr"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
func AuthMiddleware(svc *svc.ServiceContext) func(c *gin.Context) {
|
|
return func(c *gin.Context) {
|
|
if !authenticateRequest(c, svc, c.GetHeader("Authorization"), true) {
|
|
return
|
|
}
|
|
c.Next()
|
|
}
|
|
}
|
|
|
|
func OptionalAuthMiddleware(svc *svc.ServiceContext) func(c *gin.Context) {
|
|
return func(c *gin.Context) {
|
|
token := c.GetHeader("Authorization")
|
|
if token == "" {
|
|
c.Next()
|
|
return
|
|
}
|
|
if !authenticateRequest(c, svc, token, false) {
|
|
return
|
|
}
|
|
c.Next()
|
|
}
|
|
}
|
|
|
|
func authenticateRequest(c *gin.Context, svc *svc.ServiceContext, token string, requireToken bool) bool {
|
|
ctx := c.Request.Context()
|
|
|
|
if token == "" {
|
|
if requireToken {
|
|
logger.WithContext(c.Request.Context()).Debug("[AuthMiddleware] Token Empty")
|
|
result.HttpResult(c, nil, errors.Wrapf(xerr.NewErrCode(xerr.ErrorTokenEmpty), "Token Empty"))
|
|
c.Abort()
|
|
}
|
|
return !requireToken
|
|
}
|
|
|
|
claims, err := jwt.ParseJwtToken(token, svc.Config.JwtAuth.AccessSecret)
|
|
if err != nil {
|
|
logger.WithContext(c.Request.Context()).Debug("[AuthMiddleware] ParseJwtToken", logger.Field("error", err.Error()), logger.Field("token", token))
|
|
result.HttpResult(c, nil, errors.Wrapf(xerr.NewErrCode(xerr.ErrorTokenExpire), "Token Invalid"))
|
|
c.Abort()
|
|
return false
|
|
}
|
|
|
|
loginType := parseLoginType(claims)
|
|
if claims["identifier"] != nil {
|
|
ctx = context.WithValue(ctx, constant.CtxKeyIdentifier, claims["identifier"].(string))
|
|
}
|
|
userId := int64(claims["UserId"].(float64))
|
|
sessionId := claims["SessionId"].(string)
|
|
sessionIdCacheKey := fmt.Sprintf("%v:%v", config.SessionIdKey, sessionId)
|
|
value, err := svc.Redis.Get(c, sessionIdCacheKey).Result()
|
|
if err != nil {
|
|
logger.WithContext(c.Request.Context()).Debug("[AuthMiddleware] Redis Get", logger.Field("error", err.Error()), logger.Field("sessionId", sessionId))
|
|
result.HttpResult(c, nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidAccess), "Invalid Access"))
|
|
c.Abort()
|
|
return false
|
|
}
|
|
|
|
if value != fmt.Sprintf("%v", userId) {
|
|
logger.WithContext(c.Request.Context()).Debug("[AuthMiddleware] Invalid Access", logger.Field("userId", userId), logger.Field("sessionId", sessionId))
|
|
result.HttpResult(c, nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidAccess), "Invalid Access"))
|
|
c.Abort()
|
|
return false
|
|
}
|
|
|
|
svc.Redis.Expire(c, sessionIdCacheKey, time.Duration(svc.Config.JwtAuth.AccessExpire)*time.Second)
|
|
|
|
userInfo, err := svc.UserModel.FindOne(c, userId)
|
|
if err != nil {
|
|
logger.WithContext(c.Request.Context()).Debug("[AuthMiddleware] UserModel FindOne", logger.Field("error", err.Error()), logger.Field("userId", userId))
|
|
result.HttpResult(c, nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "Database Query Error"))
|
|
c.Abort()
|
|
return false
|
|
}
|
|
|
|
paths := strings.Split(c.Request.URL.Path, "/")
|
|
if tool.StringSliceContains(paths, "admin") && !*userInfo.IsAdmin {
|
|
logger.WithContext(c.Request.Context()).Debug("[AuthMiddleware] Not Admin User", logger.Field("userId", userId), logger.Field("sessionId", sessionId))
|
|
result.HttpResult(c, nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidAccess), "Invalid Access"))
|
|
c.Abort()
|
|
return false
|
|
}
|
|
ctx = context.WithValue(ctx, constant.CtxLoginType, loginType)
|
|
ctx = context.WithValue(ctx, constant.CtxKeyUser, userInfo)
|
|
ctx = context.WithValue(ctx, constant.CtxKeySessionID, sessionId)
|
|
|
|
c.Request = c.Request.WithContext(ctx)
|
|
return true
|
|
}
|
|
|
|
func parseLoginType(claims map[string]interface{}) string {
|
|
if raw, exists := claims["CtxLoginType"]; exists {
|
|
if loginType, ok := raw.(string); ok && loginType != "" {
|
|
return loginType
|
|
}
|
|
}
|
|
if raw, exists := claims["LoginType"]; exists {
|
|
if loginType, ok := raw.(string); ok && loginType != "" {
|
|
return loginType
|
|
}
|
|
}
|
|
return ""
|
|
}
|