LotteryService文档说明.md 18 KB

LotteryService 抽奖服务类文档

概述

LotteryService 是抽奖系统的核心服务类,负责处理抽奖的核心业务逻辑,包括抽奖执行、奖品管理、活动状态管理等功能。

最新优化: 系统现已支持三种抽奖方式的统一处理:

  • 即抽即中 (LOTTERY_TYPE_INSTANT = 1): 用户抽奖后立即出结果
  • 定时开奖 (LOTTERY_TYPE_TIME = 2): 到指定时间统一开奖
  • 按人数开奖 (LOTTERY_TYPE_PEOPLE = 3): 达到指定人数后开奖

所有抽奖方式都采用统一的"先记录参与,再更新结果"的流程,确保数据一致性和可追溯性。

类结构

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 各种验证失败异常

使用示例:

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() - 执行抽奖算法

功能: 基于概率权重的抽奖算法实现。

算法原理:

  • 计算所有奖品的总概率
  • 生成随机数
  • 按概率权重选择奖品
  • 保底返回第一个奖品(通常是未中奖)

使用示例:

$prizes = LotteryService::getValidPrizes($activityId);
$selectedPrize = LotteryService::executeLotteryAlgorithm($prizes);

三种抽奖方式详解

1. 统一处理入口

handleLotteryByType() - 根据开奖方式分流处理

功能: 根据活动的开奖方式(lottery_type)分流到不同的处理逻辑。

流程:

  1. 调用 createParticipationRecord() 创建参与记录
  2. 根据 lottery_type 分流处理:
    • LOTTERY_TYPE_INSTANTexecuteInstantDraw()
    • LOTTERY_TYPE_TIMEhandleTimeLottery()
    • LOTTERY_TYPE_PEOPLEhandlePeopleLottery()

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. 更新活动统计

定时任务配置示例:

# 每分钟检查一次定时开奖活动
* * * * * /usr/bin/php /path/to/project/think lottery:process-scheduled

4. 按人数开奖 (LOTTERY_TYPE_PEOPLE = 3)

handlePeopleLottery() - 处理按人数抽奖

功能: 处理按人数开奖的抽奖活动。

流程:

  1. 查询当前参与人数
  2. 检查是否达到开奖人数 (lottery_people_num)
  3. 如果达到人数,触发开奖处理
  4. 如果未达到,返回等待状态和剩余人数信息

返回信息:

  • 当前参与人数
  • 需要的参与人数
  • 是否达到开奖条件
  • 状态提示信息

使用场景: 适用于需要聚集一定人数才开奖的活动,增加参与积极性。

抽奖状态管理

抽奖状态枚举

const DRAW_STATUS_PARTICIPATED = 1;     // 已参与(等待开奖)
const DRAW_STATUS_WIN = 2;              // 已中奖
const DRAW_STATUS_NO_WIN = 3;           // 未中奖

状态流转

  1. 初始状态: 创建记录时状态为 DRAW_STATUS_PARTICIPATED
  2. 开奖后: 根据结果更新为 DRAW_STATUS_WINDRAW_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 机会信息

返回格式:

[
    '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. 基本抽奖流程

// 用户抽奖
try {
    $result = LotteryService::drawLottery(1, 123);
    if ($result['is_win']) {
        echo "恭喜中奖!奖品:" . $result['prize']['name'];
    } else {
        echo "很遗憾,未中奖";
    }
} catch (Exception $e) {
    echo "抽奖失败:" . $e->getMessage();
}

2. 检查活动状态

$activity = LotteryActivity::find(1);
if (LotteryService::isActivityRunning($activity)) {
    echo "活动正在进行中";
} elseif (LotteryService::isActivityEnded($activity)) {
    echo "活动已结束";
} elseif (LotteryService::isActivityNotStarted($activity)) {
    echo "活动未开始";
}

3. 获取用户机会

$chances = LotteryService::getUserChances(1, 123);
echo "剩余抽奖机会:" . $chances['remain_chances'];

4. 检查奖品库存

$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. 监控和统计

  • 添加详细的统计功能
  • 实现实时监控
  • 支持数据导出