validateCreateData($data); if ($res['code'] == 0) { return $res; } $redpacketModel = new Redpacket(); return $redpacketModel->createRedpacket($data); } /** * 抢红包并返回详细结果 */ public function grabWithResult($redpacketId, $userId) { $redpacketModel = new Redpacket(); $redpacket = $redpacketModel->getRedpacketInfo($redpacketId); if (!$redpacket) { 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' => 1, 'msg' => '已经抢过该红包', 'data' => ['code' => 2] //1-抢到了,2-已经抢过红包,3-没有抢到 ]; } // 使用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]; if ($message == '红包已抢完') { return [ 'code' => 1, 'msg' => '手慢了,红包已抢完', 'data' => ['code' => 3] //1-抢到了,2-已经抢过红包,3-没有抢到 ]; } elseif ($message == '已经抢过该红包') { return [ 'code' => 1, 'msg' => '已经抢过该红包', 'data' => ['code' => 2] //1-抢到了,2-已经抢过红包,3-没有抢到 ]; }else{ 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'], //记录日志 32-发红包(金币),29-发红包(钻石),30-抢红包(金币),31-抢红包(钻石) 'change_type' => $redpacket['coin_type'] == 1 ? 30 : 31, '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); unset($grabResult['previous_records']);//前端不要 unset($grabResult['all_records']);//前端不要 unset($grabResult['statistics']);//前端不要 return [ 'code' => 1, 'msg' => '抢红包成功', // 'data' => $grabResult // 'data' => null 'data' => ['code' => 1] //1-抢到了,2-已经抢过红包,3-没有抢到 ]; } catch (\Exception $e) { Db::rollback(); // 回滚Redis操作 $redis->hIncrByFloat($redpacketKey, 'left_amount', $amount); $redis->hIncrBy($redpacketKey, 'left_count', 1); $redis->sRem($userSetKey, $userId); return [ 'code' => 0, 'msg' => '系统错误,请重试', 'data' => null ]; } } /** * 获取抢红包结果详情 */ 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'], 'nickname' => Db::name('user')->where('id', $redpacket['user_id'])->value('nickname') ], '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 getDetail($redpacketId, $currentUserId = 0) { $redpacketModel = new Redpacket(); $redpacket['redpacket_info'] = $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(); //处理createtime 字段 $records = array_map(function ($record) { $record['createtime'] = date('Y-m-d H:i:s', $record['createtime']); return $record; }, $records); $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 ['code' => 0, 'msg' => '用户ID不能为空', 'data' => null]; } if (empty($data['room_id'])) { return ['code' => 0, 'msg' => '房间ID不能为空', 'data' => null]; } if (!in_array($data['type'], [Redpacket::TYPE_NORMAL, Redpacket::TYPE_PASSWORD])) { return ['code' => 0, 'msg' => '红包类型错误', 'data' => null]; } if ($data['type'] == Redpacket::TYPE_PASSWORD && empty($data['password'])) { return ['code' => 0, 'msg' => '口令红包必须设置口令', 'data' => null]; } if (!in_array($data['coin_type'], [Redpacket::COIN_GOLD, Redpacket::COIN_DIAMOND])) { return ['code' => 0, 'msg' => '币种类型错误', 'data' => null]; } if ($data['total_amount'] <= 0 || $data['total_count'] <= 0) { return ['code' => 0, 'msg' => '金额和数量必须大于0', 'data' => null]; } if ($data['total_amount'] < $data['total_count']) { return ['code' => 0, 'msg' => '总金额不能小于总个数', 'data' => null]; } // 验证领取条件 if (isset($data['conditions'])) { $res_con = $this->validateConditions($data['conditions']); if ($res_con !== true) { return $res_con; } } return ['code' => 1, 'msg' => '验证成功', 'data' => null]; } /** * 验证领取条件 */ 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; } }