validateCreateData($data); if ($res['code'] == 0) { return $res; } $redpacketModel = new Redpacket(); return $redpacketModel->createRedpacket($data); } /** * 抢红包并返回详细结果 */ public function grabWithResult($redpacketId, $userId, $password = '') { $redpacketModel = new Redpacket(); $redpacket = $redpacketModel->getRedpacketInfo($redpacketId); if (!$redpacket) { return [ 'code' => 0, 'msg' => '红包不存在', 'data' => null ]; } // 验证口令红包 if ($redpacket['type'] == Redpacket::TYPE_PASSWORD) { if (empty($password) || $password != $redpacket['password']) { return [ 'code' => 0, 'msg' => '口令错误', 'data' => null ]; } } // 验证领取条件 $conditionCheck = $this->checkConditionsWithResult($redpacket, $userId); if ($conditionCheck['code'] != 1) { return $conditionCheck; } // 检查红包状态 $statusCheck = $this->checkRedpacketStatus($redpacket); if ($statusCheck['code'] != 1) { return $statusCheck; } // 检查是否已经抢过 if ($this->hasUserGrabbed($redpacketId, $userId)) { $detail = $this->getGrabResult($redpacketId, $userId); return [ 'code' => 0, 'msg' => '已经抢过该红包', 'data' => $detail ]; } // 使用Redis+Lua保证原子性操作 $redis = Cache::store('redis')->handler(); $script = RedpacketLua::grabRedpacketScript(); $redpacketKey = "redpacket:{$redpacketId}"; $userSetKey = "redpacket_users:{$redpacketId}"; $result = $redis->eval($script, [ $redpacketKey, $userSetKey, $userId, time() ], 3); if ($result[0] == 0) { $message = $result[1]; return [ 'code' => 0, 'msg' => $message, 'data' => null ]; } $amount = floatval($result[1]); // Lua脚本执行成功,记录到数据库 Db::startTrans(); try { // 创建领取记录 $recordData = [ 'redpacket_id' => $redpacketId, 'user_id' => $userId, 'amount' => $amount ]; $recordModel = new RedpacketRecord(); $recordModel->save($recordData); // 更新用户钱包 $coinField = $redpacket['coin_type'] == 1 ? 'coin' : 'earnings'; //增加余额 $addres = Db::name('user_wallet') ->where('user_id', $userId) ->inc($coinField, $amount) ->update(); //记录用户金币日志 $data = [ 'user_id' => $userId, 'change_value' => $amount, 'room_id' => $redpacket['room_id'], 'money_type' => $redpacket['coin_type'], 'change_type' => 66,//抢红包收入 'from_id' => $redpacket['room_id'], 'remarks' => '抢红包收入', 'createtime' => time() ]; $res = Db::name('vs_user_money_log')->insert($data); if(!$res || !$addres){ Db::rollback(); } // 更新红包剩余数量和金额 Db::name('redpacket') ->where('id', $redpacketId) ->dec('left_amount', $amount) ->dec('left_count', 1) ->update(); Db::commit(); // 获取抢红包结果详情 $grabResult = $this->getGrabResult($redpacketId, $userId); return [ 'success' => true, 'code' => 'grab_success', 'message' => '抢红包成功', 'data' => $grabResult ]; } catch (\Exception $e) { Db::rollback(); // 回滚Redis操作 $redis->hIncrByFloat($redpacketKey, 'left_amount', $amount); $redis->hIncrBy($redpacketKey, 'left_count', 1); $redis->sRem($userSetKey, $userId); return [ 'success' => false, 'code' => 'system_error', 'message' => '系统错误,请重试' ]; } } /** * 获取抢红包结果详情 */ public function getGrabResult($redpacketId, $userId) { // 获取红包基本信息 $redpacket = Db::name('redpacket') ->where('id', $redpacketId) ->find(); if (!$redpacket) { return null; } // 获取当前用户的领取记录 $myRecord = Db::name('redpacket_record') ->alias('r') ->field('r.*, u.nickname, u.avatar') ->join('user u', 'u.id = r.user_id') ->where('r.redpacket_id', $redpacketId) ->where('r.user_id', $userId) ->find(); // 获取在我之前抢到的用户记录 $previousRecords = []; if ($myRecord) { $previousRecords = Db::name('redpacket_record') ->alias('r') ->field('r.*, u.nickname, u.avatar') ->join('user u', 'u.id = r.user_id') ->where('r.redpacket_id', $redpacketId) ->where('r.createtime', '<', $myRecord['createtime']) ->order('r.createtime ASC') ->select(); } // 获取所有记录用于统计 $allRecords = Db::name('redpacket_record') ->alias('r') ->field('r.*, u.nickname, u.avatar') ->join('user u', 'u.id = r.user_id') ->where('r.redpacket_id', $redpacketId) ->order('r.createtime ASC') ->select(); // 统计信息 $totalGrabbed = count($allRecords); $totalAmount = array_sum(array_column($allRecords, 'amount')); // 手气最佳 $bestRecord = null; if ($allRecords) { $maxAmount = max(array_column($allRecords, 'amount')); foreach ($allRecords as $record) { if ($record['amount'] == $maxAmount) { $bestRecord = $record; break; } } } return [ 'redpacket_info' => [ 'id' => $redpacket['id'], 'total_amount' => $redpacket['total_amount'], 'total_count' => $redpacket['total_count'], 'left_amount' => $redpacket['left_amount'], 'left_count' => $redpacket['left_count'], 'coin_type' => $redpacket['coin_type'], 'status' => $redpacket['status'] ], 'my_record' => $myRecord ? [ 'amount' => $myRecord['amount'], 'createtime' => $myRecord['createtime'], 'nickname' => $myRecord['nickname'], 'avatar' => $myRecord['avatar'] ] : null, 'previous_records' => $previousRecords, 'all_records' => $allRecords, 'statistics' => [ 'total_grabbed' => $totalGrabbed, 'total_amount_grabbed' => $totalAmount, 'best_luck' => $bestRecord ? [ 'nickname' => $bestRecord['nickname'], 'avatar' => $bestRecord['avatar'], 'amount' => $bestRecord['amount'] ] : null ] ]; } /** * 检查红包状态 */ private function checkRedpacketStatus($redpacket) { $now = time(); if ($redpacket['status'] == Redpacket::STATUS_PENDING) { if ($now < $redpacket['start_time']) { return [ 'code' => 0, 'msg' => '红包还未开始', 'data' => null ]; } } if ($redpacket['status'] == Redpacket::STATUS_FINISHED || $redpacket['status'] == Redpacket::STATUS_REFUNDED) { return [ 'code' => 0, 'msg' => '红包已结束', 'data' => null ]; } if ($now > $redpacket['end_time']) { return [ 'code' => 0, 'msg' => '红包已结束', 'data' => null ]; } return ['code' => 1]; } /** * 检查用户是否已经抢过 */ private function hasUserGrabbed($redpacketId, $userId) { $record = Db::name('redpacket_record') ->where('redpacket_id', $redpacketId) ->where('user_id', $userId) ->find(); return !empty($record); } /** * 检查领取条件(返回结果格式) */ private function checkConditionsWithResult($redpacket, $userId) { $conditions = $redpacket['conditions'] ? explode(',', $redpacket['conditions']): []; if (empty($conditions)) { return ['code' => 1]; } if (in_array(Redpacket::CONDITION_NONE, $conditions)) { return ['code' => 1]; } foreach ($conditions as $condition) { switch ($condition) { case Redpacket::CONDITION_COLLECT_ROOM: if (!$this->checkUserCollectedRoom($userId, $redpacket['room_id'])) { return [ 'code' => 0, 'msg' => '不满足收藏房间条件', 'data' => null ]; } break; case Redpacket::CONDITION_MIC_USER: if (!$this->checkUserOnMic($userId, $redpacket['room_id'])) { return [ 'code' => 0, 'msg' => '不满足麦位用户条件', 'data' => null ]; } break; } } return ['code' => 1]; } /** * 抢红包 */ // public function grab($redpacketId, $userId, $password = '') // { // $redpacketModel = new Redpacket(); // $redpacket = $redpacketModel->getRedpacketInfo($redpacketId); // // if (!$redpacket) { // throw new \Exception('红包不存在'); // } // // // 验证口令红包 // if ($redpacket['type'] == Redpacket::TYPE_PASSWORD) { // if (empty($password) || $password != $redpacket['password']) { // throw new \Exception('口令错误'); // } // } // // // 验证领取条件 // $this->checkConditions($redpacket, $userId); // // // 使用Redis+Lua保证原子性操作 // $redis = Cache::store('redis')->handler(); // $script = RedpacketLua::grabRedpacketScript(); // // $redpacketKey = "redpacket:{$redpacketId}"; // $userSetKey = "redpacket_users:{$redpacketId}"; // // $result = $redis->eval($script, [ // $redpacketKey, // $userSetKey, // $userId, // time() // ], 3); // // if ($result[0] == 0) { // throw new \Exception($result[1]); // } // // $amount = floatval($result[1]); // // // Lua脚本执行成功,记录到数据库 // Db::startTrans(); // try { // // 创建领取记录 // $recordData = [ // 'redpacket_id' => $redpacketId, // 'user_id' => $userId, // 'amount' => $amount // ]; // // $recordModel = new RedpacketRecord(); // $recordModel->save($recordData); // // // 更新用户钱包 // $walletModel = new UserWallet(); // $walletModel->increaseBalance($userId, $redpacket['coin_type'], $amount); // // // 更新红包剩余数量和金额 // Db::name('redpacket') // ->where('id', $redpacketId) // ->dec('left_amount', $amount) // ->dec('left_count', 1) // ->update(); // // Db::commit(); // // return $amount; // // } catch (\Exception $e) { // Db::rollback(); // // 回滚Redis操作 // $redis->hIncrByFloat($redpacketKey, 'left_amount', $amount); // $redis->hIncrBy($redpacketKey, 'left_count', 1); // $redis->sRem($userSetKey, $userId); // throw $e; // } // } /** * 获取红包详情和领取记录 */ public function getDetail($redpacketId, $currentUserId = 0) { $redpacketModel = new Redpacket(); $redpacket = $redpacketModel->getRedpacketInfo($redpacketId); if (!$redpacket) { return null; } // 获取领取记录 $records = Db::name('redpacket_record') ->alias('r') ->field('r.*, u.nickname, u.avatar') ->join('user u', 'u.id = r.user_id') ->where('r.redpacket_id', $redpacketId) ->order('r.createtime ASC') ->select(); $redpacket['records'] = $records; // 检查当前用户是否已抢 $redpacket['has_grabbed'] = false; $redpacket['my_record'] = null; if ($currentUserId > 0) { foreach ($records as $record) { if ($record['user_id'] == $currentUserId) { $redpacket['has_grabbed'] = true; $redpacket['my_record'] = $record; break; } } } return $redpacket; } /** * 处理过期红包退款 */ public function processExpiredRedpackets() { $now = time(); $redpacketModel = new Redpacket(); // 查找已结束但未退款的红包 $expiredRedpackets = Db::name('redpacket') ->where('status', Redpacket::STATUS_ACTIVE) ->where('end_time', '<', $now) ->where('left_count', '>', 0) ->select(); foreach ($expiredRedpackets as $redpacket) { Db::startTrans(); try { // 退款给发红包用户 if ($redpacket['left_amount'] > 0) { $walletModel = new UserWallet(); $walletModel->increaseBalance( $redpacket['user_id'], $redpacket['coin_type'], $redpacket['left_amount'] ); } // 更新红包状态 Db::name('redpacket') ->where('id', $redpacket['id']) ->update([ 'status' => Redpacket::STATUS_REFUNDED, 'updatetime' => $now ]); // 清理Redis缓存 $redis = Cache::store('redis')->handler(); $redisKey = "redpacket:{$redpacket['id']}"; $redis->del($redisKey); Db::commit(); } catch (\Exception $e) { Db::rollback(); // 记录日志 \think\Log::error("红包退款失败: {$redpacket['id']}, 错误: " . $e->getMessage()); } } } /** * 验证创建红包数据 */ private function validateCreateData($data) { if (empty($data['user_id'])) { return V(0, '用户ID不能为空'); } if (!in_array($data['type'], [Redpacket::TYPE_NORMAL, Redpacket::TYPE_PASSWORD])) { return V(0, '红包类型错误'); } if ($data['type'] == Redpacket::TYPE_PASSWORD && empty($data['password'])) { return V(0, '口令红包必须设置口令'); } if (!in_array($data['coin_type'], [Redpacket::COIN_GOLD, Redpacket::COIN_DIAMOND])) { return V(0, '币种类型错误'); } if ($data['total_amount'] <= 0 || $data['total_count'] <= 0) { return V(0, '金额和数量必须大于0'); } if ($data['total_amount'] < $data['total_count']) { return V(0, '总金额不能小于总个数'); } // 验证领取条件 if (isset($data['conditions'])) { $res_con = $this->validateConditions($data['conditions']); if ($res_con !== true) { return $res_con; } } return true; } /** * 验证领取条件 */ private function validateConditions($conditions) { if (empty($conditions)) { return true; } //字符串转为数组 $conditions = explode(',', $conditions); if (in_array(Redpacket::CONDITION_NONE, $conditions) && count($conditions) > 1) { return V(0, '选择"无"条件时不能选择其他条件'); } return true; } /** * 检查用户是否满足领取条件 */ private function checkConditions($redpacket, $userId) { $conditions = $redpacket['conditions'] ?: []; if (empty($conditions)) { return true; } if (in_array(Redpacket::CONDITION_NONE, $conditions)) { return true; } foreach ($conditions as $condition) { switch ($condition) { case Redpacket::CONDITION_COLLECT_ROOM: // 检查用户是否收藏了房间 if (!$this->checkUserCollectedRoom($userId)) { throw new \Exception('不满足收藏房间条件'); } break; case Redpacket::CONDITION_MIC_USER: // 检查用户是否在麦位上 if (!$this->checkUserOnMic($userId)) { throw new \Exception('不满足麦位用户条件'); } break; } } return true; } /** * 检查用户是否收藏了房间(需要根据实际业务实现) */ private function checkUserCollectedRoom($userId,$roomId) { $collect = Db::name('user_follow')->where(['user_id' => $userId,'type' => 2,'follow_id' => $roomId])->find(); if (!$collect) { return false; } return true; } /** * 检查用户是否在麦位上(需要根据实际业务实现) */ private function checkUserOnMic($userId,$roomId) { $onPit = Db::name('vs_room_pit')->where(['user_id' => $userId,'room_id' => $roomId])->value('pit_number'); if ($onPit <= 0){ return false; } return true; } }