合并 internal → main: 退款/提现/激活/CI 全面优化 #4
@@ -230,6 +230,12 @@ type (
|
||||
Discount float64 `json:"discount"`
|
||||
MapApple string `json:"map_apple"`
|
||||
}
|
||||
SubscribePromo {
|
||||
RuleName string `json:"rule_name"`
|
||||
RuleType string `json:"rule_type"`
|
||||
PromoPrice int64 `json:"promo_price"`
|
||||
ExpiresAt int64 `json:"expires_at"`
|
||||
}
|
||||
TrafficLimit {
|
||||
StatType string `json:"stat_type"`
|
||||
StatValue int64 `json:"stat_value"`
|
||||
@@ -244,6 +250,7 @@ type (
|
||||
UnitPrice int64 `json:"unit_price"`
|
||||
UnitTime string `json:"unit_time"`
|
||||
Discount []SubscribeDiscount `json:"discount"`
|
||||
Promo *SubscribePromo `json:"promo"`
|
||||
NodeCount int64 `json:"node_count"`
|
||||
Replacement int64 `json:"replacement"`
|
||||
Inventory int64 `json:"inventory"`
|
||||
|
||||
@@ -978,17 +978,16 @@ func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) {
|
||||
}
|
||||
|
||||
publicSubscribeGroupRouter := router.Group("/v1/public/subscribe")
|
||||
publicSubscribeGroupRouter.Use(middleware.AuthMiddleware(serverCtx), middleware.DeviceMiddleware(serverCtx))
|
||||
|
||||
{
|
||||
// Get subscribe list
|
||||
publicSubscribeGroupRouter.GET("/list", publicSubscribe.QuerySubscribeListHandler(serverCtx))
|
||||
publicSubscribeGroupRouter.GET("/list", middleware.OptionalAuthMiddleware(serverCtx), middleware.DeviceMiddleware(serverCtx), publicSubscribe.QuerySubscribeListHandler(serverCtx))
|
||||
|
||||
// Get user subscribe node info
|
||||
publicSubscribeGroupRouter.GET("/node/list", publicSubscribe.QueryUserSubscribeNodeListHandler(serverCtx))
|
||||
publicSubscribeGroupRouter.GET("/node/list", middleware.AuthMiddleware(serverCtx), middleware.DeviceMiddleware(serverCtx), publicSubscribe.QueryUserSubscribeNodeListHandler(serverCtx))
|
||||
|
||||
// Get subscribe group list
|
||||
publicSubscribeGroupRouter.GET("/group/list", publicSubscribe.QuerySubscribeGroupListHandler(serverCtx))
|
||||
publicSubscribeGroupRouter.GET("/group/list", middleware.AuthMiddleware(serverCtx), middleware.DeviceMiddleware(serverCtx), publicSubscribe.QuerySubscribeGroupListHandler(serverCtx))
|
||||
}
|
||||
|
||||
publicTicketGroupRouter := router.Group("/v1/public/ticket")
|
||||
|
||||
@@ -0,0 +1,224 @@
|
||||
package subscribe
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
stderrors "errors"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-sql-driver/mysql"
|
||||
"github.com/perfect-panel/server/internal/model/user"
|
||||
"github.com/perfect-panel/server/internal/svc"
|
||||
"github.com/perfect-panel/server/internal/types"
|
||||
"github.com/perfect-panel/server/pkg/constant"
|
||||
"github.com/perfect-panel/server/pkg/xerr"
|
||||
"github.com/pkg/errors"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
const (
|
||||
promoRuleTypeNewUser = "new_user"
|
||||
promoRuleTypeInactiveUser = "inactive_user"
|
||||
promoRuleTypeCampaign = "campaign"
|
||||
)
|
||||
|
||||
type subscribePromoCandidate struct {
|
||||
SubscribeId int64 `gorm:"column:subscribe_id"`
|
||||
RuleName string `gorm:"column:rule_name"`
|
||||
RuleType string `gorm:"column:rule_type"`
|
||||
PromoPrice int64 `gorm:"column:promo_price"`
|
||||
Params string `gorm:"column:params"`
|
||||
StartTime *time.Time `gorm:"column:start_time"`
|
||||
EndTime *time.Time `gorm:"column:end_time"`
|
||||
}
|
||||
|
||||
type promoRuleParams struct {
|
||||
WindowHours int64 `json:"window_hours"`
|
||||
InactiveMonths int `json:"inactive_months"`
|
||||
}
|
||||
|
||||
func loadSubscribePromoMap(ctx context.Context, svcCtx *svc.ServiceContext, subscribeIDs []int64) (map[int64]*types.SubscribePromo, error) {
|
||||
result := make(map[int64]*types.SubscribePromo)
|
||||
if len(subscribeIDs) == 0 || svcCtx == nil || svcCtx.DB == nil {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
userInfo, _ := ctx.Value(constant.CtxKeyUser).(*user.User)
|
||||
candidates, err := querySubscribePromoCandidates(ctx, svcCtx, subscribeIDs, userInfo != nil)
|
||||
if err != nil {
|
||||
if isMissingPromoTableError(err) {
|
||||
return result, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
evaluator := promoEligibilityEvaluator{ctx: ctx, db: svcCtx.DB, userInfo: userInfo}
|
||||
now := time.Now()
|
||||
for _, candidate := range candidates {
|
||||
if _, exists := result[candidate.SubscribeId]; exists {
|
||||
continue
|
||||
}
|
||||
if !candidate.isActive(now) {
|
||||
continue
|
||||
}
|
||||
ok, expiresAt, err := evaluator.match(candidate, now)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
result[candidate.SubscribeId] = &types.SubscribePromo{
|
||||
RuleName: candidate.RuleName,
|
||||
RuleType: candidate.RuleType,
|
||||
PromoPrice: candidate.PromoPrice,
|
||||
ExpiresAt: unixSeconds(expiresAt),
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func querySubscribePromoCandidates(ctx context.Context, svcCtx *svc.ServiceContext, subscribeIDs []int64, loggedIn bool) ([]subscribePromoCandidate, error) {
|
||||
var candidates []subscribePromoCandidate
|
||||
query := svcCtx.DB.WithContext(ctx).
|
||||
Table("subscribe_promo AS sp").
|
||||
Select("sp.subscribe_id, sp.promo_price, pr.name AS rule_name, pr.type AS rule_type, pr.params, pr.start_time, pr.end_time").
|
||||
Joins("JOIN promo_rule AS pr ON pr.id = sp.promo_rule_id AND pr.deleted_at IS NULL").
|
||||
Where("sp.subscribe_id IN ? AND sp.promo_price > 0 AND pr.enabled = ?", subscribeIDs, true)
|
||||
if !loggedIn {
|
||||
query = query.Where("pr.type = ?", promoRuleTypeCampaign)
|
||||
}
|
||||
err := query.
|
||||
Order("sp.subscribe_id ASC").
|
||||
Order("pr.priority DESC").
|
||||
Order("pr.id ASC").
|
||||
Scan(&candidates).Error
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "query subscribe promo candidates failed: %v", err)
|
||||
}
|
||||
return candidates, nil
|
||||
}
|
||||
|
||||
func (c subscribePromoCandidate) isActive(now time.Time) bool {
|
||||
if c.PromoPrice <= 0 {
|
||||
return false
|
||||
}
|
||||
if c.StartTime != nil && now.Before(*c.StartTime) {
|
||||
return false
|
||||
}
|
||||
if c.EndTime != nil && now.After(*c.EndTime) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
type promoEligibilityEvaluator struct {
|
||||
ctx context.Context
|
||||
db *gorm.DB
|
||||
userInfo *user.User
|
||||
lastExpire *time.Time
|
||||
}
|
||||
|
||||
func (e *promoEligibilityEvaluator) match(candidate subscribePromoCandidate, now time.Time) (bool, time.Time, error) {
|
||||
switch candidate.RuleType {
|
||||
case promoRuleTypeCampaign:
|
||||
return true, candidate.expiresAt(), nil
|
||||
case promoRuleTypeNewUser:
|
||||
if e.userInfo == nil {
|
||||
return false, time.Time{}, nil
|
||||
}
|
||||
params, err := candidate.params()
|
||||
if err != nil {
|
||||
return false, time.Time{}, err
|
||||
}
|
||||
if params.WindowHours <= 0 || e.userInfo.CreatedAt.IsZero() {
|
||||
return false, time.Time{}, nil
|
||||
}
|
||||
expiresAt := e.userInfo.CreatedAt.Add(time.Duration(params.WindowHours) * time.Hour)
|
||||
return now.Before(expiresAt), expiresAt, nil
|
||||
case promoRuleTypeInactiveUser:
|
||||
if e.userInfo == nil {
|
||||
return false, time.Time{}, nil
|
||||
}
|
||||
params, err := candidate.params()
|
||||
if err != nil {
|
||||
return false, time.Time{}, err
|
||||
}
|
||||
if params.InactiveMonths <= 0 {
|
||||
return false, time.Time{}, nil
|
||||
}
|
||||
lastExpire, err := e.lastSubscribeExpireAt()
|
||||
if err != nil {
|
||||
return false, time.Time{}, err
|
||||
}
|
||||
if lastExpire.Equal(time.UnixMilli(0)) || lastExpire.After(now) {
|
||||
return false, time.Time{}, nil
|
||||
}
|
||||
if lastExpire.IsZero() {
|
||||
return true, candidate.expiresAt(), nil
|
||||
}
|
||||
threshold := now.AddDate(0, -params.InactiveMonths, 0)
|
||||
return !lastExpire.After(threshold), candidate.expiresAt(), nil
|
||||
default:
|
||||
return false, time.Time{}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (e *promoEligibilityEvaluator) lastSubscribeExpireAt() (time.Time, error) {
|
||||
if e.lastExpire != nil {
|
||||
return *e.lastExpire, nil
|
||||
}
|
||||
var item user.Subscribe
|
||||
err := e.db.WithContext(e.ctx).
|
||||
Model(&user.Subscribe{}).
|
||||
Where("user_id = ?", e.userInfo.Id).
|
||||
Where("expire_time != ?", time.UnixMilli(0)).
|
||||
Order("expire_time DESC").
|
||||
Limit(1).
|
||||
Take(&item).Error
|
||||
if err != nil {
|
||||
if stderrors.Is(err, gorm.ErrRecordNotFound) {
|
||||
zero := time.Time{}
|
||||
e.lastExpire = &zero
|
||||
return zero, nil
|
||||
}
|
||||
return time.Time{}, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "query promo user last subscription failed")
|
||||
}
|
||||
e.lastExpire = &item.ExpireTime
|
||||
return item.ExpireTime, nil
|
||||
}
|
||||
|
||||
func (c subscribePromoCandidate) expiresAt() time.Time {
|
||||
if c.EndTime == nil {
|
||||
return time.Time{}
|
||||
}
|
||||
return *c.EndTime
|
||||
}
|
||||
|
||||
func (c subscribePromoCandidate) params() (promoRuleParams, error) {
|
||||
if c.Params == "" {
|
||||
return promoRuleParams{}, nil
|
||||
}
|
||||
var params promoRuleParams
|
||||
if err := json.Unmarshal([]byte(c.Params), ¶ms); err != nil {
|
||||
return promoRuleParams{}, errors.Wrapf(xerr.NewErrCode(xerr.InvalidParams), "parse promo rule params failed")
|
||||
}
|
||||
return params, nil
|
||||
}
|
||||
|
||||
func unixSeconds(t time.Time) int64 {
|
||||
if t.IsZero() {
|
||||
return 0
|
||||
}
|
||||
return t.Unix()
|
||||
}
|
||||
|
||||
func isMissingPromoTableError(err error) bool {
|
||||
var mysqlErr *mysql.MySQLError
|
||||
if stderrors.As(err, &mysqlErr) {
|
||||
return mysqlErr.Number == 1146
|
||||
}
|
||||
return strings.Contains(err.Error(), "Error 1146")
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package subscribe
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/perfect-panel/server/internal/model/user"
|
||||
)
|
||||
|
||||
func TestPromoEligibilityEvaluatorMatch(t *testing.T) {
|
||||
now := time.Unix(1710000000, 0)
|
||||
campaignEnd := now.Add(2 * time.Hour)
|
||||
|
||||
campaign := subscribePromoCandidate{
|
||||
RuleName: "限时活动",
|
||||
RuleType: promoRuleTypeCampaign,
|
||||
PromoPrice: 99,
|
||||
EndTime: &campaignEnd,
|
||||
}
|
||||
ok, expiresAt, err := (&promoEligibilityEvaluator{}).match(campaign, now)
|
||||
if err != nil {
|
||||
t.Fatalf("campaign match error: %v", err)
|
||||
}
|
||||
if !ok {
|
||||
t.Fatal("campaign promo should match without login")
|
||||
}
|
||||
if got, want := unixSeconds(expiresAt), campaignEnd.Unix(); got != want {
|
||||
t.Fatalf("campaign expires_at = %d, want %d", got, want)
|
||||
}
|
||||
|
||||
newUser := subscribePromoCandidate{
|
||||
RuleName: "新客7天优惠",
|
||||
RuleType: promoRuleTypeNewUser,
|
||||
PromoPrice: 279,
|
||||
Params: `{"window_hours":168}`,
|
||||
}
|
||||
ok, _, err = (&promoEligibilityEvaluator{}).match(newUser, now)
|
||||
if err != nil {
|
||||
t.Fatalf("anonymous new_user match error: %v", err)
|
||||
}
|
||||
if ok {
|
||||
t.Fatal("new_user promo should not match without login")
|
||||
}
|
||||
|
||||
userInfo := &user.User{Id: 1, CreatedAt: now.Add(-24 * time.Hour)}
|
||||
ok, expiresAt, err = (&promoEligibilityEvaluator{userInfo: userInfo}).match(newUser, now)
|
||||
if err != nil {
|
||||
t.Fatalf("logged-in new_user match error: %v", err)
|
||||
}
|
||||
if !ok {
|
||||
t.Fatal("new_user promo should match inside window")
|
||||
}
|
||||
if got, want := unixSeconds(expiresAt), userInfo.CreatedAt.Add(168*time.Hour).Unix(); got != want {
|
||||
t.Fatalf("new_user expires_at = %d, want %d", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSubscribePromoCandidateActiveWindow(t *testing.T) {
|
||||
now := time.Unix(1710000000, 0)
|
||||
start := now.Add(-time.Hour)
|
||||
end := now.Add(time.Hour)
|
||||
|
||||
if !(subscribePromoCandidate{PromoPrice: 1, StartTime: &start, EndTime: &end}).isActive(now) {
|
||||
t.Fatal("candidate inside active window should be active")
|
||||
}
|
||||
if (subscribePromoCandidate{PromoPrice: 0, StartTime: &start, EndTime: &end}).isActive(now) {
|
||||
t.Fatal("candidate with zero promo price should not be active")
|
||||
}
|
||||
if (subscribePromoCandidate{PromoPrice: 1, StartTime: &end}).isActive(now) {
|
||||
t.Fatal("candidate before start time should not be active")
|
||||
}
|
||||
if (subscribePromoCandidate{PromoPrice: 1, EndTime: &start}).isActive(now) {
|
||||
t.Fatal("candidate after end time should not be active")
|
||||
}
|
||||
}
|
||||
@@ -47,9 +47,11 @@ func (l *QuerySubscribeListLogic) QuerySubscribeList(req *types.QuerySubscribeLi
|
||||
Total: total,
|
||||
}
|
||||
list := make([]types.Subscribe, len(data))
|
||||
subscribeIDs := make([]int64, 0, len(data))
|
||||
for i, item := range data {
|
||||
var sub types.Subscribe
|
||||
tool.DeepCopy(&sub, item)
|
||||
subscribeIDs = append(subscribeIDs, sub.Id)
|
||||
if item.Discount != "" {
|
||||
var discount []types.SubscribeDiscount
|
||||
_ = json.Unmarshal([]byte(item.Discount), &discount)
|
||||
@@ -69,6 +71,15 @@ func (l *QuerySubscribeListLogic) QuerySubscribeList(req *types.QuerySubscribeLi
|
||||
}
|
||||
}
|
||||
|
||||
promos, err := loadSubscribePromoMap(l.ctx, l.svcCtx, subscribeIDs)
|
||||
if err != nil {
|
||||
l.Errorw("[QuerySubscribeListLogic] Query Promo Error", logger.Field("error", err.Error()))
|
||||
return nil, err
|
||||
}
|
||||
for i := range list {
|
||||
list[i].Promo = promos[list[i].Id]
|
||||
}
|
||||
|
||||
resp.List = list
|
||||
resp.Total = int64(len(list))
|
||||
return
|
||||
|
||||
@@ -22,77 +22,92 @@ import (
|
||||
|
||||
func AuthMiddleware(svc *svc.ServiceContext) func(c *gin.Context) {
|
||||
return func(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
if !authenticateRequest(c, svc, c.GetHeader("Authorization"), true) {
|
||||
return
|
||||
}
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
jwtConfig := svc.Config.JwtAuth
|
||||
// get token from header
|
||||
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
|
||||
}
|
||||
// parse token
|
||||
claims, err := jwt.ParseJwtToken(token, jwtConfig.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
|
||||
}
|
||||
|
||||
loginType := parseLoginType(claims)
|
||||
if claims["identifier"] != nil {
|
||||
ctx = context.WithValue(ctx, constant.CtxKeyIdentifier, claims["identifier"].(string))
|
||||
}
|
||||
// get user id from token
|
||||
userId := int64(claims["UserId"].(float64))
|
||||
// get session id from token
|
||||
sessionId := claims["SessionId"].(string)
|
||||
// get session id from redis
|
||||
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
|
||||
}
|
||||
|
||||
//verify user id
|
||||
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
|
||||
}
|
||||
|
||||
// sliding session: refresh TTL on every active request
|
||||
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
|
||||
}
|
||||
// admin verify
|
||||
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
|
||||
}
|
||||
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)
|
||||
c.Next()
|
||||
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 {
|
||||
|
||||
@@ -2785,6 +2785,13 @@ type StripePayment struct {
|
||||
PublishableKey string `json:"publishable_key"`
|
||||
}
|
||||
|
||||
type SubscribePromo struct {
|
||||
RuleName string `json:"rule_name"`
|
||||
RuleType string `json:"rule_type"`
|
||||
PromoPrice int64 `json:"promo_price"`
|
||||
ExpiresAt int64 `json:"expires_at"`
|
||||
}
|
||||
|
||||
type Subscribe struct {
|
||||
Id int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
@@ -2793,6 +2800,7 @@ type Subscribe struct {
|
||||
UnitPrice int64 `json:"unit_price"`
|
||||
UnitTime string `json:"unit_time"`
|
||||
Discount []SubscribeDiscount `json:"discount"`
|
||||
Promo *SubscribePromo `json:"promo"`
|
||||
NodeCount int64 `json:"node_count"`
|
||||
Replacement int64 `json:"replacement"`
|
||||
Inventory int64 `json:"inventory"`
|
||||
|
||||
Reference in New Issue
Block a user