hyapp-server/docs/幸运礼物产品文档.md

25 KiB
Raw Blame History

幸运礼物产品文档

本文档按当前代码实现描述幸运礼物产品能力。所有功能点均给出实现依据;未在代码中闭环的能力不写成已上线能力。

1. 产品定位和范围

幸运礼物是建立在房间送礼链路上的付费抽奖玩法。用户发送带 pool_id 的幸运礼物,系统先完成金币扣费,再按奖池规则抽奖;中奖金币由钱包服务异步入账,开奖结果通过房间 IM 和区域播报做表现。

功能点 产品规则 实现依据
付费抽奖定位 幸运礼物不是独立支付入口,而是送礼成功后的抽奖附加玩法。 services/room-service/internal/room/service/gift.goSendGift 先调用 wallet.DebitGift,成功后再调用 ExecuteLuckyGiftDraw
规则隔离维度 规则按 pool_id 隔离,不按真实礼物 ID 绑定规则;一个奖池可服务不同价格礼物。 docs/幸运礼物实现文档.mdservices/activity-service/internal/storage/mysql/lucky_gift_rule_config_repository.goGetLuckyGiftRuleConfiggetLuckyGiftRuleConfigluckyRuntimeConfig 按真实 coin_spent 缩放奖档金额。
服务边界 activity-service 负责规则、抽奖、奖池、风控、抽奖事实和 outboxwallet-service 负责扣费和返奖入账;room-service 负责房间状态、热度、榜单和房间 outbox。 services/activity-service/internal/service/luckygift/service.goRepositoryWalletClientRoomPublisher 接口;services/room-service/internal/room/service/gift.goservices/wallet-service/internal/service/wallet/service.goCreditLuckyGiftReward
不兼容旧玩法假设 当前只以 v2 规则版本为准;旧文档和旧 gift_id 规则模型不作为产品口径。 docs/幸运礼物实现文档.md 开头说明;后台路由固定为 /admin/activity/lucky-gifts/v2/*,见 server/admin/internal/modules/luckygift/routes.go

2. 角色和使用场景

角色 功能点 产品规则 实现依据
送礼用户 发送幸运礼物并获得即时抽奖结果 用户在房间内给一个在线用户送礼,送礼成功后当前用户同步拿到 lucky_gift 结果。 SendGift 校验送礼者和收礼者都在房间;响应包含 roomv1.SendGiftResponse.LuckyGift;转换逻辑在 luckyGiftResultFromProto
收礼用户/主播 作为礼物目标和主播风控主体 目标用户必须在房间内;主播风控优先使用房主 ID缺失时退回房间 ID。 SendGift 校验 TargetUserIDluckyGiftAnchorID
房间内用户 看到送礼事实和开奖结果表现 普通送礼表现继续广播;中奖结果由 activity outbox worker 发布 lucky_gift_drawn 房间群消息。 RoomGiftSent outbox 构建在 SendGift;开奖结果发布在 publishLuckyGiftDrawn
运营管理员 配置奖池、阶段奖档、RTP、风控上限并查看数据 后台提供奖池配置、发布、列表、抽奖汇总和抽奖明细接口;前端提供配置页、模拟工具和数据概览。 后端 server/admin/internal/modules/luckygift/handler.go;前端 hyapp-admin-platform/src/features/lucky-gift/pages/LuckyGiftConfigPage.jsx

3. 用户端功能

功能点 产品规则 实现依据
送礼前资格检查 App 可在送礼前调用检查接口确认奖池是否可用;该接口只检查,不扣费、不抽奖。 POST /api/v1/activities/lucky-gifts/check 注册在 services/gateway-service/internal/transport/http/httproutes/router.go;处理函数 activityapi.Handler.checkLuckyGift
检查参数 检查接口要求 room_idgift_idpool_id 必填,用户 ID 来自登录态。 services/gateway-service/internal/transport/http/activityapi/lucky_gift_handler.goluckyGiftCheckRequest 和必填校验。
检查返回 返回是否启用、原因、奖池 ID、礼物 ID、参考价格、规则版本、目标 RTP、用户当前体验池。 luckyGiftCheckDataactivityv1.CheckLuckyGiftResponseRepository.CheckLuckyGift 读取用户状态并返回 ExperiencePool
显式奖池触发 新客户端传 pool_id 时,系统必须先检查规则启用,再扣费;规则未启用则整笔送礼失败。 SendGiftcmd.PoolID != "" 分支调用 CheckLuckyGift,未启用返回 RuleNotActive,位置在扣费调用前。
旧客户端兼容触发 未显式传 pool_id 时,若钱包返回礼物类型为 luckysuper_lucky,仍可触发幸运礼物抽奖。 Service.shouldDrawLuckyGift 判断 giftTypeCodelucky/super_lucky
扣费优先 抽奖只使用钱包扣费成功后的 coin_spent,不信任客户端价格。 SendGift 调用 wallet.DebitGift 后,将 billing.GetCoinSpent() 写入 LuckyGiftMeta.CoinSpent
设备风控标识 设备维度优先使用房间命令的 session_id,没有时退回 command_id,保证风控 scope 不为空。 luckyGiftDeviceID
同步抽奖结果 送礼响应中返回抽奖 ID、奖池、规则版本、体验池、命中奖档、倍率、奖励金币、发放状态、阶段反馈和高倍标识。 api/proto/room/v1/room.protoLuckyGiftDrawResultluckyGiftResultFromProto
普通送礼事实保留 幸运礼物不会替代普通送礼房间热度、本地礼物榜、RoomGiftSent、RoomHeatChanged、RoomRankChanged 仍照常落地。 SendGift 在抽奖后继续更新 current.HeatGiftRank,并构建 RoomGiftSentRoomHeatChangedRoomRankChanged outbox。
批量送礼 gift_count=N 表示抽 N 次;响应展示整次送礼的聚合倍数和总奖励。 executeLuckyGiftDrawBatchluckyDrawUnitSpendstageLuckyBatchDrawluckyAggregateDrawResults
批量金额拆分 总扣费按 gift_count 拆为 N 个单抽金额,余数分给前几抽,保证子抽金额总和等于扣费事实。 luckyDrawUnitSpend;单测 TestLuckyDrawUnitSpendSplitsBatchSpendByGiftCount

4. 抽奖规则

功能点 产品规则 实现依据
奖池配置开关 每个 pool_id 有独立启用状态;未配置或未启用时不能抽奖。 Service.GetConfig 未配置返回 disabled 草稿;Repository.CheckLuckyGift 未配置返回 enabled=falseexecuteSingleLuckyGiftDrawexecuteOptimizedLuckyGiftDrawBatch 未启用返回 RuleNotActive
不可变规则版本 每次后台发布都新增 rule_version,不覆盖历史版本。 Repository.PublishLuckyGiftRuleConfig 事务内锁最新版本并写入 lucky_gift_rule_versionslucky_gift_stage_tiers
最新规则读取 App 检查、抽奖运行和后台列表都读取当前奖池最新版本。 getLuckyGiftRuleConfigrule_version DESC LIMIT 1ListLuckyGiftRuleConfigs 使用每个 pool 的 MAX(rule_version)
RTP 目标 target_rtp_ppm 表示基础返奖目标,后台业务单位是百分比,服务端持久化为 ppm。 server/admin/internal/modules/luckygift/handler.gopercentToPPMppmToPercentvalidateRuleConfig 限制 10%-99%。
入池比例 每笔扣费按 pool_rate_ppm 进入基础奖池,且入池比例必须不低于目标 RTP。 validateRuleConfig 校验 PoolRatePPM >= TargetRTPPPMluckyBasePoolIncreditLuckyBasePools
结算窗口 RTP 结算窗口按金币流水配置,运行时按参考价格折算窗口抽数;窗口只用于观察和结算,不改变随机概率。 luckyRuntimeConfigFromRuleConfigSettlementWindowWager / GiftPriceReference 折算窗口;getOpenLuckyRTPWindowselectLuckyCandidate 注释和实现不读取 RTP 缺口。
波动带 后台发布时校验每个阶段期望 RTP 是否落在 目标 RTP ± 波动带 validateRuleStageExpectedRTP;前端 luckyGiftConfigFormSchema 同步校验。
参考价格 gift_price_reference 用于配置奖档金额、结算窗口和等价抽数;真实抽奖按本次单抽 coin_spent 缩放奖档金额。 luckyRuntimeConfigFromRuleConfigluckyRuntimeConfigluckyScaleReward
用户阶段 用户分为新手、正常、高阶三个阶段;阶段由累计金币流水折算等价抽数决定。 luckyEquivalentDrawsluckyExperiencePool;用户状态表字段见 lucky_user_states
阶段奖档 每个阶段都配置一组奖档,每个奖档包含倍率、概率、预算来源、高水位、播报级别、启用状态;当前 v2 抽奖主链路只把 base_rtp 奖档转换为随机候选。 LuckyGiftRuleStageLuckyGiftRuleTier protodomain.RuleStagedomain.RuleTier;后台 DTO stageDTOtierDTO;运行转换见 luckyRuntimeTiersFromRuleStages
概率闭合 每个阶段启用奖档概率合计必须为 100%,且必须存在 0x 档和至少一个基础 RTP 奖档。 validateRuleStage;前端 luckyGiftConfigFormSchema
高倍限制 高倍率奖档必须标记高水位,运行时仍受奖池和风控过滤。 后端 validateRuleStage 对高倍率校验;前端 schema 对 5x 及以上要求 highWaterOnlyselectLuckyCandidate 对高倍和 LargeTierEnabled 做过滤。
随机候选 抽奖只使用后台配置概率;奖池和风控只删除不可支付候选,不动态注入 RTP 修正档。 selectLuckyCandidateluckyConfiguredWeightCandidates;单测 TestLuckyConfiguredWeightCandidatesUsesConfiguredWeightsWithoutForcedSettlement
无可支付奖档 所有正奖档都因奖池或风控不可支付时,本抽返回 0 倍 no_payable_tier,不透支。 selectLuckyCandidate 返回 no_payable_tier;单测 TestSelectLuckyCandidateFallsBackToZeroWhenAllPositiveTiersAreUnpayable

5. 奖池、预算和风控

功能点 产品规则 实现依据
三层基础奖池 基础返奖由平台池、房间池、礼物池共同承担;任一层余额不足都会过滤奖档。 getOrCreateLuckyPoolluckySplitWeightedluckyWeightedPoolCapacitycreditLuckyBasePoolsdebitLuckyPool
先入池后开奖 每笔扣费先按入池比例补充奖池,再过滤候选并开奖。 单抽路径先调用 creditLuckyBasePoolsselectLuckyCandidate;批量路径在 stageLuckyBatchDraw 中先 creditLuckyPoolInMemory 再候选过滤。
房间气氛池 当前保留房间气氛池账本和结果字段,且气氛奖励口径独立于基础 RTPv2 主候选默认仍来自基础 RTP。 lucky_room_atmosphere_pools 表;getOrCreateLuckyAtmospherecreditLuckyAtmosphereupdateLuckyAtmosphereNetapplyLuckyDraw 注释说明气氛奖励不进入基础 RTP。
活动补贴预算 当前保留活动补贴总预算和 UTC 日预算账本v2 后台第一阶段未暴露活动预算,运行主候选默认不从活动补贴生成。 lucky_activity_budgets 表;getLuckyActivityRemaininggetOrCreateLuckyActivityTotalRemainingluckyRuntimeConfigFromRuleConfigActivityBudgetActivityDailyLimit 置为 0。
单次风控上限 单抽奖励不能超过规则配置的单次返奖上限。 validateRuleConfig 要求 MaxSinglePayout > 0luckyRiskCapacity 首先使用 MaxSinglePayout
用户小时/日上限 同一用户在小时和 UTC 自然日内有返奖上限。 getLuckyRiskCounters 创建 user:houruser:day 计数;luckyRiskCapacityupdateLuckyRiskCounters
设备日上限 同一设备在 UTC 自然日内有返奖上限。 getLuckyRiskCountersdevice/dayluckyGiftDeviceID 保证设备 scope。
房间小时上限 同一房间每小时有返奖上限。 getLuckyRiskCountersroom/hourluckyRiskCapacity
主播日上限 同一主播 UTC 自然日有返奖上限。 getLuckyRiskCountersanchor/dayluckyGiftAnchorID
并发一致性 奖池、用户状态、RTP 窗口和风控计数在事务内锁定,避免并发透支。 Repository.ExecuteLuckyGiftDraw 使用 MySQL 事务;getLuckyUserStateForUpdategetLatestLuckyRTPWindow(... FOR UPDATE)getOrCreateLuckyPool(... FOR UPDATE)getOrCreateLuckyRiskCounter(... FOR UPDATE)

6. 返奖和表现

功能点 产品规则 实现依据
异步发奖 抽奖事务只写事实和 outbox不直接调用钱包worker 后续处理返奖和表现。 applyLuckyDrawinsertLuckyAggregateDrawOutboxactivity_outbox;外部 RPC 在 processDrawOutbox
钱包入账 有有效奖励时worker 调用钱包 CreditLuckyGiftReward 给中奖用户加 COIN。 Service.processDrawOutbox 调用 walletv1.CreditLuckyGiftReward;钱包实现见 services/wallet-service/internal/service/wallet/service.gointernal/storage/mysql/repository.go
返奖幂等 钱包命令 ID 由聚合 draw_id 派生,同一 draw 重试只加币一次。 processDrawOutbox 使用 CommandId: "lucky_reward:" + payload.DrawID;钱包 CreditLuckyGiftRewardlookupTransaction 和 request hash 幂等;单测 TestCreditLuckyGiftRewardIsIdempotent
房间开奖结果 worker 发 lucky_gift_drawn 房间群自定义消息,用于房间内开奖结果表现。 publishLuckyGiftDrawnIM 消息 Desclucky_gift_drawn
区域播报 当存在可见区域、有效奖励且聚合倍率不低于 1x 时,写区域播报 outbox。 shouldPublishLuckyGiftRegionBroadcastpublishLuckyGiftRegionBroadcastluckyGiftRegionBroadcastPayload
成功收敛 钱包、区域播报和房间 IM 都成功后,整批 draw records 标记 grantedoutbox 标记 delivered processDrawOutbox 末尾调用 MarkLuckyGiftDrawsGrantedMarkLuckyGiftOutboxDelivered
失败退避 outbox 处理失败按指数退避重试;达到最大重试后标记失败。 markOutboxFailedluckyGiftBackoffMarkLuckyGiftOutboxRetryableMarkLuckyGiftOutboxFailed
已返奖但表现失败 钱包已加币后,如果 IM 或播报失败,不把 draw 改成 failed避免覆盖账务事实。 markOutboxFailed(... alreadyCredited=true) 分支;MarkLuckyGiftDrawsFailed 注释约束 only pending。

7. 批量抽奖产品规则

功能点 产品规则 实现依据
N 次独立随机 gift_count=N 时内部执行 N 次抽奖,每次保留独立 draw record。 executeOptimizedLuckyGiftDrawBatch 循环调用 stageLuckyBatchDrawinsertLuckyDrawRecords 批量写每条明细。
一次聚合发放 批量抽奖只产生一条聚合 LuckyGiftDrawn outbox钱包只入账一次。 insertLuckyAggregateDrawOutbox payload 包含 draw_id 和全部 draw_idsprocessDrawOutbox 对聚合金额调用一次钱包。
聚合结果展示 批量响应展示总基础奖励、总气氛奖励、总活动补贴、总有效奖励、总倍率;总倍率是子抽倍率相加,不是平均倍率。 luckyAggregateDrawResults
批量性能 规则、用户状态、RTP 窗口、奖池、预算和风控行只锁一次,循环内推进内存状态,事务尾部批量写回。 executeOptimizedLuckyGiftDrawBatchstageLuckyBatchDrawpersistLuckyBatchDraw
批量幂等 批量子抽命令使用 command_id#序号;重试读回已提交子抽,部分存在时报冲突。 luckyDrawSubCommandIDcollectLuckyBatchDrawsByCommand

8. 后台配置功能

功能点 产品规则 实现依据
后台菜单 运营后台有“幸运礼物”菜单,权限码为 lucky-gift:viewlucky-gift:update server/admin/migrations/017_lucky_gift_navigation.sqlserver/admin/internal/repository/seed.go;前端 routes.js
查看配置 管理员可按 pool_id 查看奖池配置;未配置时返回 disabled 默认草稿。 GET /admin/activity/lucky-gifts/v2/configHandler.GetLuckyGiftConfigService.GetConfig;前端 getLuckyGiftConfig
新增奖池 前端允许输入新奖池 ID并以默认草稿打开配置抽屉。 LuckyGiftConfigPage.jsxAddPoolDialoguseLuckyGiftPage.submitAddPool
发布配置 管理员保存后发布为新的不可变规则版本。 PUT /admin/activity/lucky-gifts/v2/configHandler.UpsertLuckyGiftConfigService.UpsertConfigRepository.PublishLuckyGiftRuleConfig
奖池列表 管理员可查看已有奖池列表,每个奖池展示最新版本。 GET /admin/activity/lucky-gifts/v2/configsHandler.ListLuckyGiftConfigsRepository.ListLuckyGiftRuleConfigs;前端 listLuckyGiftConfigs
基础控制字段 后台可配置启用状态、RTP、入池比例、结算窗口流水、波动带、参考价格、新手/正常等价抽数。 前端 LuckyGiftConfigDrawer 的“基础控制”;后端 configRequestLuckyGiftRuleConfig 字段。
风控上限字段 后台可配置单次、用户小时、用户每日、设备每日、房间小时、主播每日上限。 前端 LuckyGiftConfigDrawer 的“风控上限”;后端 validateRuleConfig
阶段奖档编辑 后台可编辑新手、正常、高阶三个阶段的奖档 ID、倍率、概率、预算来源、广播级别、高水位和启用状态其中 reward_source 当前用于配置和校验,运行随机只消费 base_rtp StageTierEditorTierRow;后端 configToProtoconfigFromProto;运行转换见 luckyRuntimeTiersFromRuleStages
概率纠正工具 前端提供“纠正概率”工具,按目标 RTP 调整阶段概率,帮助运营形成可发布配置。 LuckyGiftConfigDrawer 调用 correctStageTierProbabilities;实现见 configModel.js
表单校验 前端提前校验概率闭合、RTP 波动带、0x 档、基础 RTP 档和高倍高水位;后端仍做最终校验。 前端 luckyGiftConfigFormSchema;后端 validateRuleConfigvalidateRuleStagevalidateRuleStageExpectedRTP
随机模拟测试 后台配置页提供随机模拟,可输入用户数、房间数、单抽花费范围和抽奖次数范围,输出概览并下载 CSV。 LuckyGiftSimulationPanel.jsxrunLuckyGiftSimulation

9. 后台审计和数据

功能点 产品规则 实现依据
抽奖明细查询 后台接口支持按 pool_id/gift_id/user_id/room_id/status 分页查询抽奖明细。 GET /admin/activity/lucky-gifts/v2/drawsHandler.ListLuckyGiftDrawsRepository.ListLuckyGiftDrawsluckyDrawWhereClause
抽奖汇总 后台接口和页面展示总抽数、参与用户、参与房间、消耗金币、返奖金币、实际 RTP、pending/granted/failed 数。 GET /admin/activity/lucky-gifts/v2/draw-summaryRepository.GetLuckyGiftDrawSummary;前端 LuckyGiftDrawSummaryPanel
审计快照 每条抽奖明细记录命中奖档、过滤原因、奖池快照、RTP 快照、发放状态和钱包交易 ID。 lucky_draw_records 表;luckyDrawRecordSnapshotsListLuckyGiftDraws
统计大盘口径 统计服务从 RoomGiftSent 事件识别带 pool_id 的礼物,累计幸运礼物流水和去重付费人数。 RoomGiftSent.pool_idstatistics-service/internal/storage/mysql/repository.goConsumeRoomGiftstat_lucky_gift_day_payers
发放状态 抽奖记录状态分为 pendinggrantedfailed;无奖励且无表现副作用的记录可直接 granted domain.StatusPending/Granted/FailedluckyInitialRewardStatusMarkLuckyGiftDrawsGrantedMarkLuckyGiftDrawsFailed

10. 接口清单

接口 功能点 实现依据
App HTTP POST /api/v1/activities/lucky-gifts/check 送礼前资格检查。 activityapi.Handler.checkLuckyGifthttproutes.registerTaskRoutes
App HTTP POST /api/v1/rooms/gift/send 发礼物并在成功扣费后触发幸运礼物抽奖。 httproutes.registerRoomRoutesroom-serviceSendGift
内部 gRPC LuckyGiftService.CheckLuckyGift 房间链路或 gateway 查询奖池可用性。 api/proto/activity/v1/activity.protoLuckyGiftServer.CheckLuckyGift
内部 gRPC LuckyGiftService.ExecuteLuckyGiftDraw 扣费成功后的抽奖命令。 LuckyGiftMetaLuckyGiftDrawResultLuckyGiftServer.ExecuteLuckyGiftDraw
内部 gRPC WalletService.CreditLuckyGiftReward 抽奖 outbox worker 调钱包入账。 api/proto/wallet/v1/wallet.proto;钱包 CreditLuckyGiftReward
后台 HTTP GET /admin/activity/lucky-gifts/v2/config 获取单个奖池配置。 Handler.GetLuckyGiftConfig
后台 HTTP PUT /admin/activity/lucky-gifts/v2/config 发布奖池配置。 Handler.UpsertLuckyGiftConfig
后台 HTTP GET /admin/activity/lucky-gifts/v2/configs 获取奖池配置列表。 Handler.ListLuckyGiftConfigs
后台 HTTP GET /admin/activity/lucky-gifts/v2/draws 查询抽奖明细。 Handler.ListLuckyGiftDraws
后台 HTTP GET /admin/activity/lucky-gifts/v2/draw-summary 查询抽奖汇总。 Handler.GetLuckyGiftDrawSummary

11. 异常和边界

场景 产品处理 实现依据
奖池未配置 检查接口返回不可用;后台返回可编辑 disabled 草稿。 Repository.CheckLuckyGift 返回 not_configuredService.GetConfig 返回 DefaultRuleConfig
奖池关闭 显式传 pool_id 的送礼在扣费前失败;抽奖入口也会拒绝。 SendGiftCheckLuckyGift 分支;executeSingleLuckyGiftDrawexecuteOptimizedLuckyGiftDrawBatch
抽奖失败 整笔送礼失败,不落普通礼物事实,依赖钱包扣费幂等处理重试。 SendGift 在抽奖失败时直接返回错误,未进入 Room Cell 事件落地。
outbox payload 损坏 无法解析时走失败收敛,不无限重试。 decodeDrawOutboxPayloadmarkOutboxFailed
worker 依赖缺失 钱包、房间发布或区域播报依赖缺失时outbox 进入重试或失败,不直接丢弃。 processDrawOutboxwallet == nilpublisher == nilbroadcaster == nil 分支。
小额礼物 阶段按累计流水折算,奖档金额按实际单抽金额缩放,风控上限保持绝对金币。 luckyRuntimeConfig 注释和实现;单测 TestLuckyRuntimeConfigScalesTierRewardsByActualSpendButKeepsRiskCapsAbsolute
UTC 窗口 付费时间、风控小时/日、活动预算日均使用 UTC epoch ms。 Service.DrawPaidAtMS 使用 UTC().UnixMilli()getLuckyRiskCountersgetLuckyActivityRemaining 使用 time.UnixMilli(...).UTC()

12. 当前不作为已实现产品能力

能力 当前状态 实现依据
运行时按 RTP 缺口动态调概率 不支持。RTP 只用于发布校验和窗口观察。 selectLuckyCandidate 只使用配置权重并过滤不可支付候选。
多目标送礼抽奖拆分 不支持。当前房间送礼仅支持 target_type=user 且一个目标用户。 SendGift 对非 user 和多目标返回 InvalidArgument
后台配置三层奖池权重 v2 后台当前不暴露,运行侧使用保守默认权重。 luckyRuntimeConfigFromRuleConfig 固定 PlatformPoolWeightPPM=200000RoomPoolWeightPPM=300000GiftPoolWeightPPM=500000
按奖档广播级别精细化播报 配置字段存在,但当前区域播报阈值由代码统一判断为有区域、有效奖励、倍率不低于 1x。 LuckyGiftRuleTier.broadcast_levelshouldPublishLuckyGiftRegionBroadcast
reward_source 生成活动补贴或表现候选 不支持。当前 v2 运行候选只读取 base_rtp 奖档,活动补贴和表现来源不会被转换成随机候选。 luckyRuntimeTiersFromRuleStages 对非 SourceBaseRTP 的奖档 continue

13. 验证依据

验证项 覆盖功能 实现依据
Activity 配置校验单测 默认配置、ppm 单位、防错、概率闭合、0x 档、风控上限、阶段阈值。 services/activity-service/internal/service/luckygift/config_test.go
Activity worker 单测 钱包入账、房间 IM、批量一次发奖并批量 granted。 services/activity-service/internal/service/luckygift/service_test.go
Activity 抽奖算法单测 真实金额缩放、等价抽数、批量拆分、聚合倍数、候选过滤、不可支付回退 0x。 services/activity-service/internal/storage/mysql/lucky_gift_repository_test.go
Room 送礼链路单测 送礼后返回 lucky_gift,并把扣费后的金额传给抽奖。 services/room-service/internal/room/service/lucky_gift_test.go
Gateway 检查接口单测 App 检查接口正确转发 room/gift/pool/user 参数。 services/gateway-service/internal/transport/http/activityapi/lucky_gift_handler_test.go
Wallet 返奖幂等单测 同一幸运礼物返奖命令只产生一条钱包交易和 outbox。 services/wallet-service/internal/service/wallet/service_test.goTestCreditLuckyGiftRewardIsIdempotent
后台单位转换单测 百分比和倍率在后台 HTTP DTO 与 proto ppm 之间正确转换。 server/admin/internal/modules/luckygift/handler_test.go
前端表单与模拟单测 配置 payload、阶段 RTP、概率纠正和模拟确定性。 hyapp-admin-platform/src/features/lucky-gift/*test*