feat: 单订阅模式下赠送订阅自动合并,保持token不变
Some checks failed
Build docker and publish / build (20.15.1) (push) Failing after 7m38s

- purchaseLogic: 允许只有赠送订阅(order_id=0)的用户在SingleModel下新购
- activateOrderLogic: NewPurchase激活时检测赠送订阅,在原有订阅上延长到期时间而非创建新记录
This commit is contained in:
shanshanzhong 2026-02-24 21:01:36 -08:00
parent a81f28f304
commit 9638cc11fa
3 changed files with 133 additions and 5 deletions

View File

@ -66,7 +66,15 @@ func (l *PurchaseLogic) Purchase(req *types.PurchaseOrderRequest) (resp *types.P
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "find user subscription error: %v", err.Error()) return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "find user subscription error: %v", err.Error())
} }
if l.svcCtx.Config.Subscribe.SingleModel { if l.svcCtx.Config.Subscribe.SingleModel {
if len(userSub) > 0 { // 检查是否有非赠送的活跃订阅order_id > 0 表示付费订阅)
hasPaidSubscription := false
for _, s := range userSub {
if s.OrderId > 0 {
hasPaidSubscription = true
break
}
}
if hasPaidSubscription {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.UserSubscribeExist), "user has subscription") return nil, errors.Wrapf(xerr.NewErrCode(xerr.UserSubscribeExist), "user has subscription")
} }
} }
@ -103,7 +111,7 @@ func (l *PurchaseLogic) Purchase(req *types.PurchaseOrderRequest) (resp *types.P
} }
price := sub.UnitPrice * req.Quantity price := sub.UnitPrice * req.Quantity
// discount amount // discount amount
amount := int64(math.Round(float64(price) * discount)) amount := int64(math.Round(float64(price) * discount))
discountAmount := price - amount discountAmount := price - amount
var coupon int64 = 0 var coupon int64 = 0
// Calculate the coupon deduction // Calculate the coupon deduction

View File

@ -206,9 +206,31 @@ func (l *ActivateOrderLogic) NewPurchase(ctx context.Context, orderInfo *order.O
return err return err
} }
userSub, err := l.createUserSubscription(ctx, orderInfo, sub) var userSub *user.Subscribe
if err != nil {
return err // 单订阅模式下检查用户是否已有赠送订阅order_id=0
if l.svc.Config.Subscribe.SingleModel {
giftSub, err := l.findGiftSubscription(ctx, orderInfo.UserId, orderInfo.SubscribeId)
if err == nil && giftSub != nil {
// 在赠送订阅上延长时间,保持 token 不变
userSub, err = l.extendGiftSubscription(ctx, giftSub, orderInfo, sub)
if err != nil {
logger.WithContext(ctx).Error("Extend gift subscription failed",
logger.Field("error", err.Error()),
logger.Field("gift_subscribe_id", giftSub.Id),
)
// 合并失败时回退到创建新订阅
userSub = nil
}
}
}
// 如果没有合并赠送订阅,则正常创建新订阅
if userSub == nil {
userSub, err = l.createUserSubscription(ctx, orderInfo, sub)
if err != nil {
return err
}
} }
// Handle commission in separate goroutine to avoid blocking // Handle commission in separate goroutine to avoid blocking
@ -382,6 +404,63 @@ func (l *ActivateOrderLogic) createUserSubscription(ctx context.Context, orderIn
return userSub, nil return userSub, nil
} }
// findGiftSubscription 查找用户指定套餐的赠送订阅order_id=0 且状态活跃)
// 返回找到的赠送订阅记录,如果没有则返回 nil
func (l *ActivateOrderLogic) findGiftSubscription(ctx context.Context, userId int64, subscribeId int64) (*user.Subscribe, error) {
// 查询用户所有活跃订阅
subs, err := l.svc.UserModel.QueryUserSubscribe(ctx, userId, 0, 1)
if err != nil {
return nil, err
}
// 查找 order_id=0赠送且同套餐的订阅
for _, s := range subs {
if s.OrderId == 0 && s.SubscribeId == subscribeId {
// 通过 ID 获取完整的 Subscribe 记录
sub, err := l.svc.UserModel.FindOneSubscribe(ctx, s.Id)
if err != nil {
return nil, err
}
return sub, nil
}
}
return nil, nil
}
// extendGiftSubscription 在现有赠送订阅上延长到期时间,保持 token 不变
// 将购买的天数叠加到赠送订阅的到期时间上,并更新 order_id 为新订单 ID
func (l *ActivateOrderLogic) extendGiftSubscription(ctx context.Context, giftSub *user.Subscribe, orderInfo *order.Order, sub *subscribe.Subscribe) (*user.Subscribe, error) {
now := time.Now()
// 计算基准时间:取赠送订阅到期时间和当前时间的较大值
baseTime := giftSub.ExpireTime
if baseTime.Before(now) {
baseTime = now
}
// 在基准时间上增加购买的天数
newExpireTime := tool.AddTime(sub.UnitTime, orderInfo.Quantity, baseTime)
// 更新赠送订阅的信息
giftSub.OrderId = orderInfo.Id
giftSub.ExpireTime = newExpireTime
giftSub.Status = 1
if err := l.svc.UserModel.UpdateSubscribe(ctx, giftSub); err != nil {
logger.WithContext(ctx).Error("Update gift subscription failed",
logger.Field("error", err.Error()),
logger.Field("subscribe_id", giftSub.Id),
)
return nil, err
}
logger.WithContext(ctx).Info("Extended gift subscription successfully",
logger.Field("subscribe_id", giftSub.Id),
logger.Field("old_expire_time", baseTime),
logger.Field("new_expire_time", newExpireTime),
logger.Field("order_id", orderInfo.Id),
)
return giftSub, nil
}
// handleCommission processes referral commission for the referrer if applicable. // handleCommission processes referral commission for the referrer if applicable.
// This runs asynchronously to avoid blocking the main order processing flow. // This runs asynchronously to avoid blocking the main order processing flow.
func (l *ActivateOrderLogic) handleCommission(ctx context.Context, userInfo *user.User, orderInfo *order.Order) { func (l *ActivateOrderLogic) handleCommission(ctx context.Context, userInfo *user.User, orderInfo *order.Order) {

41
scripts/version-history.sh Executable file
View File

@ -0,0 +1,41 @@
#!/bin/bash
# 版本历史查询工具
# 用法: ./scripts/version-history.sh [数量]
set -e
COUNT=${1:-20}
BRANCH=${2:-dev}
echo "========================================"
echo "📦 PPanel Server 版本历史"
echo "========================================"
echo ""
echo "🔹 分支: $BRANCH"
echo "🔹 显示最近 $COUNT 个版本"
echo ""
echo "----------------------------------------"
printf "%-10s | %-16s | %-50s\n" "版本SHA" "提交时间" "提交信息"
echo "----------------------------------------"
git log --oneline --date=format:'%Y-%m-%d %H:%M' \
--pretty=format:'%h | %ad | %s' \
-$COUNT $BRANCH
echo ""
echo "----------------------------------------"
echo ""
echo "💡 回滚方法:"
echo ""
echo "1⃣ SSH 到服务器修改 .env 文件:"
echo " cd /root/bindbox"
echo " echo 'PPANEL_SERVER_TAG=<版本SHA>' > .env"
echo " docker-compose -f docker-compose.cloud.yml pull ppanel-server"
echo " docker-compose -f docker-compose.cloud.yml up -d ppanel-server"
echo ""
echo "2⃣ 或者本地 Git 回滚后重新推送:"
echo " git reset --hard <版本SHA>"
echo " git push -f origin $BRANCH"
echo ""
echo "========================================"