营销与优惠模块.md 16 KB

营销与优惠模块业务说明

本文档描述营销活动与优惠券体系,涵盖活动类型、优惠券生命周期、优惠计算逻辑及叠加规则。 帮助开发人员快速理解"为什么这样设计"以及各环节的特殊处理方式。


一、营销活动体系

1.1 三套活动体系

体系 核心实体 管理入口 用途
营销活动 MarketingActivity MarketingActivityController 首单立减/折扣/满减
定时折扣 TimedDiscountActivity TimedDiscountController 按时间段自动执行的折扣
邀请有礼 InviteActivity InviteActivityAdminController 邀请奖励活动

统一活动管理UnifiedActivityService / UnifiedActivityController 提供三套活动的统一查询和管理入口。

为什么有三套活动体系而非统一一套?

三套体系虽同属"营销",但业务差异很大,强行合并会增加复杂度:

  • 营销活动(MarketingActivity):面向设备端计算的即时优惠,有时间范围、预算控制,由运营人员配置
  • 定时折扣(TimedDiscountActivity):按每天时段自动执行(如夜场22:00-06:00打折),有库存关联的阶梯折扣逻辑,可自动执行
  • 邀请有礼(InviteActivity):奖励机制完全不同(发放优惠券给邀请人/被邀请人),有每日限额、首单门槛等专属逻辑

UnifiedActivityService 在查询层面做统一聚合,但各体系保持独立的 CRUD 和业务逻辑。

1.2 MarketingActivity 实体

字段 类型 说明
id Long 雪花算法主键
activityName String 活动名称
activityType Integer 活动类型:1-首单立减 2-优惠折扣 3-商品满减 4-邀请有礼
activityDesc String 活动描述
startTime / endTime LocalDateTime 活动有效期
status Integer 状态:0-草稿 1-已发布 2-进行中 3-已暂停 4-已结束
discountValue BigDecimal 优惠值(立减金额/折扣率/减免金额)
minAmount BigDecimal 最低消费金额(满减门槛)
maxDiscount BigDecimal 最大优惠金额(折扣活动上限)
applyScope Integer 适用范围:1-全部门店 2-指定门店
deviceScope Integer 设备范围:1-全部设备 2-指定设备
productScope Integer 商品范围:1-全部商品 2-指定商品
totalBudget / usedBudget BigDecimal 预算控制
deleted Integer 逻辑删除

1.3 活动类型与计算规则

类型1:首单立减 (FIRST_ORDER_DISCOUNT)

条件:用户之前无已完成订单(isFirstOrder 判断)
优惠 = min(discountValue, orderAmount)
结果:可能免单(优惠 >= 订单金额)

类型2:优惠折扣 (DISCOUNT)

折扣率 discountValue:0 < discountValue <= 1(如0.8表示8折)
优惠 = orderAmount × (1 - discountValue),保留2位小数
如有 maxDiscount:优惠 = min(计算优惠, maxDiscount)

类型3:商品满减 (FULL_REDUCTION)

门槛:orderAmount >= minAmount
满足:优惠 = discountValue(固定减免金额)
不满足:优惠 = 0

1.4 活动状态流转 (ActivityStatusEnum)

DRAFT(0, 草稿)
    ↓ publish
PUBLISHED(1, 已发布) → 到达开始时间自动 → ONGOING(2, 进行中)
    ↓ pause                              ↓ pause
PAUSED(3, 已暂停) → resume → ONGOING    ↓ 到达结束时间
    ↓                                    ↓
                                       ENDED(4, 已结束)

状态操作权限:

  • 可编辑:仅 DRAFT
  • 可发布:仅 DRAFT
  • 可暂停:仅 ONGOING
  • 可恢复:仅 PAUSED
  • 可删除:DRAFT 或 ENDED

1.5 关联关系表

关联表 说明
t_activity_shop 活动-门店关联(applyScope=2时)
t_activity_device 活动-设备关联(deviceScope=2时)
t_activity_product 活动-商品关联(productScope=2时)

营销活动与优惠券的关系设计

关系模型:一个营销活动可以关联多个优惠券模板(1:N),优惠券模板的 activityId 字段指向所属活动,但优惠券也可以独立存在(activityId 为空)。

设计考量

  • 活动和优惠券解耦,优惠券可独立发放(如日常运营券),也可归属某个活动(如春节大促券)
  • 活动详情页可展示关联的优惠券列表,便于运营人员统一管理
  • activityId 为可选字段,不强制绑定

二、定时折扣体系

2.1 TimedDiscountActivity 实体

字段 类型 说明
id Long 雪花算法主键
activityName String 活动名称
startTime / endTime LocalTime 折扣时段(精确到时分秒,如 22:00:00 - 06:00:00)
discountType Integer 折扣类型:1-固定折扣 2-阶梯折扣 3-固定价格
discountValue BigDecimal 折扣值
minDiscountPrice BigDecimal 最低折扣价(底线价格)
applyScope / deviceScope / productScope Integer 适用范围
productTypes String 商品分类(逗号分隔)
priorityProductTypes String 优先折扣商品分类
minStock / maxStock Integer 库存范围筛选
status Integer 状态
autoExecute Integer 是否自动执行
notifyOnExecute Integer 执行时是否通知

2.2 折扣类型 (DiscountTypeEnum)

code 枚举值 说明
1 FIXED_DISCOUNT 固定折扣(如统一8折)
2 TIERED_DISCOUNT 阶梯折扣(库存越少折扣越大)
3 FIXED_PRICE 固定价格(如夜场统一3元)

定时折扣的业务场景

定时折扣主要解决临期/夜间清库存问题:

  • 夜场折扣:22:00-06:00 折扣销售,提高夜间销量
  • 临期清仓:库存低于阈值时自动加大折扣,减少损耗
  • 固定价格:特定时段统一定价(如夜场饮品一律3元),简化计价

阶梯折扣是定时折扣独有的机制:根据当前库存量动态调整折扣力度(库存越少折扣越大),由 minStock / maxStock 字段定义库存区间。

2.3 关联表

关联表 说明
t_timed_discount_device 折扣-设备关联
t_timed_discount_shop 折扣-门店关联
t_timed_discount_product 折扣-商品关联
t_timed_discount_record 折扣执行记录
t_timed_discount_statistics 折扣统计

三、优惠券体系

3.1 CouponTemplate 实体(优惠券模板)

字段 类型 说明
id Long 雪花算法主键
couponName String 优惠券名称
couponType Integer 类型:1-满减券 2-折扣券 3-立减券 4-商品券
couponDesc String 描述
discountValue BigDecimal 优惠值(金额/折扣率)
minAmount BigDecimal 最低消费金额(满减门槛)
maxDiscount BigDecimal 最大优惠金额(折扣券上限)
totalCount Integer 发放总量
remainCount Integer 剩余库存
receiveLimit Integer 每人限领数量
validType Integer 有效期类型:1-固定时间 2-相对时间
validStartTime / validEndTime LocalDateTime 固定有效期
validDays Integer 相对有效天数(领取后N天有效)
applyScope Integer 适用范围:1-全部门店 2-指定门店
productScope Integer 商品范围:1-全部商品 2-指定商品
activityId Long 关联营销活动ID(可选)
status Integer 状态:0-禁用 1-启用
receiveType String 发放类型:Collect-主动领取,Release-系统发放

3.2 优惠券类型与计算 (CouponTypeEnum)

couponType 名称 discountValue含义 计算方式
1 满减券 减免金额(元) 订单金额 >= minAmount → 优惠 = discountValue
2 折扣券 折扣率(如0.8=8折) 优惠 = orderAmount × (1 - discountValue/10),有 maxDiscount 上限
3 立减券 抵扣金额(元) 优惠 = discountValue(无门槛)
4 商品券 无意义 免费兑换指定商品,不用于金额扣减

3.3 UserCoupon 实体(用户优惠券)

字段 类型 说明
id Long 雪花算法主键
couponCode String 优惠券编码(唯一)
templateId Long 优惠券模板ID
userId Long 用户ID
orderId Long 使用订单ID(使用后写入)
status Integer 状态:0-未使用 1-已使用 2-已过期
receiveTime LocalDateTime 领取时间
useTime LocalDateTime 使用时间
validStartTime / validEndTime LocalDateTime 有效期
discountAmount BigDecimal 实际优惠金额(使用时记录)

3.4 优惠券状态 (CouponStatusEnum)

code 枚举值 说明
0 UNUSED 未使用
1 USED 已使用
2 EXPIRED 已过期

3.5 关联表

关联表 说明
t_coupon_shop 优惠券-门店关联
t_coupon_product 优惠券-商品关联
t_coupon_distribute 优惠券发放记录

优惠券发放类型 (receiveType)

类型 说明
Collect 主动领取:用户在小程序“领券中心”点击领取
Release 系统发放:系统自动发放(如邀请奖励、新人礼包)

优惠券库存防超卖

高并发领取场景下使用 Redis 原子操作扣减库存:

// Redis原子扣减
String key = "coupon:stock:" + templateId;
Long remain = redisTemplate.opsForValue().decrement(key);
if (remain < 0) {
    redisTemplate.opsForValue().increment(key);  // 回滚
    return false;
}

同时使用 Redis 分布式锁防止重复领取:

String lockKey = "coupon:receive:" + userId + ":" + templateId;
Boolean acquired = redisTemplate.opsForValue().setIfAbsent(lockKey, "1", Duration.ofMinutes(1));

四、优惠计算与叠加规则

4.1 优惠叠加规则

优惠类型 是否可叠加 计算顺序
多个营销活动 可叠加 设备端计算
优惠券 一次仅用1张 服务端计算
营销活动 + 优惠券 可叠加 先营销活动,后优惠券

4.2 优惠券自动扣减流程(订单回调时触发)

为什么是自动扣减?

智能售卖机没有“确认订单”环节,用户关门即结算。系统无法等待用户选择优惠券,因此采用自动匹配策略:优先使用最先到期的符合条件的券。

设计考量

  • 优先到期策略避免优惠券过期浪费,符合用户利益
  • 一次仅用1张优惠券,避免复杂叠加计算
  • 异常不阻断:优惠券计算失败时返回原金额,不影响正常订单流程

    HahaCallbackServiceImpl.processCouponDiscount()
    ↓
    1. 查询用户所有可用优惠券(status=0 且未过期)
    ↓
    2. 按到期时间升序排序(优先使用最先到期的券)
    ↓
    3. 遍历优惠券,检查适用条件:
    - 订单金额 >= 优惠券满减门槛(minAmount)
    - 适用范围匹配(applyScope)
    ↓
    4. 使用第一张符合条件的优惠券
    ↓
    5. 计算优惠金额(根据 couponType)
    ↓
    6. 确保优惠金额 <= 订单金额(paidAmount >= 0)
    ↓
    7. 标记优惠券为已使用(useCoupon)
    ↓
    8. 更新订单 discountAmount / paidAmount
    ↓
    9. 返回最终实付金额(用于支付扣费)
    

4.3 优惠券使用校验(UserCouponServiceImpl.useCoupon)

1. 验证优惠券归属(userId 匹配)
2. 检查状态(must be UNUSED)
3. 检查有效期(validEndTime)
4. 更新状态为 USED,写入 orderId / useTime / discountAmount

4.4 优惠金额公式

最终实付 = totalAmount - discountAmount
discountAmount = 营销活动优惠(设备端已扣减,不可见) + 优惠券优惠(服务端扣减)

注意:设备端传来的 totalAmount 已包含营销活动优惠
      服务端 discountAmount 仅记录优惠券优惠部分
      营销活动优惠金额不单独持久化到订单表

为什么营销活动优惠金额不持久化到订单表?

营销活动优惠是动态计算的:同一个活动在不同订单中的优惠金额不同(如折扣率取决于订单金额),且活动配置可能随时修改。将计算结果持久化到订单表既不必要也不合理——订单创建时优惠已确定,后续活动修改不影响已有订单。

如果将来需要统计营销活动优惠总额,可通过 MarketingRuleService.calculateOrderDiscount() 重新计算或从统计表 t_marketing_statistics 获取。


五、邀请有礼体系

5.1 InviteActivity 实体

字段 类型 说明
id Long 雪花算法主键
activityName / activityDesc String 活动名称/描述
startTime / endTime LocalDateTime 活动有效期
status Integer 状态:0-草稿 1-已发布 2-进行中 3-已暂停 4-已结束
couponTemplateId Long 邀请人奖励优惠券模板ID
inviteeCouponTemplateId Long 被邀请人奖励优惠券模板ID(双向奖励)
dailyLimit Integer 每人每日最大邀请数
totalLimit Integer 每人活动期间最大邀请总数
requireFirstOrder Integer 是否要求完成首单:0-否 1-是
minOrderAmount BigDecimal 首单最低金额要求
inviteCodePrefix String 邀请码前缀
applyScope / productScope Integer 适用范围

5.2 邀请记录 (InviteRecord)

关键字段 说明
inviterUserId 邀请人
inviteeUserId 被邀请人
inviteCode 邀请码
status 0-待激活 1-已激活 2-已失效 3-已奖励

5.3 邀请奖励 (InviteReward)

关键字段 说明
rewardType 0-邀请人奖励 1-被邀请人奖励
triggerType 0-首单完成触发 1-邀请绑定触发
couponTemplateId 奖励的优惠券模板
userCouponId 发放的用户优惠券ID
status 0-待发放 1-已发放 2-发放失败

5.4 邀请激活触发

订单完成时(payStatus = PAID),InviteActivityServiceImpl 检查被邀请人是否有待激活的邀请记录,满足条件后发放奖励优惠券。


六、核心代码文件索引

文件 职责
haha-entity/.../MarketingActivity.java 营销活动实体
haha-entity/.../TimedDiscountActivity.java 定时折扣活动实体
haha-entity/.../InviteActivity.java 邀请有礼活动实体
haha-entity/.../CouponTemplate.java 优惠券模板实体
haha-entity/.../UserCoupon.java 用户优惠券实体
haha-service/.../MarketingRuleServiceImpl.java 营销规则计算(首单/折扣/满减)
haha-service/.../MarketingActivityServiceImpl.java 营销活动CRUD
haha-service/.../UserCouponServiceImpl.java 优惠券领取/使用/查询
haha-service/.../CouponTemplateServiceImpl.java 优惠券模板管理
haha-service/.../TimedDiscountServiceImpl.java 定时折扣管理
haha-service/.../InviteActivityServiceImpl.java 邀请有礼管理
haha-service/.../UnifiedActivityServiceImpl.java 统一活动管理
haha-service/.../HahaCallbackServiceImpl.java 订单回调中优惠券自动扣减
haha-admin/.../MarketingActivityController.java 运营平台-营销活动API
haha-admin/.../CouponController.java 运营平台-优惠券管理API
haha-admin/.../TimedDiscountController.java 运营平台-定时折扣API
haha-admin/.../InviteActivityAdminController.java 运营平台-邀请有礼API
haha-admin/.../UnifiedActivityController.java 运营平台-统一活动API
haha-miniapp/.../AppCouponController.java 小程序-优惠券API
haha-miniapp/.../InviteController.java 小程序-邀请API
haha-common/.../enums/ActivityTypeEnum.java 活动类型枚举
haha-common/.../enums/ActivityStatusEnum.java 活动状态枚举
haha-common/.../enums/CouponTypeEnum.java 优惠券类型枚举
haha-common/.../enums/CouponStatusEnum.java 优惠券状态枚举
haha-common/.../enums/DiscountTypeEnum.java 折扣类型枚举