新功能(#77): 套餐列表 API 返回规格级促销信息
Build docker and publish / build (20.15.1) (push) Failing after 8m23s
Build docker and publish / build (20.15.1) (pull_request) Successful in 7m54s

将 promo 字段从 Subscribe 顶层移至 SubscribeDiscount(discount[] 项内),
促销查询按 subscribe_id + quantity 维度写入,每个规格独立命中最高优先级规则。

Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
2026-05-27 02:19:58 -07:00
parent c90edac630
commit d12c340743
4 changed files with 31 additions and 21 deletions
+10 -5
View File
@@ -25,6 +25,7 @@ const (
type subscribePromoCandidate struct {
SubscribeId int64 `gorm:"column:subscribe_id"`
Quantity int64 `gorm:"column:quantity"`
RuleName string `gorm:"column:rule_name"`
RuleType string `gorm:"column:rule_type"`
PromoPrice int64 `gorm:"column:promo_price"`
@@ -38,8 +39,8 @@ type promoRuleParams struct {
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)
func loadSubscribePromoMap(ctx context.Context, svcCtx *svc.ServiceContext, subscribeIDs []int64) (map[int64]map[int64]*types.SubscribePromo, error) {
result := make(map[int64]map[int64]*types.SubscribePromo)
if len(subscribeIDs) == 0 || svcCtx == nil || svcCtx.DB == nil {
return result, nil
}
@@ -56,7 +57,7 @@ func loadSubscribePromoMap(ctx context.Context, svcCtx *svc.ServiceContext, subs
evaluator := promoEligibilityEvaluator{ctx: ctx, db: svcCtx.DB, userInfo: userInfo}
now := time.Now()
for _, candidate := range candidates {
if _, exists := result[candidate.SubscribeId]; exists {
if _, exists := result[candidate.SubscribeId][candidate.Quantity]; exists {
continue
}
if !candidate.isActive(now) {
@@ -69,7 +70,10 @@ func loadSubscribePromoMap(ctx context.Context, svcCtx *svc.ServiceContext, subs
if !ok {
continue
}
result[candidate.SubscribeId] = &types.SubscribePromo{
if _, exists := result[candidate.SubscribeId]; !exists {
result[candidate.SubscribeId] = make(map[int64]*types.SubscribePromo)
}
result[candidate.SubscribeId][candidate.Quantity] = &types.SubscribePromo{
RuleName: candidate.RuleName,
RuleType: candidate.RuleType,
PromoPrice: candidate.PromoPrice,
@@ -84,7 +88,7 @@ func querySubscribePromoCandidates(ctx context.Context, svcCtx *svc.ServiceConte
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").
Select("sp.subscribe_id, sp.quantity, 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 {
@@ -92,6 +96,7 @@ func querySubscribePromoCandidates(ctx context.Context, svcCtx *svc.ServiceConte
}
err := query.
Order("sp.subscribe_id ASC").
Order("sp.quantity ASC").
Order("pr.priority DESC").
Order("pr.id ASC").
Scan(&candidates).Error
@@ -56,11 +56,19 @@ func (l *QuerySubscribeListLogic) QuerySubscribeList(req *types.QuerySubscribeLi
var discount []types.SubscribeDiscount
_ = json.Unmarshal([]byte(item.Discount), &discount)
sub.Discount = discount
list[i] = sub
}
list[i] = sub
}
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 {
applySubscribeDiscountPromos(&list[i], promos[list[i].Id])
}
// 老版本客户端(无 X-App-Id)去掉每个套餐 discount 的最后一个
hasAppId, _ := l.ctx.Value(constant.CtxKeyHasAppId).(bool)
if !hasAppId {
@@ -71,16 +79,13 @@ 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
}
func applySubscribeDiscountPromos(subscribe *types.Subscribe, promoByQuantity map[int64]*types.SubscribePromo) {
for i := range subscribe.Discount {
subscribe.Discount[i].Promo = promoByQuantity[subscribe.Discount[i].Quantity]
}
}
+5 -5
View File
@@ -2789,7 +2789,6 @@ 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"`
@@ -2850,10 +2849,11 @@ type SubscribeConfig struct {
}
type SubscribeDiscount struct {
Quantity int64 `json:"quantity"`
Discount float64 `json:"discount"`
NewUserOnly bool `json:"new_user_only"`
MapApple string `json:"map_apple"`
Quantity int64 `json:"quantity"`
Discount float64 `json:"discount"`
NewUserOnly bool `json:"new_user_only"`
MapApple string `json:"map_apple"`
Promo *SubscribePromo `json:"promo"`
}
type SubscribeGroup struct {