# LotteryService 抽奖服务类文档 ## 概述 `LotteryService` 是抽奖系统的核心服务类,负责处理抽奖的核心业务逻辑,包括抽奖执行、奖品管理、活动状态管理等功能。 **最新优化**: 系统现已支持三种抽奖方式的统一处理: - **即抽即中** (LOTTERY_TYPE_INSTANT = 1): 用户抽奖后立即出结果 - **定时开奖** (LOTTERY_TYPE_TIME = 2): 到指定时间统一开奖 - **按人数开奖** (LOTTERY_TYPE_PEOPLE = 3): 达到指定人数后开奖 所有抽奖方式都采用统一的"先记录参与,再更新结果"的流程,确保数据一致性和可追溯性。 ## 类结构 ```php namespace app\common\Service\lottery; class LotteryService { // ============ 核心抽奖方法 ============ public static function drawLottery($activityId, $userId, $triggerType, $triggerOrderId, $triggerAmount) // ============ 三种抽奖方式处理方法 ============ // 统一处理入口 private static function handleLotteryByType($activity, $userId, $triggerType, $triggerOrderId, $triggerAmount) private static function createParticipationRecord($activity, $userId, $triggerType, $triggerOrderId, $triggerAmount) // 即抽即中 private static function executeInstantDraw($drawRecord, $activity) // 定时开奖 private static function handleTimeLottery($drawRecord, $activity) public static function processScheduledLotteries() // 定时任务入口 private static function executeScheduledDraw($activity) private static function executeDrawForRecord($drawRecord, $activity) // 按人数开奖 private static function handlePeopleLottery($drawRecord, $activity) // ============ 奖品管理方法 ============ public static function hasPrizeStock(LotteryPrize $prize, $quantity) public static function decreasePrizeStock(LotteryPrize $prize, $quantity) public static function getAvailableExchangeCode(LotteryPrize $prize) public static function markExchangeCodeUsed(LotteryPrize $prize, $code) public static function getValidPrizes($activityId) public static function isPrizeUnlocked(LotteryPrize $prize, $currentPeopleCount) // ============ 活动状态管理方法 ============ public static function isActivityRunning(LotteryActivity $activity) public static function isActivityEnded(LotteryActivity $activity) public static function isActivityNotStarted(LotteryActivity $activity) public static function isActivitySuspended(LotteryActivity $activity) public static function isActivityCancelled(LotteryActivity $activity) public static function isInDrawTime(LotteryActivity $activity) public static function getRunningActivities() public static function getNotStartedActivities() public static function getEndedActivities() public static function getDisplayableActivities() public static function isValidActivityStatus(LotteryActivity $activity) public static function isValidLotteryType(LotteryActivity $activity) // ============ 用户机会管理方法 ============ public static function getUserChances($activityId, $userId) // ============ 抽奖算法和辅助方法 ============ private static function executeLotteryAlgorithm($prizes) private static function buildWinInfo($prize) private static function buildPrizeValue($prize) private static function updateActivityStats($activity, $isWin) private static function buildDrawResult($drawRecord, $prize, $winRecord) } ``` ## 核心方法详解 ### 1. drawLottery() - 执行抽奖 **功能**: 执行抽奖的核心方法,包含完整的抽奖流程验证和处理。 **参数**: - `$activityId` (int): 活动ID - `$userId` (int): 用户ID - `$triggerType` (int): 触发类型,默认1 - `$triggerOrderId` (int|null): 触发订单ID - `$triggerAmount` (float|null): 触发金额 **返回值**: `array` 抽奖结果 **异常**: `Exception` 各种验证失败异常 **使用示例**: ```php try { $result = LotteryService::drawLottery(1, 123, 1, 456, 100.00); echo "抽奖成功: " . json_encode($result); } catch (Exception $e) { echo "抽奖失败: " . $e->getMessage(); } ``` **抽奖流程**: 1. 验证活动有效性 2. 验证抽奖时间 3. 验证用户资格 4. 检查用户抽奖机会 5. 检查用户参与次数限制 6. 防重复抽奖检查 7. 使用Redis锁防止并发 8. **根据开奖方式分流处理**(新增) 9. 返回抽奖结果 **三种抽奖方式处理**: - **即抽即中**: 立即执行抽奖算法并返回结果 - **定时开奖**: 创建参与记录,等待定时任务处理 - **按人数开奖**: 检查人数,达标则触发开奖 ### 2. processDrawLottery() - 处理抽奖核心逻辑 **功能**: 私有方法,处理抽奖的核心业务逻辑。 **流程**: 1. 开启数据库事务 2. 获取可用奖品列表 3. 执行抽奖算法 4. 减少奖品库存 5. 消耗用户抽奖机会 6. 创建抽奖记录 7. 创建中奖记录(如果中奖) 8. 自动发放奖品(如果配置为自动发放) 9. 更新活动统计 10. 提交事务 11. 返回抽奖结果 ### 3. executeLotteryAlgorithm() - 执行抽奖算法 **功能**: 基于概率权重的抽奖算法实现。 **算法原理**: - 计算所有奖品的总概率 - 生成随机数 - 按概率权重选择奖品 - 保底返回第一个奖品(通常是未中奖) **使用示例**: ```php $prizes = LotteryService::getValidPrizes($activityId); $selectedPrize = LotteryService::executeLotteryAlgorithm($prizes); ``` ## 三种抽奖方式详解 ### 1. 统一处理入口 #### handleLotteryByType() - 根据开奖方式分流处理 **功能**: 根据活动的开奖方式(lottery_type)分流到不同的处理逻辑。 **流程**: 1. 调用 `createParticipationRecord()` 创建参与记录 2. 根据 `lottery_type` 分流处理: - `LOTTERY_TYPE_INSTANT` → `executeInstantDraw()` - `LOTTERY_TYPE_TIME` → `handleTimeLottery()` - `LOTTERY_TYPE_PEOPLE` → `handlePeopleLottery()` #### createParticipationRecord() - 创建参与记录 **功能**: 所有抽奖方式的统一入口,负责消耗用户机会并创建抽奖记录。 **流程**: 1. 开启数据库事务 2. 消耗用户抽奖机会 3. 创建抽奖记录(状态:`DRAW_STATUS_PARTICIPATED`) 4. 提交事务 5. 返回抽奖记录对象 ### 2. 即抽即中 (LOTTERY_TYPE_INSTANT = 1) #### executeInstantDraw() - 执行即抽即中抽奖 **功能**: 立即执行抽奖并返回结果。 **流程**: 1. 获取可用奖品列表 2. 执行抽奖算法选择奖品 3. 减少奖品库存 4. 更新抽奖记录状态和中奖信息 5. 如果中奖,创建中奖记录 6. 自动发放奖品(如果配置为自动发放) 7. 更新活动统计 8. 返回抽奖结果 **使用场景**: 适用于需要立即反馈结果的抽奖活动。 ### 3. 定时开奖 (LOTTERY_TYPE_TIME = 2) #### handleTimeLottery() - 处理定时抽奖参与 **功能**: 处理用户参与定时开奖活动。 **流程**: 1. 验证开奖时间是否已过期 2. 返回等待开奖的状态信息 #### processScheduledLotteries() - 定时任务处理入口 **功能**: 公开方法,供定时任务调用,批量处理到期的定时开奖活动。 **流程**: 1. 查询所有到达开奖时间的活动 2. 为每个活动调用 `executeScheduledDraw()` 3. 更新活动状态为已结束 4. 返回处理的记录数量 #### executeScheduledDraw() - 执行定时开奖 **功能**: 为指定活动执行定时开奖处理。 **流程**: 1. 获取所有参与记录(状态为 `DRAW_STATUS_PARTICIPATED`) 2. 为每个参与记录调用 `executeDrawForRecord()` #### executeDrawForRecord() - 为单个记录执行开奖 **功能**: 为单个抽奖记录执行开奖逻辑。 **流程**: 1. 获取可用奖品 2. 执行抽奖算法 3. 减少奖品库存 4. 更新抽奖记录状态 5. 创建中奖记录(如果中奖) 6. 更新活动统计 **定时任务配置示例**: ```bash # 每分钟检查一次定时开奖活动 * * * * * /usr/bin/php /path/to/project/think lottery:process-scheduled ``` ### 4. 按人数开奖 (LOTTERY_TYPE_PEOPLE = 3) #### handlePeopleLottery() - 处理按人数抽奖 **功能**: 处理按人数开奖的抽奖活动。 **流程**: 1. 查询当前参与人数 2. 检查是否达到开奖人数 (`lottery_people_num`) 3. 如果达到人数,触发开奖处理 4. 如果未达到,返回等待状态和剩余人数信息 **返回信息**: - 当前参与人数 - 需要的参与人数 - 是否达到开奖条件 - 状态提示信息 **使用场景**: 适用于需要聚集一定人数才开奖的活动,增加参与积极性。 ## 抽奖状态管理 ### 抽奖状态枚举 ```php const DRAW_STATUS_PARTICIPATED = 1; // 已参与(等待开奖) const DRAW_STATUS_WIN = 2; // 已中奖 const DRAW_STATUS_NO_WIN = 3; // 未中奖 ``` ### 状态流转 1. **初始状态**: 创建记录时状态为 `DRAW_STATUS_PARTICIPATED` 2. **开奖后**: 根据结果更新为 `DRAW_STATUS_WIN` 或 `DRAW_STATUS_NO_WIN` 3. **异常处理**: 发生错误时自动设置为 `DRAW_STATUS_NO_WIN` ## 奖品管理方法 ### 1. hasPrizeStock() - 检查奖品库存 **功能**: 检查奖品库存是否充足。 **参数**: - `$prize` (LotteryPrize): 奖品对象 - `$quantity` (int): 需要数量,默认1 **返回值**: `bool` 库存是否充足 ### 2. decreasePrizeStock() - 减少奖品库存 **功能**: 减少奖品库存并增加中奖次数。 **参数**: - `$prize` (LotteryPrize): 奖品对象 - `$quantity` (int): 减少数量,默认1 **返回值**: `bool` 操作是否成功 ### 3. getAvailableExchangeCode() - 获取可用兑换码 **功能**: 获取奖品中可用的兑换码。 **参数**: - `$prize` (LotteryPrize): 奖品对象 **返回值**: `string|null` 可用兑换码或null ### 4. markExchangeCodeUsed() - 标记兑换码已使用 **功能**: 将兑换码标记为已使用状态。 **参数**: - `$prize` (LotteryPrize): 奖品对象 - `$code` (string): 兑换码 **返回值**: `bool` 操作是否成功 ### 5. getValidPrizes() - 获取有效奖品 **功能**: 获取活动的有效奖品列表(库存大于0且状态正常)。 **参数**: - `$activityId` (int): 活动ID **返回值**: `Collection` 奖品集合 ### 6. isPrizeUnlocked() - 检查奖品是否已解锁 **功能**: 检查奖品是否已按人数解锁。 **参数**: - `$prize` (LotteryPrize): 奖品对象 - `$currentPeopleCount` (int): 当前参与人数 **返回值**: `bool` 是否已解锁 ## 活动状态管理方法 ### 1. isActivityRunning() - 检查活动是否正在进行 **功能**: 检查活动是否处于进行中状态。 **参数**: - `$activity` (LotteryActivity): 活动对象 **返回值**: `bool` 是否正在进行 ### 2. isActivityEnded() - 检查活动是否已结束 **功能**: 检查活动是否已结束。 **参数**: - `$activity` (LotteryActivity): 活动对象 **返回值**: `bool` 是否已结束 ### 3. isActivityNotStarted() - 检查活动是否未开始 **功能**: 检查活动是否还未开始。 **参数**: - `$activity` (LotteryActivity): 活动对象 **返回值**: `bool` 是否未开始 ### 4. isActivitySuspended() - 检查活动是否已暂停 **功能**: 检查活动是否已暂停。 **参数**: - `$activity` (LotteryActivity): 活动对象 **返回值**: `bool` 是否已暂停 ### 5. isActivityCancelled() - 检查活动是否已取消 **功能**: 检查活动是否已取消。 **参数**: - `$activity` (LotteryActivity): 活动对象 **返回值**: `bool` 是否已取消 ### 6. isInDrawTime() - 检查是否在抽奖时间内 **功能**: 检查当前时间是否在活动的抽奖时间范围内。 **参数**: - `$activity` (LotteryActivity): 活动对象 **返回值**: `bool` 是否在抽奖时间内 ### 7. getRunningActivities() - 获取正在进行的活动 **功能**: 获取所有正在进行的抽奖活动。 **返回值**: `Collection` 活动集合 ### 8. getNotStartedActivities() - 获取未开始的活动 **功能**: 获取所有未开始的抽奖活动。 **返回值**: `Collection` 活动集合 ### 9. getEndedActivities() - 获取已结束的活动 **功能**: 获取所有已结束的抽奖活动。 **返回值**: `Collection` 活动集合 ### 10. getDisplayableActivities() - 获取可显示的活动 **功能**: 获取可显示的活动(排除逻辑状态)。 **返回值**: `Collection` 活动集合 ### 11. isValidActivityStatus() - 验证活动状态 **功能**: 验证活动状态是否有效。 **参数**: - `$activity` (LotteryActivity): 活动对象 **返回值**: `bool` 状态是否有效 ### 12. isValidLotteryType() - 验证开奖方式 **功能**: 验证开奖方式是否有效。 **参数**: - `$activity` (LotteryActivity): 活动对象 **返回值**: `bool` 开奖方式是否有效 ## 用户机会管理方法 ### getUserChances() - 获取用户抽奖机会 **功能**: 获取用户在指定活动中的抽奖机会信息。 **参数**: - `$activityId` (int): 活动ID - `$userId` (int): 用户ID **返回值**: `array` 机会信息 **返回格式**: ```php [ 'total_chances' => 10, // 总机会数 'used_chances' => 3, // 已使用机会数 'remain_chances' => 7, // 剩余机会数 'last_get_time' => 1234567890, // 最后获得时间 'last_use_time' => 1234567890 // 最后使用时间 ] ``` ## 私有辅助方法 ### 1. validateUserQualification() - 验证用户资格 **功能**: 验证用户是否符合活动参与资格。 **验证内容**: - 用户群体限制(全部会员/会员等级/会员标签) ### 2. checkUserLevel() - 检查用户等级 **功能**: 检查用户等级是否符合限制条件。 ### 3. checkUserTag() - 检查用户标签 **功能**: 检查用户标签是否符合限制条件。 ### 4. checkUserDrawLimit() - 检查用户抽奖次数限制 **功能**: 检查用户是否已达到参与次数上限。 ### 5. hasDrawnForOrder() - 检查订单是否已抽奖 **功能**: 检查用户是否已为指定订单进行过抽奖。 ### 6. buildWinInfo() - 构建中奖信息 **功能**: 构建中奖记录的详细信息。 ### 7. buildPrizeValue() - 构建奖品价值信息 **功能**: 根据奖品类型构建奖品价值信息。 ### 8. autoDeliverPrize() - 自动发放奖品 **功能**: 根据奖品类型自动发放奖品。 ### 9. deliverCoupon() - 发放优惠券 **功能**: 发放优惠券类型的奖品。 ### 10. deliverRedPacket() - 发放红包 **功能**: 发放红包类型的奖品。 ### 11. deliverExchangeCode() - 发放兑换码 **功能**: 发放兑换码类型的奖品。 ### 12. deliverGoods() - 发放商城商品 **功能**: 发放商城商品类型的奖品。 ### 13. updateActivityStats() - 更新活动统计 **功能**: 更新活动的抽奖和中奖统计。 ### 14. buildDrawResult() - 构建抽奖结果 **功能**: 构建返回给客户端的抽奖结果。 ## 使用示例 ### 1. 基本抽奖流程 ```php // 用户抽奖 try { $result = LotteryService::drawLottery(1, 123); if ($result['is_win']) { echo "恭喜中奖!奖品:" . $result['prize']['name']; } else { echo "很遗憾,未中奖"; } } catch (Exception $e) { echo "抽奖失败:" . $e->getMessage(); } ``` ### 2. 检查活动状态 ```php $activity = LotteryActivity::find(1); if (LotteryService::isActivityRunning($activity)) { echo "活动正在进行中"; } elseif (LotteryService::isActivityEnded($activity)) { echo "活动已结束"; } elseif (LotteryService::isActivityNotStarted($activity)) { echo "活动未开始"; } ``` ### 3. 获取用户机会 ```php $chances = LotteryService::getUserChances(1, 123); echo "剩余抽奖机会:" . $chances['remain_chances']; ``` ### 4. 检查奖品库存 ```php $prize = LotteryPrize::find(1); if (LotteryService::hasPrizeStock($prize, 1)) { echo "奖品库存充足"; } else { echo "奖品库存不足"; } ``` ## 注意事项 ### 1. 并发控制 - 使用Redis锁防止并发抽奖 - 锁超时时间设置为10秒 - 确保锁的释放 ### 2. 事务处理 - 抽奖过程使用数据库事务 - 确保数据一致性 - 异常时自动回滚 ### 3. 库存管理 - 抽奖前检查库存 - 抽奖成功后立即减少库存 - 防止超发奖品 ### 4. 时间验证 - 验证活动时间范围 - 验证抽奖时间限制 - 考虑时区问题 ### 5. 用户资格 - 验证用户群体限制 - 检查参与次数限制 - 防止重复参与 ## 错误处理 ### 常见异常 1. `活动不存在或未开始` - 活动状态验证失败 2. `不在抽奖时间内` - 时间验证失败 3. `用户不符合参与条件` - 用户资格验证失败 4. `没有抽奖机会` - 机会不足 5. `已达到参与次数上限` - 次数限制 6. `该订单已参与过抽奖` - 重复参与 7. `操作太频繁,请稍后再试` - 并发控制 8. `暂无可抽取的奖品` - 奖品库存不足 9. `奖品库存不足` - 库存验证失败 10. `抽奖机会使用失败` - 机会使用失败 ### 异常处理建议 - 在API层捕获异常并返回友好提示 - 记录详细的错误日志 - 监控异常频率和类型 ## 性能优化 ### 1. 数据库优化 - 使用索引优化查询 - 减少不必要的查询 - 使用批量操作 ### 2. 缓存优化 - 缓存活动信息 - 缓存奖品信息 - 使用Redis锁 ### 3. 算法优化 - 优化抽奖算法 - 减少循环次数 - 使用高效的数据结构 ## 扩展建议 ### 1. 抽奖算法扩展 - 支持更多抽奖算法 - 支持动态概率调整 - 支持保底机制 ### 2. 奖品类型扩展 - 支持更多奖品类型 - 支持组合奖品 - 支持条件奖品 ### 3. 活动类型扩展 - 支持更多活动类型 - 支持活动组合 - 支持活动联动 ### 4. 监控和统计 - 添加详细的统计功能 - 实现实时监控 - 支持数据导出