Browse Source

fix: 抽奖记录

super-yimizi 1 month ago
parent
commit
7575253822

+ 36 - 39
application/api/controller/Lottery.php

@@ -195,7 +195,7 @@ class Lottery extends Api
 
         $activityId = $this->request->post('lottery_id/d');
         $page = $this->request->param('page/d', 1);
-        $limit = $this->request->param('limit/d', 20);
+        $pageSize = $this->request->param('pageSize/d', 20);
 
         $userId = $this->auth->id;
         $where = ['user_id' => $userId];
@@ -203,41 +203,37 @@ class Lottery extends Api
             $where['activity_id'] = $activityId;
         }
 
-        $records = LotteryRecordService::getUserDrawRecords($userId, $activityId, $page, $limit);
-
-        $list = [];
-        foreach ($records as $record) {
-            $item = [
-                'id' => $record->id,
-                'activity_id' => $record->activity_id,
-                'activity_name' => $record->activity->name ?? '',
-                'prize_id' => $record->prize_id,
-                'prize_name' => $record->prize->name ?? '',
-                'prize_type' => $record->prize->type ?? 0,
-                'prize_image' => $record->prize->image ?? '',
-                'is_win' => $record->is_win,
-                'draw_time' => $record->draw_time,
-                'trigger_type_text' => $record->trigger_type_text
-            ];
-
-            // 如果中奖,获取中奖记录详情
-            if ($record->is_win && $record->winRecord) {
-                $item['win_record'] = [
-                    'id' => $record->winRecord->id,
-                    'deliver_status' => $record->winRecord->deliver_status,
-                    'deliver_status_text' => $record->winRecord->deliver_status_text,
-                    'deliver_time' => $record->winRecord->deliver_time,
-                    'exchange_code' => $record->winRecord->exchange_code
-                ];
-            }
-
-            $list[] = $item;
-        }
-
-        $this->success('获取成功', [
-            'list' => $list,
-            'total' => LotteryRecordService::getUserDrawCount($activityId, $userId)
-        ]);
+        $records = LotteryRecordService::getUserDrawRecords($userId, $activityId, $page, $pageSize);
+
+    
+        // foreach ($records as $record) {
+        //     $item = [
+        //         'id' => $record->id,
+        //         'activity_id' => $record->activity_id,
+        //         'activity_name' => $record->activity->name ?? '',
+        //         'prize_id' => $record->prize_id,
+        //         'prize_name' => $record->prize->name ?? '',
+        //         'prize_type' => $record->prize->type ?? 0,
+        //         'prize_image' => $record->prize->image ?? '',
+        //         'is_win' => $record->is_win,
+        //         'draw_time' => $record->draw_time,
+        //         'trigger_type_text' => $record->trigger_type_text
+        //     ];
+
+        //     // 如果中奖,获取中奖记录详情
+        //     if ($record->is_win && $record->winRecord) {
+        //         $item['win_record'] = [
+        //             'id' => $record->winRecord->id,
+        //             'deliver_status' => $record->winRecord->deliver_status,
+        //             'deliver_status_text' => $record->winRecord->deliver_status_text,
+        //             'deliver_time' => $record->winRecord->deliver_time,
+        //             'exchange_code' => $record->winRecord->exchange_code
+        //         ];
+        //     }
+
+        // }
+
+        $this->success('获取成功',  $records);
     }
 
     /**
@@ -252,11 +248,11 @@ class Lottery extends Api
         }
 
         $page = $this->request->param('page/d', 1);
-        $limit = $this->request->param('limit/d', 20);
+        $pageSize = $this->request->param('pageSize/d', 20);
 
         $userId = $this->auth->id;
 
-        $records = LotteryRecordService::getUserWinRecords($userId, $page, $limit);
+        $records = LotteryRecordService::getUserWinRecords($userId, $page, $pageSize);
 
         $list = [];
         foreach ($records as $record) {
@@ -288,7 +284,8 @@ class Lottery extends Api
                     $item['goods_sku_id'] = $prizeValue['goods_sku_id'] ?? 0;
                     break;
             }
-
+            // 奖品图片
+            $item['prize_image'] = cdnurl($item['prize_image']);
             $list[] = $item;
         }
 

+ 12 - 0
application/common/Service/Lottery/LotteryActivityService.php

@@ -81,6 +81,18 @@ class LotteryActivityService
     }
 
     /**
+     * 获取当前正在进行的单个抽奖活动
+     * 假设同一时间段只有一个活动在进行
+     * 
+     * @return LotteryActivity|null
+     */
+    public static function getCurrentRunningActivity()
+    {
+        $activities = static::getRunningActivities();
+        return empty($activities) ? null : $activities[0];
+    }
+
+    /**
      * 获取未开始的活动
      */
     public static function getNotStartedActivities()

+ 218 - 25
application/common/Service/Lottery/LotteryChanceService.php

@@ -13,6 +13,7 @@ use think\Exception;
 use think\Db;
 use app\common\exception\BusinessException;
 use app\common\Enum\OrderEnum;
+use app\common\Service\Lottery\LotteryActivityService;
 /**
  * 抽奖机会服务类
  * 处理用户获得抽奖机会的逻辑
@@ -51,7 +52,7 @@ class LotteryChanceService
 
         // try {
             // 获取当前正在进行的单个抽奖活动
-            $activity = static::getCurrentRunningActivity();
+            $activity = LotteryActivityService::getCurrentRunningActivity();
             
             // 如果没有正在进行的活动,返回null
             if (!$activity) {
@@ -94,7 +95,7 @@ class LotteryChanceService
         
         try {
         // 获取所有正在进行的抽奖活动  一段时间 有且只有一个抽奖活动
-            $activities = LotteryService::getRunningActivities();
+            $activities = LotteryActivityService::getRunningActivities();
         
         foreach ($activities as $activity) {
             try {
@@ -137,7 +138,7 @@ class LotteryChanceService
 
         try {
             // 获取当前正在进行的单个抽奖活动
-            $activity = static::getCurrentRunningActivity();
+            $activity = LotteryActivityService::getCurrentRunningActivity();
             
             // 如果没有正在进行的活动,返回null
             if (!$activity) {
@@ -182,7 +183,7 @@ class LotteryChanceService
         
         try {
         // 获取所有正在进行的抽奖活动
-            $activities = LotteryService::getRunningActivities();
+            $activities = LotteryActivityService::getRunningActivities();
         
         foreach ($activities as $activity) {
             try {
@@ -295,19 +296,7 @@ class LotteryChanceService
         return $result;
     }
 
-    // ============ 活动查询方法 ============
 
-    /**
-     * 获取当前正在进行的单个抽奖活动
-     * 假设同一时间段只有一个活动在进行
-     * 
-     * @return LotteryActivity|null
-     */
-    public static function getCurrentRunningActivity()
-    {
-        $activities = LotteryService::getRunningActivities();
-        return empty($activities) ? null : $activities[0];
-    }
 
     // ============ 私有处理方法 ============
 
@@ -863,8 +852,8 @@ class LotteryChanceService
             ];
         }
 
-        // 获取机会获得记录
-        $getRecords = LotteryUserChanceRecord::getUserChanceRecords($activityId, $userId);
+        // 获取机会获得记录(使用服务类方法)
+        $getRecords = static::getUserChanceRecord($userId, $activityId);
 
         return [
             'total_chances' => $userChance->total_chances,
@@ -1233,7 +1222,12 @@ class LotteryChanceService
      */
     public static function getUserChanceRecords($activityId, $userId, $page = 1, $limit = 20)
     {
-        return LotteryUserChanceRecord::getUserChanceRecords($activityId, $userId, $page, $limit);
+        return LotteryUserChanceRecord::where('activity_id', $activityId)
+                   ->where('user_id', $userId)
+                   ->with(['condition', 'order', 'admin'])
+                   ->order('get_time desc')
+                   ->page($page, $limit)
+                   ->select();
     }
 
     /**
@@ -1244,7 +1238,32 @@ class LotteryChanceService
      */
     public static function getActivityChanceRecordStats($activityId)
     {
-        return LotteryUserChanceRecord::getActivityChanceStats($activityId);
+        $records = LotteryUserChanceRecord::where('activity_id', $activityId)->select();
+        
+        $stats = [
+            'total_records' => count($records),
+            'total_chances' => 0,
+            'type_stats' => [],
+        ];
+        
+        foreach ($records as $record) {
+            $stats['total_chances'] += $record->chances;
+            
+            $getType = $record->get_type;
+            if (!isset($stats['type_stats'][$getType])) {
+                $stats['type_stats'][$getType] = [
+                    'type' => $getType,
+                    'type_text' => LotteryEnum::getChanceGetTypeText($getType),
+                    'count' => 0,
+                    'chances' => 0,
+                ];
+            }
+            
+            $stats['type_stats'][$getType]['count']++;
+            $stats['type_stats'][$getType]['chances'] += $record->chances;
+        }
+        
+        return $stats;
     }
 
     /**
@@ -1254,9 +1273,48 @@ class LotteryChanceService
      * @param int $activityId 活动ID(可选)
      * @return array
      */
-    public static function getUserChanceRecordStats($userId, $activityId = null)
+    public static function getUserChanceRecord($userId, $activityId = null)
     {
-        return LotteryUserChanceRecord::getUserChanceStats($userId, $activityId);
+        $query = LotteryUserChanceRecord::where('user_id', $userId);
+        
+        if ($activityId) {
+            $query->where('activity_id', $activityId);
+        }
+        
+        $records = $query->select();
+        
+        $stats = [
+            'total_records' => count($records),
+            'total_chances' => 0,
+            'type_stats' => [],
+            'recent_records' => [],
+        ];
+        
+        foreach ($records as $record) {
+            $stats['total_chances'] += $record->chances;
+            
+            $getType = $record->get_type;
+            if (!isset($stats['type_stats'][$getType])) {
+                $stats['type_stats'][$getType] = [
+                    'type' => $getType,
+                    'type_text' => LotteryEnum::getChanceGetTypeText($getType),
+                    'count' => 0,
+                    'chances' => 0,
+                ];
+            }
+            
+            $stats['type_stats'][$getType]['count']++;
+            $stats['type_stats'][$getType]['chances'] += $record->chances;
+        }
+        
+        // 获取最近的记录
+        $stats['recent_records'] = LotteryUserChanceRecord::where('user_id', $userId)
+                                       ->with(['activity'])
+                                       ->order('get_time desc')
+                                       ->limit(5)
+                                       ->select();
+        
+        return $stats;
     }
 
     /**
@@ -1267,17 +1325,152 @@ class LotteryChanceService
      */
     public static function batchCreateChanceRecords($records)
     {
-        return LotteryUserChanceRecord::batchCreateRecords($records);
+        if (empty($records)) {
+            return false;
+        }
+        
+        // 为每条记录添加创建时间
+        foreach ($records as &$record) {
+            $record['createtime'] = time();
+            $record['get_time'] = $record['get_time'] ?? time();
+        }
+        
+        return LotteryUserChanceRecord::insertAll($records);
     }
 
     /**
      * 验证机会获取记录数据
      * 
      * @param array $data 记录数据
-     * @return bool|string
+     * @return bool|string true表示验证通过,string表示错误信息
      */
     public static function validateChanceRecord($data)
     {
-        return LotteryUserChanceRecord::validateRecord($data);
+        // 验证必填字段
+        if (empty($data['activity_id'])) {
+            return '活动ID不能为空';
+        }
+        
+        if (empty($data['user_id'])) {
+            return '用户ID不能为空';
+        }
+        
+        if (empty($data['get_type'])) {
+            return '获取类型不能为空';
+        }
+        
+        if (empty($data['chances']) || $data['chances'] <= 0) {
+            return '机会次数必须大于0';
+        }
+        
+        // 验证获取类型是否有效
+        if (!LotteryEnum::isValidChanceGetType($data['get_type'])) {
+            return '无效的获取类型';
+        }
+        
+        // 根据获取类型验证相关字段
+        switch ($data['get_type']) {
+            case LotteryEnum::CHANCE_GET_TYPE_BUY_GOODS:
+            case LotteryEnum::CHANCE_GET_TYPE_ORDER_AMOUNT:
+                if (empty($data['order_id'])) {
+                    return '订单ID不能为空';
+                }
+                break;
+                
+            case LotteryEnum::CHANCE_GET_TYPE_RECHARGE:
+                if (empty($data['recharge_amount']) || $data['recharge_amount'] <= 0) {
+                    return '充值金额必须大于0';
+                }
+                break;
+                
+            case LotteryEnum::CHANCE_GET_TYPE_ADMIN_GRANT:
+                if (empty($data['admin_id'])) {
+                    return '管理员ID不能为空';
+                }
+                break;
+        }
+        
+        return true;
+    }
+
+    /**
+     * 创建单条机会获取记录
+     * 
+     * @param array $data 记录数据
+     * @return bool|int 成功返回记录ID,失败返回false
+     */
+    public static function createChanceRecord($data)
+    {
+        // 验证数据
+        $validation = static::validateChanceRecord($data);
+        if ($validation !== true) {
+            throw new BusinessException($validation);
+        }
+        
+        // 设置默认值
+        $data['createtime'] = time();
+        $data['get_time'] = $data['get_time'] ?? time();
+        
+        $record = new LotteryUserChanceRecord($data);
+        if ($record->save()) {
+            return $record->id;
+        }
+        
+        return false;
+    }
+
+    /**
+     * 获取用户在某个活动中的最新记录
+     * 
+     * @param int $activityId 活动ID
+     * @param int $userId 用户ID
+     * @param int $limit 限制数量
+     * @return array
+     */
+    public static function getUserLatestRecords($activityId, $userId, $limit = 10)
+    {
+        return LotteryUserChanceRecord::where('activity_id', $activityId)
+                   ->where('user_id', $userId)
+                   ->with(['condition', 'order', 'admin'])
+                   ->order('get_time desc')
+                   ->limit($limit)
+                   ->select();
+    }
+
+    /**
+     * 获取活动中某种获取类型的记录统计
+     * 
+     * @param int $activityId 活动ID
+     * @param int $getType 获取类型
+     * @return array
+     */
+    public static function getActivityRecordsByType($activityId, $getType)
+    {
+        $records = LotteryUserChanceRecord::where('activity_id', $activityId)
+                      ->where('get_type', $getType)
+                      ->select();
+        
+        $stats = [
+            'total_records' => count($records),
+            'total_chances' => 0,
+            'users' => [],
+        ];
+        
+        foreach ($records as $record) {
+            $stats['total_chances'] += $record->chances;
+            
+            if (!isset($stats['users'][$record->user_id])) {
+                $stats['users'][$record->user_id] = [
+                    'user_id' => $record->user_id,
+                    'records' => 0,
+                    'chances' => 0,
+                ];
+            }
+            
+            $stats['users'][$record->user_id]['records']++;
+            $stats['users'][$record->user_id]['chances'] += $record->chances;
+        }
+        
+        return $stats;
     }
 } 

+ 2 - 3
application/common/Service/Lottery/LotteryRecordService.php

@@ -105,7 +105,7 @@ class LotteryRecordService
     /**
      * 获取用户抽奖记录列表
      */
-    public static function getUserDrawRecords($userId, $activityId = null, $page = 1, $limit = 20)
+    public static function getUserDrawRecords($userId = 0, $activityId = null, $page = 1, $pageSize = 20)
     {
         $query = LotteryDrawRecord::where('user_id', $userId);
         
@@ -115,8 +115,7 @@ class LotteryRecordService
         
         return $query->with(['activity', 'prize', 'winRecord'])
                      ->order('draw_time', 'desc')
-                     ->page($page, $limit)
-                     ->select();
+                     ->paginate($pageSize, false, ['page' => $page]);
     }
 
     /**

+ 92 - 26
application/common/Service/Lottery/LotteryService.php

@@ -17,6 +17,12 @@ use think\Cache;
  */
 class LotteryService
 {
+    // TODO: 分布式锁相关功能(暂时移除,后续实现)
+    /**
+     * 当前进程持有的锁信息
+     * @var array
+     */
+    // private static $locks = [];
     /**
      * 执行抽奖
      * @param int $activityId 活动ID
@@ -60,20 +66,20 @@ class LotteryService
             throw new BusinessException('该订单已参与过抽奖', ErrorCodeEnum::LOTTERY_ORDER_ALREADY_DRAWN);
         }
 
-        // 7. 使用Redis锁防止并发
-        $lockKey = "lottery_lock_{$activityId}_{$userId}";
-        $lock = Cache::store('redis')->handler()->set($lockKey, 1, 'NX', 'EX', 10);
-        if (!$lock) {
-            throw new Exception('操作太频繁,请稍后再试');
-        }
+        // TODO: 7. 使用分布式锁防止并发(暂时移除,后续实现)
+        // $lockKey = "lottery_lock_{$activityId}_{$userId}";
+        // $lockAcquired = static::acquireLock($lockKey, 10);
+        // if (!$lockAcquired) {
+        //     throw new Exception('操作太频繁,请稍后再试');
+        // }
 
-        try {
-            // 8. 开始抽奖流程
+        // try {
+            // 7. 开始抽奖流程
             return static::processDrawLottery($activity, $userId, $triggerType, $triggerOrderId, $triggerAmount);
-        } finally {
-            // 释放锁
-            Cache::store('redis')->handler()->del($lockKey);
-        }
+        // } finally {
+        //     // 释放锁
+        //     static::releaseLock($lockKey);
+        // }
     }
 
     /**
@@ -87,6 +93,7 @@ class LotteryService
         try {
             // 1. 获取可用奖品
             $prizes = static::getAvailablePrizes($activity);
+        
             if (empty($prizes)) {
                 throw new Exception('暂无可抽取的奖品');
             }
@@ -107,6 +114,7 @@ class LotteryService
 
             // 5. 创建抽奖记录
             $isWin = $selectedPrize->type != LotteryEnum::PRIZE_TYPE_NO_PRIZE;
+        
             $winInfo = $isWin ? static::buildWinInfo($selectedPrize) : [];
             
             $drawRecord = LotteryRecordService::createDrawRecord(
@@ -119,7 +127,7 @@ class LotteryService
                 $triggerAmount,
                 $winInfo
             );
-
+          
             // 6. 如果中奖,创建中奖记录
             $winRecord = null;
             if ($isWin) {
@@ -138,7 +146,7 @@ class LotteryService
                     LotteryRecordService::autoDeliverPrize($winRecord, $selectedPrize);
                 }
             }
-
+        
             // 7. 更新活动统计
             static::updateActivityStats($activity, $isWin);
 
@@ -160,7 +168,6 @@ class LotteryService
     private static function getAvailablePrizes($activity)
     {
         $prizes = static::getValidPrizes($activity->id);
-        
         // 如果开启按人数解锁功能
         if ($activity->unlock_by_people) {
             $currentPeopleCount = LotteryChanceService::getActivityParticipants($activity->id);
@@ -177,23 +184,55 @@ class LotteryService
      */
     private static function executeLotteryAlgorithm($prizes)
     {
-        // 计算总概率
-        $totalProbability = $prizes->sum('probability');
+        // 检查是否有奖品
+        if (empty($prizes)) {
+            return null;
+        }
+        
+        // 将奖品转换为数组(如果是集合对象)
+        $prizeArray = is_array($prizes) ? $prizes : $prizes->toArray();
+        
+        // 使用BC函数计算总概率
+        $totalProbability = '0';
+        foreach ($prizeArray as $prize) {
+            $totalProbability = bcadd($totalProbability, $prize['probability'] ?? '0', 4);
+        }
         
-        // 生成随机数
-        $randomNumber = mt_rand(1, $totalProbability * 100) / 100;
+        // 如果总概率为0,返回未中奖奖品
+        if (bccomp($totalProbability, '0', 4) == 0) {
+            return static::findNoPrizePrize($prizeArray);
+        }
+        
+        // 生成随机数(0-总概率之间)
+        $randomNumber = bcdiv(mt_rand(0, bcmul($totalProbability, '10000', 4)), '10000', 4);
         
         // 按概率选择奖品
-        $currentProbability = 0;
-        foreach ($prizes as $prize) {
-            $currentProbability += $prize->probability;
-            if ($randomNumber <= $currentProbability) {
+        $currentProbability = '0';
+        foreach ($prizeArray as $prize) {
+            $currentProbability = bcadd($currentProbability, $prize['probability'] ?? '0', 4);
+            if (bccomp($randomNumber, $currentProbability, 4) <= 0) {
                 return $prize;
             }
         }
         
-        // 保底返回第一个奖品(通常是未中奖)
-        return $prizes->first();
+        // 保底返回未中奖类型的奖品
+        return static::findNoPrizePrize($prizeArray);
+    }
+
+    /**
+     * 查找未中奖类型的奖品
+     */
+    private static function findNoPrizePrize($prizeArray)
+    {
+        // 优先查找未中奖类型的奖品
+        foreach ($prizeArray as $prize) {
+            if (($prize['type'] ?? 0) == LotteryEnum::PRIZE_TYPE_NO_PRIZE) {
+                return $prize;
+            }
+        }
+        
+        // 如果没找到未中奖类型,返回第一个奖品作为兜底
+        return isset($prizeArray[0]) ? $prizeArray[0] : null;
     }
 
     /**
@@ -413,7 +452,11 @@ class LotteryService
         if ($winRecord) {
             $result['win_record_id'] = $winRecord->id;
             $result['deliver_status'] = $winRecord->deliver_status;
-            $result['exchange_code'] = $winRecord->exchange_code;
+            
+            // 只有兑换码类型的奖品才记录兑换码信息
+            if ($prize->type == LotteryEnum::PRIZE_TYPE_CODE) {
+                $result['exchange_code'] = $winRecord->exchange_code;
+            }
         }
 
         return $result;
@@ -575,6 +618,29 @@ class LotteryService
     {
         return $activity->status == LotteryEnum::STATUS_CANCELLED;
     }
+    // TODO: 分布式锁相关方法(暂时移除,后续实现)
+    /*
+    private static function acquireLock($lockKey, $expireTime = 10)
+    {
+        // Redis锁获取逻辑
+    }
+
+    private static function releaseLock($lockKey)
+    {
+        // Redis锁释放逻辑
+    }
+
+    private static function acquireFileLock($lockKey, $expireTime = 10)
+    {
+        // 文件锁获取逻辑
+    }
+
+    private static function releaseFileLock($lockKey)
+    {
+        // 文件锁释放逻辑
+    }
+    */
+
     /**
      * 获取正在进行的活动
      */

+ 12 - 0
application/common/Service/lottery/LotteryActivityService.php

@@ -81,6 +81,18 @@ class LotteryActivityService
     }
 
     /**
+     * 获取当前正在进行的单个抽奖活动
+     * 假设同一时间段只有一个活动在进行
+     * 
+     * @return LotteryActivity|null
+     */
+    public static function getCurrentRunningActivity()
+    {
+        $activities = static::getRunningActivities();
+        return empty($activities) ? null : $activities[0];
+    }
+
+    /**
      * 获取未开始的活动
      */
     public static function getNotStartedActivities()

+ 218 - 25
application/common/Service/lottery/LotteryChanceService.php

@@ -13,6 +13,7 @@ use think\Exception;
 use think\Db;
 use app\common\exception\BusinessException;
 use app\common\Enum\OrderEnum;
+use app\common\Service\Lottery\LotteryActivityService;
 /**
  * 抽奖机会服务类
  * 处理用户获得抽奖机会的逻辑
@@ -51,7 +52,7 @@ class LotteryChanceService
 
         // try {
             // 获取当前正在进行的单个抽奖活动
-            $activity = static::getCurrentRunningActivity();
+            $activity = LotteryActivityService::getCurrentRunningActivity();
             
             // 如果没有正在进行的活动,返回null
             if (!$activity) {
@@ -94,7 +95,7 @@ class LotteryChanceService
         
         try {
         // 获取所有正在进行的抽奖活动  一段时间 有且只有一个抽奖活动
-            $activities = LotteryService::getRunningActivities();
+            $activities = LotteryActivityService::getRunningActivities();
         
         foreach ($activities as $activity) {
             try {
@@ -137,7 +138,7 @@ class LotteryChanceService
 
         try {
             // 获取当前正在进行的单个抽奖活动
-            $activity = static::getCurrentRunningActivity();
+            $activity = LotteryActivityService::getCurrentRunningActivity();
             
             // 如果没有正在进行的活动,返回null
             if (!$activity) {
@@ -182,7 +183,7 @@ class LotteryChanceService
         
         try {
         // 获取所有正在进行的抽奖活动
-            $activities = LotteryService::getRunningActivities();
+            $activities = LotteryActivityService::getRunningActivities();
         
         foreach ($activities as $activity) {
             try {
@@ -295,19 +296,7 @@ class LotteryChanceService
         return $result;
     }
 
-    // ============ 活动查询方法 ============
 
-    /**
-     * 获取当前正在进行的单个抽奖活动
-     * 假设同一时间段只有一个活动在进行
-     * 
-     * @return LotteryActivity|null
-     */
-    public static function getCurrentRunningActivity()
-    {
-        $activities = LotteryService::getRunningActivities();
-        return empty($activities) ? null : $activities[0];
-    }
 
     // ============ 私有处理方法 ============
 
@@ -863,8 +852,8 @@ class LotteryChanceService
             ];
         }
 
-        // 获取机会获得记录
-        $getRecords = LotteryUserChanceRecord::getUserChanceRecords($activityId, $userId);
+        // 获取机会获得记录(使用服务类方法)
+        $getRecords = static::getUserChanceRecord($userId, $activityId);
 
         return [
             'total_chances' => $userChance->total_chances,
@@ -1233,7 +1222,12 @@ class LotteryChanceService
      */
     public static function getUserChanceRecords($activityId, $userId, $page = 1, $limit = 20)
     {
-        return LotteryUserChanceRecord::getUserChanceRecords($activityId, $userId, $page, $limit);
+        return LotteryUserChanceRecord::where('activity_id', $activityId)
+                   ->where('user_id', $userId)
+                   ->with(['condition', 'order', 'admin'])
+                   ->order('get_time desc')
+                   ->page($page, $limit)
+                   ->select();
     }
 
     /**
@@ -1244,7 +1238,32 @@ class LotteryChanceService
      */
     public static function getActivityChanceRecordStats($activityId)
     {
-        return LotteryUserChanceRecord::getActivityChanceStats($activityId);
+        $records = LotteryUserChanceRecord::where('activity_id', $activityId)->select();
+        
+        $stats = [
+            'total_records' => count($records),
+            'total_chances' => 0,
+            'type_stats' => [],
+        ];
+        
+        foreach ($records as $record) {
+            $stats['total_chances'] += $record->chances;
+            
+            $getType = $record->get_type;
+            if (!isset($stats['type_stats'][$getType])) {
+                $stats['type_stats'][$getType] = [
+                    'type' => $getType,
+                    'type_text' => LotteryEnum::getChanceGetTypeText($getType),
+                    'count' => 0,
+                    'chances' => 0,
+                ];
+            }
+            
+            $stats['type_stats'][$getType]['count']++;
+            $stats['type_stats'][$getType]['chances'] += $record->chances;
+        }
+        
+        return $stats;
     }
 
     /**
@@ -1254,9 +1273,48 @@ class LotteryChanceService
      * @param int $activityId 活动ID(可选)
      * @return array
      */
-    public static function getUserChanceRecordStats($userId, $activityId = null)
+    public static function getUserChanceRecord($userId, $activityId = null)
     {
-        return LotteryUserChanceRecord::getUserChanceStats($userId, $activityId);
+        $query = LotteryUserChanceRecord::where('user_id', $userId);
+        
+        if ($activityId) {
+            $query->where('activity_id', $activityId);
+        }
+        
+        $records = $query->select();
+        
+        $stats = [
+            'total_records' => count($records),
+            'total_chances' => 0,
+            'type_stats' => [],
+            'recent_records' => [],
+        ];
+        
+        foreach ($records as $record) {
+            $stats['total_chances'] += $record->chances;
+            
+            $getType = $record->get_type;
+            if (!isset($stats['type_stats'][$getType])) {
+                $stats['type_stats'][$getType] = [
+                    'type' => $getType,
+                    'type_text' => LotteryEnum::getChanceGetTypeText($getType),
+                    'count' => 0,
+                    'chances' => 0,
+                ];
+            }
+            
+            $stats['type_stats'][$getType]['count']++;
+            $stats['type_stats'][$getType]['chances'] += $record->chances;
+        }
+        
+        // 获取最近的记录
+        $stats['recent_records'] = LotteryUserChanceRecord::where('user_id', $userId)
+                                       ->with(['activity'])
+                                       ->order('get_time desc')
+                                       ->limit(5)
+                                       ->select();
+        
+        return $stats;
     }
 
     /**
@@ -1267,17 +1325,152 @@ class LotteryChanceService
      */
     public static function batchCreateChanceRecords($records)
     {
-        return LotteryUserChanceRecord::batchCreateRecords($records);
+        if (empty($records)) {
+            return false;
+        }
+        
+        // 为每条记录添加创建时间
+        foreach ($records as &$record) {
+            $record['createtime'] = time();
+            $record['get_time'] = $record['get_time'] ?? time();
+        }
+        
+        return LotteryUserChanceRecord::insertAll($records);
     }
 
     /**
      * 验证机会获取记录数据
      * 
      * @param array $data 记录数据
-     * @return bool|string
+     * @return bool|string true表示验证通过,string表示错误信息
      */
     public static function validateChanceRecord($data)
     {
-        return LotteryUserChanceRecord::validateRecord($data);
+        // 验证必填字段
+        if (empty($data['activity_id'])) {
+            return '活动ID不能为空';
+        }
+        
+        if (empty($data['user_id'])) {
+            return '用户ID不能为空';
+        }
+        
+        if (empty($data['get_type'])) {
+            return '获取类型不能为空';
+        }
+        
+        if (empty($data['chances']) || $data['chances'] <= 0) {
+            return '机会次数必须大于0';
+        }
+        
+        // 验证获取类型是否有效
+        if (!LotteryEnum::isValidChanceGetType($data['get_type'])) {
+            return '无效的获取类型';
+        }
+        
+        // 根据获取类型验证相关字段
+        switch ($data['get_type']) {
+            case LotteryEnum::CHANCE_GET_TYPE_BUY_GOODS:
+            case LotteryEnum::CHANCE_GET_TYPE_ORDER_AMOUNT:
+                if (empty($data['order_id'])) {
+                    return '订单ID不能为空';
+                }
+                break;
+                
+            case LotteryEnum::CHANCE_GET_TYPE_RECHARGE:
+                if (empty($data['recharge_amount']) || $data['recharge_amount'] <= 0) {
+                    return '充值金额必须大于0';
+                }
+                break;
+                
+            case LotteryEnum::CHANCE_GET_TYPE_ADMIN_GRANT:
+                if (empty($data['admin_id'])) {
+                    return '管理员ID不能为空';
+                }
+                break;
+        }
+        
+        return true;
+    }
+
+    /**
+     * 创建单条机会获取记录
+     * 
+     * @param array $data 记录数据
+     * @return bool|int 成功返回记录ID,失败返回false
+     */
+    public static function createChanceRecord($data)
+    {
+        // 验证数据
+        $validation = static::validateChanceRecord($data);
+        if ($validation !== true) {
+            throw new BusinessException($validation);
+        }
+        
+        // 设置默认值
+        $data['createtime'] = time();
+        $data['get_time'] = $data['get_time'] ?? time();
+        
+        $record = new LotteryUserChanceRecord($data);
+        if ($record->save()) {
+            return $record->id;
+        }
+        
+        return false;
+    }
+
+    /**
+     * 获取用户在某个活动中的最新记录
+     * 
+     * @param int $activityId 活动ID
+     * @param int $userId 用户ID
+     * @param int $limit 限制数量
+     * @return array
+     */
+    public static function getUserLatestRecords($activityId, $userId, $limit = 10)
+    {
+        return LotteryUserChanceRecord::where('activity_id', $activityId)
+                   ->where('user_id', $userId)
+                   ->with(['condition', 'order', 'admin'])
+                   ->order('get_time desc')
+                   ->limit($limit)
+                   ->select();
+    }
+
+    /**
+     * 获取活动中某种获取类型的记录统计
+     * 
+     * @param int $activityId 活动ID
+     * @param int $getType 获取类型
+     * @return array
+     */
+    public static function getActivityRecordsByType($activityId, $getType)
+    {
+        $records = LotteryUserChanceRecord::where('activity_id', $activityId)
+                      ->where('get_type', $getType)
+                      ->select();
+        
+        $stats = [
+            'total_records' => count($records),
+            'total_chances' => 0,
+            'users' => [],
+        ];
+        
+        foreach ($records as $record) {
+            $stats['total_chances'] += $record->chances;
+            
+            if (!isset($stats['users'][$record->user_id])) {
+                $stats['users'][$record->user_id] = [
+                    'user_id' => $record->user_id,
+                    'records' => 0,
+                    'chances' => 0,
+                ];
+            }
+            
+            $stats['users'][$record->user_id]['records']++;
+            $stats['users'][$record->user_id]['chances'] += $record->chances;
+        }
+        
+        return $stats;
     }
 } 

+ 2 - 3
application/common/Service/lottery/LotteryRecordService.php

@@ -105,7 +105,7 @@ class LotteryRecordService
     /**
      * 获取用户抽奖记录列表
      */
-    public static function getUserDrawRecords($userId, $activityId = null, $page = 1, $limit = 20)
+    public static function getUserDrawRecords($userId = 0, $activityId = null, $page = 1, $pageSize = 20)
     {
         $query = LotteryDrawRecord::where('user_id', $userId);
         
@@ -115,8 +115,7 @@ class LotteryRecordService
         
         return $query->with(['activity', 'prize', 'winRecord'])
                      ->order('draw_time', 'desc')
-                     ->page($page, $limit)
-                     ->select();
+                     ->paginate($pageSize, false, ['page' => $page]);
     }
 
     /**

+ 92 - 26
application/common/Service/lottery/LotteryService.php

@@ -17,6 +17,12 @@ use think\Cache;
  */
 class LotteryService
 {
+    // TODO: 分布式锁相关功能(暂时移除,后续实现)
+    /**
+     * 当前进程持有的锁信息
+     * @var array
+     */
+    // private static $locks = [];
     /**
      * 执行抽奖
      * @param int $activityId 活动ID
@@ -60,20 +66,20 @@ class LotteryService
             throw new BusinessException('该订单已参与过抽奖', ErrorCodeEnum::LOTTERY_ORDER_ALREADY_DRAWN);
         }
 
-        // 7. 使用Redis锁防止并发
-        $lockKey = "lottery_lock_{$activityId}_{$userId}";
-        $lock = Cache::store('redis')->handler()->set($lockKey, 1, 'NX', 'EX', 10);
-        if (!$lock) {
-            throw new Exception('操作太频繁,请稍后再试');
-        }
+        // TODO: 7. 使用分布式锁防止并发(暂时移除,后续实现)
+        // $lockKey = "lottery_lock_{$activityId}_{$userId}";
+        // $lockAcquired = static::acquireLock($lockKey, 10);
+        // if (!$lockAcquired) {
+        //     throw new Exception('操作太频繁,请稍后再试');
+        // }
 
-        try {
-            // 8. 开始抽奖流程
+        // try {
+            // 7. 开始抽奖流程
             return static::processDrawLottery($activity, $userId, $triggerType, $triggerOrderId, $triggerAmount);
-        } finally {
-            // 释放锁
-            Cache::store('redis')->handler()->del($lockKey);
-        }
+        // } finally {
+        //     // 释放锁
+        //     static::releaseLock($lockKey);
+        // }
     }
 
     /**
@@ -87,6 +93,7 @@ class LotteryService
         try {
             // 1. 获取可用奖品
             $prizes = static::getAvailablePrizes($activity);
+        
             if (empty($prizes)) {
                 throw new Exception('暂无可抽取的奖品');
             }
@@ -107,6 +114,7 @@ class LotteryService
 
             // 5. 创建抽奖记录
             $isWin = $selectedPrize->type != LotteryEnum::PRIZE_TYPE_NO_PRIZE;
+        
             $winInfo = $isWin ? static::buildWinInfo($selectedPrize) : [];
             
             $drawRecord = LotteryRecordService::createDrawRecord(
@@ -119,7 +127,7 @@ class LotteryService
                 $triggerAmount,
                 $winInfo
             );
-
+          
             // 6. 如果中奖,创建中奖记录
             $winRecord = null;
             if ($isWin) {
@@ -138,7 +146,7 @@ class LotteryService
                     LotteryRecordService::autoDeliverPrize($winRecord, $selectedPrize);
                 }
             }
-
+        
             // 7. 更新活动统计
             static::updateActivityStats($activity, $isWin);
 
@@ -160,7 +168,6 @@ class LotteryService
     private static function getAvailablePrizes($activity)
     {
         $prizes = static::getValidPrizes($activity->id);
-        
         // 如果开启按人数解锁功能
         if ($activity->unlock_by_people) {
             $currentPeopleCount = LotteryChanceService::getActivityParticipants($activity->id);
@@ -177,23 +184,55 @@ class LotteryService
      */
     private static function executeLotteryAlgorithm($prizes)
     {
-        // 计算总概率
-        $totalProbability = $prizes->sum('probability');
+        // 检查是否有奖品
+        if (empty($prizes)) {
+            return null;
+        }
+        
+        // 将奖品转换为数组(如果是集合对象)
+        $prizeArray = is_array($prizes) ? $prizes : $prizes->toArray();
+        
+        // 使用BC函数计算总概率
+        $totalProbability = '0';
+        foreach ($prizeArray as $prize) {
+            $totalProbability = bcadd($totalProbability, $prize['probability'] ?? '0', 4);
+        }
         
-        // 生成随机数
-        $randomNumber = mt_rand(1, $totalProbability * 100) / 100;
+        // 如果总概率为0,返回未中奖奖品
+        if (bccomp($totalProbability, '0', 4) == 0) {
+            return static::findNoPrizePrize($prizeArray);
+        }
+        
+        // 生成随机数(0-总概率之间)
+        $randomNumber = bcdiv(mt_rand(0, bcmul($totalProbability, '10000', 4)), '10000', 4);
         
         // 按概率选择奖品
-        $currentProbability = 0;
-        foreach ($prizes as $prize) {
-            $currentProbability += $prize->probability;
-            if ($randomNumber <= $currentProbability) {
+        $currentProbability = '0';
+        foreach ($prizeArray as $prize) {
+            $currentProbability = bcadd($currentProbability, $prize['probability'] ?? '0', 4);
+            if (bccomp($randomNumber, $currentProbability, 4) <= 0) {
                 return $prize;
             }
         }
         
-        // 保底返回第一个奖品(通常是未中奖)
-        return $prizes->first();
+        // 保底返回未中奖类型的奖品
+        return static::findNoPrizePrize($prizeArray);
+    }
+
+    /**
+     * 查找未中奖类型的奖品
+     */
+    private static function findNoPrizePrize($prizeArray)
+    {
+        // 优先查找未中奖类型的奖品
+        foreach ($prizeArray as $prize) {
+            if (($prize['type'] ?? 0) == LotteryEnum::PRIZE_TYPE_NO_PRIZE) {
+                return $prize;
+            }
+        }
+        
+        // 如果没找到未中奖类型,返回第一个奖品作为兜底
+        return isset($prizeArray[0]) ? $prizeArray[0] : null;
     }
 
     /**
@@ -413,7 +452,11 @@ class LotteryService
         if ($winRecord) {
             $result['win_record_id'] = $winRecord->id;
             $result['deliver_status'] = $winRecord->deliver_status;
-            $result['exchange_code'] = $winRecord->exchange_code;
+            
+            // 只有兑换码类型的奖品才记录兑换码信息
+            if ($prize->type == LotteryEnum::PRIZE_TYPE_CODE) {
+                $result['exchange_code'] = $winRecord->exchange_code;
+            }
         }
 
         return $result;
@@ -575,6 +618,29 @@ class LotteryService
     {
         return $activity->status == LotteryEnum::STATUS_CANCELLED;
     }
+    // TODO: 分布式锁相关方法(暂时移除,后续实现)
+    /*
+    private static function acquireLock($lockKey, $expireTime = 10)
+    {
+        // Redis锁获取逻辑
+    }
+
+    private static function releaseLock($lockKey)
+    {
+        // Redis锁释放逻辑
+    }
+
+    private static function acquireFileLock($lockKey, $expireTime = 10)
+    {
+        // 文件锁获取逻辑
+    }
+
+    private static function releaseFileLock($lockKey)
+    {
+        // 文件锁释放逻辑
+    }
+    */
+
     /**
      * 获取正在进行的活动
      */

+ 1 - 181
application/common/model/lottery/LotteryUserChanceRecord.php

@@ -63,7 +63,7 @@ class LotteryUserChanceRecord extends Model
      */
     public function admin()
     {
-        return $this->belongsTo('app\common\model\Admin', 'admin_id', 'id');
+        return $this->belongsTo('app\admin\model\Admin', 'admin_id', 'id');
     }
 
     /**
@@ -98,185 +98,5 @@ class LotteryUserChanceRecord extends Model
         return $value ? strtotime($value) : time();
     }
 
-    /**
-     * 获取指定活动的用户机会获取记录
-     * 
-     * @param int $activityId 活动ID
-     * @param int $userId 用户ID
-     * @param int $page 页码
-     * @param int $limit 每页数量
-     * @return array
-     */
-    public static function getUserChanceRecords($activityId, $userId, $page = 1, $limit = 20)
-    {
-        return self::where('activity_id', $activityId)
-                   ->where('user_id', $userId)
-                   ->with(['condition', 'order', 'admin'])
-                   ->order('get_time desc')
-                   ->page($page, $limit)
-                   ->select();
-    }
-
-    /**
-     * 获取指定活动的机会获取统计
-     * 
-     * @param int $activityId 活动ID
-     * @return array
-     */
-    public static function getActivityChanceStats($activityId)
-    {
-        $records = self::where('activity_id', $activityId)->select();
-        
-        $stats = [
-            'total_records' => count($records),
-            'total_chances' => 0,
-            'type_stats' => [],
-        ];
-        
-        foreach ($records as $record) {
-            $stats['total_chances'] += $record->chances;
-            
-            $getType = $record->get_type;
-            if (!isset($stats['type_stats'][$getType])) {
-                $stats['type_stats'][$getType] = [
-                    'type' => $getType,
-                    'type_text' => LotteryEnum::getChanceGetTypeText($getType),
-                    'count' => 0,
-                    'chances' => 0,
-                ];
-            }
-            
-            $stats['type_stats'][$getType]['count']++;
-            $stats['type_stats'][$getType]['chances'] += $record->chances;
-        }
-        
-        return $stats;
-    }
 
-    /**
-     * 获取用户机会获取统计
-     * 
-     * @param int $userId 用户ID
-     * @param int $activityId 活动ID(可选)
-     * @return array
-     */
-    public static function getUserChanceStats($userId, $activityId = null)
-    {
-        $query = self::where('user_id', $userId);
-        
-        if ($activityId) {
-            $query->where('activity_id', $activityId);
-        }
-        
-        $records = $query->select();
-        
-        $stats = [
-            'total_records' => count($records),
-            'total_chances' => 0,
-            'type_stats' => [],
-            'recent_records' => [],
-        ];
-        
-        foreach ($records as $record) {
-            $stats['total_chances'] += $record->chances;
-            
-            $getType = $record->get_type;
-            if (!isset($stats['type_stats'][$getType])) {
-                $stats['type_stats'][$getType] = [
-                    'type' => $getType,
-                    'type_text' => LotteryEnum::getChanceGetTypeText($getType),
-                    'count' => 0,
-                    'chances' => 0,
-                ];
-            }
-            
-            $stats['type_stats'][$getType]['count']++;
-            $stats['type_stats'][$getType]['chances'] += $record->chances;
-        }
-        
-        // 获取最近的记录
-        $stats['recent_records'] = self::where('user_id', $userId)
-                                       ->with(['activity'])
-                                       ->order('get_time desc')
-                                       ->limit(5)
-                                       ->select();
-        
-        return $stats;
-    }
-
-    /**
-     * 批量创建机会获取记录
-     * 
-     * @param array $records 记录数组
-     * @return bool
-     */
-    public static function batchCreateRecords($records)
-    {
-        if (empty($records)) {
-            return false;
-        }
-        
-        // 为每条记录添加创建时间
-        foreach ($records as &$record) {
-            $record['createtime'] = time();
-            $record['get_time'] = $record['get_time'] ?? time();
-        }
-        
-        return self::insertAll($records);
-    }
-
-    /**
-     * 验证记录数据
-     * 
-     * @param array $data 记录数据
-     * @return bool|string true表示验证通过,string表示错误信息
-     */
-    public static function validateRecord($data)
-    {
-        // 验证必填字段
-        if (empty($data['activity_id'])) {
-            return '活动ID不能为空';
-        }
-        
-        if (empty($data['user_id'])) {
-            return '用户ID不能为空';
-        }
-        
-        if (empty($data['get_type'])) {
-            return '获取类型不能为空';
-        }
-        
-        if (empty($data['chances']) || $data['chances'] <= 0) {
-            return '机会次数必须大于0';
-        }
-        
-        // 验证获取类型是否有效
-        if (!LotteryEnum::isValidChanceGetType($data['get_type'])) {
-            return '无效的获取类型';
-        }
-        
-        // 根据获取类型验证相关字段
-        switch ($data['get_type']) {
-            case LotteryEnum::CHANCE_GET_TYPE_BUY_GOODS:
-            case LotteryEnum::CHANCE_GET_TYPE_ORDER_AMOUNT:
-                if (empty($data['order_id'])) {
-                    return '订单ID不能为空';
-                }
-                break;
-                
-            case LotteryEnum::CHANCE_GET_TYPE_RECHARGE:
-                if (empty($data['recharge_amount']) || $data['recharge_amount'] <= 0) {
-                    return '充值金额必须大于0';
-                }
-                break;
-                
-            case LotteryEnum::CHANCE_GET_TYPE_ADMIN_GRANT:
-                if (empty($data['admin_id'])) {
-                    return '管理员ID不能为空';
-                }
-                break;
-        }
-        
-        return true;
-    }
 } 

+ 31 - 9
application/config.php

@@ -183,15 +183,37 @@ return [
     // +----------------------------------------------------------------------
     // | 缓存设置
     // +----------------------------------------------------------------------
-    'cache'                  => [
-        // 驱动方式
-        'type'   => 'File',
-        // 缓存保存目录
-        'path'   => CACHE_PATH,
-        // 缓存前缀
-        'prefix' => '',
-        // 缓存有效期 0表示永久缓存
-        'expire' => 0,
+
+    'cache' =>  [
+        // 使用复合缓存类型
+        'type'  =>  'complex',
+        // 默认使用的缓存
+        'default'   =>  [
+            // 驱动方式
+            'type'   => 'File',
+            // 缓存保存目录
+            'path'   => CACHE_PATH,
+        ],
+        // 文件缓存
+        'file'   =>  [
+            // 驱动方式
+            'type'   => 'file',
+            // 设置不同的缓存保存目录
+            'path'   => RUNTIME_PATH . 'file/',
+        ],  
+        // redis缓存
+        'redis'   =>  [
+            // 驱动方式
+            'type'   => 'redis',
+            'host'       =>  Env::get('redis.REDIS_HOST','127.0.0.1'),
+            'port'       =>  Env::get('redis.REDIS_PORT','6379'),
+            'password'   =>  Env::get('redis.REDIS_PASSWORD','secret_redis'),
+            'select'     =>  ENV::get('redis.REDIS_SELECT','1'),
+            'expire'     =>  0,
+            'timeout'    =>  0,
+            'persistent' => false,
+            'prefix'     => ENV::get('redis.REDIS_PREFIX','shop'),
+        ],     
     ],
     // +----------------------------------------------------------------------
     // | 会话设置