营销活动
受众:开发者 / 运营后台开发者 摘要:Marketing 模块用于承载运营活动、用户增长和轻量互动玩法。当前已完成营销活动 Admin contract、后台管理页、签到记录首段、抽奖奖池、参与记录持久化、三端抽奖入口、参与访问权益校验、卡券 / 积分 / 余额 / 额外抽奖次数 / 会员经验自动发放、外部奖品履约请求台账、奖品履约 Provider 扩展目录、抽奖奖励发放 Provider SPI 和后台派奖结果收口,后续补签和更多奖励执行器在业务项目内按 Provider 接入。
模块边界
Marketing 是可插拔业务模块,使用独立前缀:
| 类型 | 约定 |
|---|---|
| Maven 模块 | spring-open-module-marketing |
| API 前缀 | /api/v1/marketing/** |
| 配置前缀 | spring.open.marketing.* |
| 表前缀 | marketing_ |
| 权限前缀 | marketing:* |
当前模块默认启用。需要关闭后台活动接口时可设置:
spring:
open:
marketing:
enabled: falseMarketing 不强依赖 Mall、Distribution、Forum、Marketplace 等业务模块。跨模块奖励和业务联动应通过事件、扩展点或应用装配层桥接。
spring.open.marketing.enabled 已通过模块 ServiceProvider 声明为配置中心元数据。启用配置中心后,后台可以发现并维护 Marketing 模块启停配置;未通过配置中心覆盖时继续使用 application.yml / 环境变量中的默认装配值。
营销活动
当前提供活动主表 marketing_campaign、Admin 管理 API 和后台管理页。活动类型和状态保持中性,方便签到、刮刮乐、转盘等玩法复用。
活动类型:
| 值 | 含义 |
|---|---|
check-in | 签到活动 |
scratch-card | 刮刮乐 |
lucky-wheel | 幸运转盘 |
custom | 自定义活动 |
活动状态:
| 值 | 含义 |
|---|---|
draft | 草稿 |
enabled | 启用 |
disabled | 停用 |
ended | 已结束 |
参与访问策略
签到和抽奖写入前会读取活动 riskPolicyJson 中的 participationAccess 或 participationAccessPolicy,并通过 Entitlement 的中立 ContentAccessPolicyService 做参与资格判断。未配置时默认 free,不影响普通活动。
示例:
{
"ipLimit": 20,
"participationAccess": {
"accessType": "member",
"itemCode": "marketing-member-draw"
}
}字段说明:
| 字段 | 说明 |
|---|---|
accessType | 支持 free、login、member、vip、paid、points、limited-free;未配置时按 free 处理 |
itemCode | 需要权益凭证的访问类型使用;未填写且需要凭证时默认使用 marketing-campaign-participation |
directUnlock | 是否允许直接解锁;仅用于业务项目明确放行的特殊活动 |
limitedFreeUntil | 限免截止时间,ISO-8601 LocalDateTime 格式,例如 2026-07-31T23:59:59 |
该策略只控制参与资格,不发放权益、不扣款、不写 Money 账本。用户不满足条件时,签到和抽奖写入会被拒绝;状态查询、日历查询和后台运营查询不受影响。
抽奖限次加成策略
抽奖活动可以在 riskPolicyJson.drawBonusChances 或 riskPolicyJson.drawLimitBonus 中配置后台侧限次加成,用于广告解锁、邀请助力、运营临时加赠等轻量玩法。该策略只由活动配置决定,不信任用户请求体中的自报次数。
示例:
{
"drawBonusChances": {
"daily": 1,
"total": 2,
"reason": "ad-watch"
}
}字段说明:
| 字段 | 说明 |
|---|---|
daily / dailyChances / dailyBonus | 在活动每日参与上限基础上追加的次数;基础每日上限为 0 时仍表示不限次,不额外记录加成 |
total / totalChances / totalBonus | 在活动累计参与上限基础上追加的次数;基础累计上限为 0 时仍表示不限次,不额外记录加成 |
reason | 可选,记录本次加成来源,例如 ad-watch、invite-help、ops-bonus |
参与成功时,如果限次加成实际改变了本次活动的有效上限,抽奖记录 extraJson.drawLimitBonus 会写入基础上限、加成次数、有效上限、评估时间和原因,便于后台排查活动玩法。该能力不新增表、不生成永久次数授予;需要可累积、可消耗的次数仍使用 draw-chance 奖项和 marketing_lottery_draw_chance_grant。
抽奖玩法
刮刮乐和幸运转盘复用活动主表的 scratch-card / lucky-wheel 活动类型。当前已固化玩法规则、奖项配置、参与策略、参与结果 contract,并落地奖池配置与参与记录持久化。
| Contract | 说明 |
|---|---|
MarketingLotteryRule | 约束活动编码、玩法类型、每日 / 总参与限制、奖池和风控策略快照 |
MarketingLotteryPrize | 约束奖项编码、奖项类型、名称、权重、库存、单用户限制和外部奖励 payload |
MarketingLotteryDrawPolicy | 约束一次参与前的次数限制、奖池可用性、幂等键和下一步动作 |
MarketingLotteryDrawResult | 约束一次参与的中奖快照、派奖状态、幂等键和参与时间 |
奖项类型:
| 值 | 含义 |
|---|---|
points | 积分奖励 |
coupon | 卡券奖励(Voucher) |
physical | 实物奖品 |
virtual | 虚拟奖品 |
draw-chance | 额外抽奖次数 |
thank-you | 谢谢参与 |
派奖状态:
| 值 | 含义 |
|---|---|
pending | 等待外部奖励执行器发放 |
granted | 已发放 |
failed | 发放失败 |
skipped | 未中奖或无需发放 |
中奖奖项必须保存 rewardPayloadJson。coupon 奖项会把 payload 中的 voucherTemplateId 交给 Voucher 发放;points 奖项会把 payload 中的 points 交给 Member Points 写入 Money 积分账本;balance 奖项会把 payload 中的余额资产和金额交给 Money 用户余额账本;draw-chance 奖项会把 payload 中的 chances 写入 Marketing 额外抽奖次数授予表;member-exp 奖项会把 payload 中的 experience 写入 Marketing 会员经验授予表;physical / virtual 奖项会把 providerCode 和 externalPrizeCode 写入外部履约请求台账,真实发货、虚拟码发放和供应商回调仍在部署环境验收。参与前可用 MarketingLotteryDrawPolicy 按 drawsToday、drawsTotal、奖池可用奖项和 idempotencyKey 得到 draw、daily-limit-reached、total-limit-reached 或 prize-pool-empty 下一步动作;谢谢参与奖项即使库存为 0 也可作为保底可抽奖项。后续奖励执行器应按参与结果的 idempotencyKey 幂等发放;未中奖结果必须使用 skipped,中奖结果不能使用 skipped。
coupon 奖项 payload 示例:
{
"voucherTemplateId": 1001,
"ownerType": "user",
"grantType": "marketing-lottery",
"sourceType": "marketing-campaign",
"remark": "新人活动赠券",
"metadata": {
"scene": "new-user"
}
}voucherTemplateId 必填且必须大于 0。中奖后 Marketing 使用中奖用户作为 ownerId,使用 marketing-lottery:{idempotencyKey}:voucher 作为发券幂等键;Voucher 发放成功后抽奖记录的 rewardStatus 更新为 granted。无自动发放 Provider 的中奖奖项继续写入 pending,由后续奖励执行器或外部运营流程处理;后台可在外部发放完成、失败或复核后手工收口本地派奖状态,并把操作人、TraceId 和备注写入 extraJson.rewardOperation。
points 奖项 payload 示例:
{
"points": 100,
"symbol": "mall",
"remark": "新人抽奖积分"
}points 必填且必须大于 0,symbol 可选,默认沿用 Member Points 默认积分资产。中奖后 Marketing 使用 member-points-ledger Provider 调用 Member Points,幂等键为 member-points:marketing-lottery:{userId}:{idempotencyKey}:points,流水业务类型为 member-points-marketing-lottery-reward;写入成功后抽奖记录的 rewardStatus 更新为 granted。
balance 奖项 payload 示例:
{
"amount": "18.60",
"symbol": "CNY",
"remark": "新人抽奖余额"
}amount 必填且必须大于 0。assetCode 可直接指定稳定资产编码;未传 assetCode 时可用 assetType、symbol、chain、contractAddress 组合解析资产;都未传时默认使用法币 CNY。中奖后 Marketing 使用 money-balance-ledger Provider 发布中立余额奖励事件,Money 监听后写入用户余额账户 adjust_in 流水,幂等键为 money:balance:marketing-lottery:{userId}:{idempotencyKey}:balance,流水业务类型为 marketing-lottery-reward;写入成功后抽奖记录的 rewardStatus 更新为 granted。
draw-chance 奖项 payload 示例:
{
"chances": 2,
"remark": "再来两次"
}chances 必填且必须大于 0。中奖后 Marketing 使用 draw-chance-local Provider 写入 marketing_lottery_draw_chance_grant,来源幂等键为 marketing-lottery:{idempotencyKey}:draw-chance;写入成功后抽奖记录的 rewardStatus 更新为 granted。用户后续参与同一活动时,如果日限或总限已达到,服务端会优先原子消耗一条可用额外次数并继续抽奖,抽奖记录 extraJson.drawChanceConsumption 会保留被消耗授予记录、来源中奖记录和消耗时间。
physical / virtual 奖项 payload 示例:
{
"providerCode": "mock-warehouse",
"externalPrizeCode": "sku-vip-card",
"externalOrderNo": "ext-optional",
"remark": "虚拟卡密"
}providerCode 和 externalPrizeCode 必填。中奖后 Marketing 使用 external-fulfillment-contract Provider 写入 marketing_lottery_external_fulfillment,来源幂等键为 marketing-lottery:{idempotencyKey}:external-fulfillment;抽奖记录保持 pending,因为本地只生成可追踪外部履约请求,不伪装真实供应商已经发货或发码。真实外部处理完成后,运营或项目侧 Provider 需要通过派奖处理接口登记 granted / failed、TraceId、外部单号或失败原因。
抽奖奖项表:marketing_lottery_prize。核心字段包括活动 ID、奖项编码、奖项类型、名称、权重、库存、单用户中奖上限、奖励 payload、排序和启用状态。
抽奖参与记录表:marketing_lottery_draw_record。核心字段包括活动 ID / 编号、用户 ID、玩法类型、奖项 ID / 编码 / 类型快照、中奖状态、派奖状态、幂等键、客户端追踪标识和参与时间。
额外抽奖次数授予表:marketing_lottery_draw_chance_grant。核心字段包括活动 ID / 编号、用户 ID、来源中奖记录、来源派奖幂等键、授予次数、已消耗次数、客户端追踪标识和扩展 JSON。
会员经验授予表:marketing_lottery_member_exp_grant。核心字段包括活动 ID / 编号、用户 ID、来源中奖记录、来源派奖幂等键、经验值、目标会员等级编码、客户端追踪标识和扩展 JSON。
外部奖品履约请求表:marketing_lottery_external_fulfillment。核心字段包括活动 ID / 编号、用户 ID、来源中奖记录、来源派奖幂等键、奖项编码 / 类型、Provider 编码、外部奖品编码、履约状态、外部单号、请求 / 响应 payload 和扩展 JSON。
用户侧 API:
| 方法 | 路径 | 说明 |
|---|---|---|
POST | /api/v1/marketing/lotteries/campaigns/{campaignId}/draw | 当前登录用户参与一次刮刮乐或幸运转盘抽奖;请求体必须携带 idempotencyKey |
Admin API:
| 方法 | 路径 | 权限 | 说明 |
|---|---|---|---|
GET | /api/v1/admin/marketing/lotteries/prizes | marketing:lottery:view | 抽奖奖项分页,支持按活动、奖项类型和启用状态筛选 |
POST | /api/v1/admin/marketing/lotteries/prizes | marketing:lottery:manage | 按活动 ID 和奖项编码新增或更新抽奖奖项 |
DELETE | /api/v1/admin/marketing/lotteries/prizes/{id} | marketing:lottery:manage | 删除抽奖奖项 |
GET | /api/v1/admin/marketing/lotteries/reward-providers | marketing:lottery:view | 查询抽奖奖品履约 Provider 目录,区分本地 contract 与真实外部验收要求 |
GET | /api/v1/admin/marketing/lotteries/draw-records | marketing:lottery:view | 抽奖参与记录分页,支持按活动、用户、玩法、奖项类型、中奖状态和派奖状态筛选 |
GET | /api/v1/admin/marketing/lotteries/draw-records/summary | marketing:lottery:view | 按同一筛选条件汇总参与次数、中奖次数和派奖状态 |
GET | /api/v1/admin/marketing/lotteries/draw-records/reward-compensation-plan | marketing:lottery:view | 只读生成待派奖 / 派奖失败记录的补偿建议,不直接修改派奖状态或触发外部发奖 |
POST | /api/v1/admin/marketing/lotteries/draw-records/{id}/reward-status | marketing:lottery:manage | 对单条抽奖记录做本地派奖结果收口,支持 granted / failed / skipped,并记录审计备注 |
抽奖奖品履约 Provider 目录用于后台运维识别奖品类型和履约边界:
voucher-local:卡券奖品走 Voucher 本地自动发放,中奖后使用marketing-lottery:{idempotencyKey}:voucher幂等键。member-points-ledger:积分奖品走 Member Points 和 Money 积分账户本地自动发放,中奖后使用member-points:marketing-lottery:{userId}:{idempotencyKey}:points幂等键。money-balance-ledger:余额奖品走 Money 用户余额账户本地自动发放,中奖后使用money:balance:marketing-lottery:{userId}:{idempotencyKey}:balance幂等键。draw-chance-local:额外抽奖次数走 Marketing 本地自动发放,中奖后使用marketing-lottery:{idempotencyKey}:draw-chance幂等键,限次后自动消耗。member-experience-local:会员经验走 Marketing 本地授予台账,中奖后使用marketing-lottery:{idempotencyKey}:member-exp幂等键,后续会员等级模块可按台账接入成长规则。external-fulfillment-contract:实物 / 虚拟奖品走 Marketing 外部履约请求台账,中奖后使用marketing-lottery:{idempotencyKey}:external-fulfillment幂等键;真实发货 / 发码 / 供应商回调仍是部署环境验收项。account-manual:未知账户类奖品保留人工收口兜底,真实发放需运营或项目侧 Provider 按工单完成。external-fulfillment:实物和虚拟奖品人工兜底说明,用于未接入外部履约请求或业务自定义 Provider 时提示真实外部链路验收要求。no-reward:谢谢参与不触发外部履约,记录直接进入跳过状态。
业务项目可以实现 MarketingRewardProvider 追加自己的奖品履约 Provider 描述,例如把会员等级升级或外部虚拟码接到业务侧自动发放执行器。Marketing 只负责让后台目录、补偿计划和审计报表发现这些能力,不自建余额表或外部履约事实。
实际自动发放走 MarketingLotteryRewardGrantProvider。内置 VoucherMarketingLotteryRewardGrantProvider 已把 coupon 奖项接到 Voucher 发券,MemberPointsMarketingLotteryRewardGrantProvider 已把 points 奖项接到 Member Points / Money 积分账本,MoneyBalanceMarketingLotteryRewardGrantProvider 已把 balance 奖项接到 Money 用户余额账本,DrawChanceMarketingLotteryRewardGrantProvider 已把 draw-chance 奖项接到 Marketing 自有额外抽奖次数表,MemberExperienceMarketingLotteryRewardGrantProvider 已把 member-exp 奖项接到 Marketing 自有会员经验授予表,ExternalFulfillmentMarketingLotteryRewardGrantProvider 已把 physical / virtual 奖项接到 Marketing 外部履约请求表;外部奖品真实供应商接入方可以按同一 SPI 判断奖项类型、读取 rewardPayloadJson、使用抽奖记录 idempotencyKey 幂等发放,并返回 granted 或 failed。Provider 返回 granted / failed 后,Marketing 会回写派奖状态和 extraJson.rewardOperation;返回 pending 时只保留本地请求台账和待处理状态,不伪装真实外部发放完成。
派奖补偿计划是只读运营排障入口:它只读取中奖且状态为 pending / failed 的抽奖记录,按奖项类型生成卡券核对、账户类补发、外部履约跟进等建议,并标记需要真实外部验收的实物 / 虚拟奖品。该计划不会写数据库、不会调用真实外部 Provider,也不会替代“派奖处理”;运营确认真实发放或失败原因后,仍需通过派奖处理接口登记本地状态、TraceId、操作人和备注。
前端入口:
| 端 | 路由 / 组件 | 说明 |
|---|---|---|
| Admin | marketing/lottery-prizes/index | 抽奖奖池分页、活动 / 类型 / 状态筛选、新增 / 修改和删除 |
| Admin | marketing/lottery-records/index | 抽奖参与记录分页、多条件筛选、当前筛选运营统计、奖品 Provider 目录、派奖补偿计划和派奖结果处理 |
| App | marketing/lottery | 输入活动 ID 后参与刮刮乐 / 幸运转盘并展示中奖结果 |
| PC | /marketing/lottery | 输入活动 ID 后参与刮刮乐 / 幸运转盘并展示中奖结果 |
抽奖写入规则:
- 只允许
campaignType=scratch-card或campaignType=lucky-wheel的活动抽奖。 - 活动必须处于
enabled状态,且当前时间在活动开始和结束时间窗口内。 - 如果活动
riskPolicyJson.participationAccess配置了登录、会员、VIP、付费或积分解锁等条件,服务端会先通过 Entitlement 校验当前用户参与资格,未满足时拒绝写入抽奖记录。 - 如果活动
riskPolicyJson.drawBonusChances/drawLimitBonus配置了限次加成,服务端会在计算每日 / 累计上限时叠加后台配置的次数,并把加成证据写入抽奖记录extraJson.drawLimitBonus。 - 同一
idempotencyKey只能处理一次,避免重复参与或重复发奖。 dailyLimit大于 0 时限制单用户每日参与次数;totalLimit大于 0 时限制单用户累计参与次数。- 当用户已达到
dailyLimit或totalLimit时,如果存在同活动未消耗的额外抽奖次数授予记录,服务端会原子消耗 1 次并允许继续抽奖;没有可用额外次数时仍按原限制拒绝。 - 服务端只从启用奖项中抽取;中奖奖项必须有库存且未超过单用户中奖上限;
thank-you可作为库存为 0 的保底未中奖奖项。 - 抽中中奖奖项时扣减 Marketing 奖项库存;
coupon、points、balance、draw-chance和member-exp奖项由内置 Provider 自动发放并写入granted,physical/virtual奖项会生成外部履约请求并保持pending,无可用 Provider 的中奖奖项保持pending;抽中thank-you时写入skipped。 - 积分奖励已由内置 Provider 写入 Member Points / Money 积分账本;余额奖励已由内置 Provider 写入 Money 用户余额账本;额外抽奖次数和会员经验已由内置 Provider 写入 Marketing 自有授予表;实物履约等奖励由后续监听方或奖励执行器按
idempotencyKey幂等发放。若当前仍是外部人工流程,运营可在 Admin 抽奖记录页按真实结果把本地状态收口为granted或failed。 - 中奖记录不能手工调整为
skipped;已经granted的中奖记录不能降级为失败,避免外部奖品已交付后产生状态回退歧义。
Admin API
| 方法 | 路径 | 权限 | 说明 |
|---|---|---|---|
GET | /api/v1/admin/marketing/campaigns | marketing:campaign:view | 活动分页 |
GET | /api/v1/admin/marketing/campaigns/{id} | marketing:campaign:view | 活动详情 |
GET | /api/v1/admin/marketing/campaigns/summary | marketing:campaign:view | 活动状态汇总 |
GET | /api/v1/admin/marketing/operations/summary | marketing:campaign:view | 营销运营总览,聚合活动状态、签到记录和抽奖派奖积压 |
GET | /api/v1/admin/marketing/operations/audit-report | marketing:campaign:view | 营销运营审计报表,聚合派奖积压、外部奖品验收项和运营处理建议 |
POST | /api/v1/admin/marketing/campaigns | marketing:campaign:create | 创建活动 |
PUT | /api/v1/admin/marketing/campaigns/{id} | marketing:campaign:update | 更新活动 |
PUT | /api/v1/admin/marketing/campaigns/{id}/status | marketing:campaign:update | 更新活动状态 |
DELETE | /api/v1/admin/marketing/campaigns/{id} | marketing:campaign:delete | 删除单个活动 |
DELETE | /api/v1/admin/marketing/campaigns | marketing:campaign:delete | 批量删除活动 |
后台菜单入口:营销活动 / 营销活动,组件路径 marketing/campaigns/index。页面支持活动分页、状态汇总、运营总览、运营审计报表、创建、编辑、启用 / 停用 / 结束、单删和批删。运营总览会展示今日签到、累计签到、抽奖参与、待派奖和派奖失败,便于运营快速发现需要补偿或人工处理的活动积压。
运营审计报表只读复用现有活动、签到、抽奖记录和奖品 Provider 目录,不新增报表表。它会把派奖失败、待派奖、真实外部奖品验收和无启用活动等情况转成可操作发现,并在后台展示外部验收清单。该入口不会修改派奖状态、不会调用真实外部 Provider,也不会替代抽奖记录页的派奖处理;运营确认真实发放或失败原因后,仍需在派奖处理入口登记状态、TraceId、操作人和备注。
创建和更新活动时,campaignNo 在全局唯一。活动开始时间不能晚于结束时间;启用状态只允许使用 draft、enabled、disabled 和 ended。
签到
签到首段基于 check-in 类型活动工作,提供当前用户签到和后台记录审计。
签到记录表:marketing_check_in_record。核心字段包括活动 ID、用户 ID、签到自然日、连续签到天数、累计签到天数、场景、奖励上下文 JSON 快照、客户端追踪标识和签到时间。
用户侧 API:
| 方法 | 路径 | 说明 |
|---|---|---|
GET | /api/v1/marketing/check-ins/campaigns/{campaignId}/status | 查询当前登录用户今日签到状态 |
GET | /api/v1/marketing/check-ins/campaigns/{campaignId}/calendar | 查询当前登录用户在日期区间内的每日签到状态;startDate / endDate 可选,默认本月一日至今天 |
POST | /api/v1/marketing/check-ins/campaigns/{campaignId} | 当前登录用户完成今日签到 |
Admin API:
| 方法 | 路径 | 权限 | 说明 |
|---|---|---|---|
GET | /api/v1/admin/marketing/check-ins | marketing:check-in:view | 签到记录分页,支持按活动、用户、场景和签到日期范围筛选 |
后台菜单入口:营销活动 / 签到记录,组件路径 marketing/checkins/index。
签到写入规则:
- 只允许
campaignType=check-in的活动签到。 - 活动必须处于
enabled状态,且当前时间在活动开始和结束时间窗口内。 - 同一用户在同一活动的同一自然日只能签到一次。
totalLimit大于 0 时作为单用户累计签到上限。- 签到成功后服务端计算连续天数和累计天数,并把活动
rewardPolicyJson写入记录的rewardContextJson,供奖励扩展点消费。 - 签到记录写入成功后发布
MarketingCheckInCompletedEvent,事件在事务提交后触发;无事务上下文时直接触发。 - 签到日历复用签到记录表,未签到日期会返回
checked=false,不额外写空白记录。
签到事件扩展
其他业务模块可以监听 com.springopen.module.marketing.event.MarketingCheckInCompletedEvent 接入奖励、通知、任务或会员成长。
事件字段:
| 字段 | 说明 |
|---|---|
eventId | 稳定事件 ID,当前格式为 marketing-check-in:{checkInRecordId} |
campaignId / campaignNo | 签到活动 ID / 编码 |
userId | 签到用户 ID |
checkInRecordId | 签到记录 ID |
checkInDate | 签到自然日 |
streakDays / totalDays | 连续签到天数 / 累计签到天数 |
scene | 签到场景 |
rewardContextJson | 活动奖励策略快照 |
clientTraceId / extraJson | 客户端追踪标识 / 扩展上下文 |
checkedAt / occurredAt | 签到时间 / 事件发生时间 |
监听示例:
@Component
public class CheckInRewardListener {
@EventListener
public void onCheckInCompleted(MarketingCheckInCompletedEvent event) {
String idempotencyKey = event.getEventId() + ":points";
// 按 idempotencyKey 幂等发放积分、优惠券、会员经验或任务奖励。
}
}接入要求:
- 奖励发放必须幂等,建议使用
eventId + rewardType + rewardTarget或checkInRecordId + rewardType + rewardTarget。 - Marketing 不直接依赖积分、优惠券、余额、会员、通知等业务模块。
- 奖励流水、到账状态和用户侧展示由监听方所在模块维护。
后续扩展
后续签到、补签、抽奖和奖池能力应继续遵循当前活动 contract:
- 玩法表只保存 Marketing 自己的规则、参与记录、奖项和中奖审计。
- 抽奖玩法继续使用
MarketingLotteryRule、MarketingLotteryPrize和MarketingLotteryDrawResult;除coupon奖项复用 Voucher 发放外,不把更多奖项 payload 解释逻辑写死在 Marketing 模块。 - 余额和积分奖品通过中立事件交给 Money / Member Points 写入对应账本;会员经验、徽章、通知等发放不写死在 Marketing 中。
- 奖励发放通过事件交给对应模块处理,并保持幂等;确有编排需求时再新增 Contributor 扩展点。
- 注释掉 Marketing 模块后,其他业务模块仍应可以编译、启动和运行。