From c606916057e1008f9dabaf5b1488138d3a2498d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E5=8D=8E=E6=B8=85?= <18691022700@163.com> Date: Fri, 10 Oct 2025 19:15:41 +0800 Subject: [PATCH] =?UTF-8?q?=E7=BA=A2=E5=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- application/api/controller/Redpacket.php | 104 +++ application/common/library/RedpacketLua.php | 105 +++ application/common/model/Redpacket.php | 130 ++++ application/common/model/RedpacketRecord.php | 12 + .../common/service/RedpacketService.php | 647 ++++++++++++++++++ application/extra/redis.php | 12 + 6 files changed, 1010 insertions(+) create mode 100644 application/api/controller/Redpacket.php create mode 100644 application/common/library/RedpacketLua.php create mode 100644 application/common/model/Redpacket.php create mode 100644 application/common/model/RedpacketRecord.php create mode 100644 application/common/service/RedpacketService.php create mode 100644 application/extra/redis.php diff --git a/application/api/controller/Redpacket.php b/application/api/controller/Redpacket.php new file mode 100644 index 0000000..d8772f9 --- /dev/null +++ b/application/api/controller/Redpacket.php @@ -0,0 +1,104 @@ +request->post(); + + $data['user_id'] = $this->uid; + + $service = new RedpacketService(); + $reslut = $service->create($data); + + return V($reslut['code'], $reslut['msg'], $reslut['data']); + } + + /** + * 抢红包 + */ + public function grab() + { + $redpacketId = $this->request->post('redpacket_id'); + $password = $this->request->post('password', ''); + + if (empty($redpacketId)) { + return V(0, '红包ID不能为空'); + } + + $service = new RedpacketService(); + $reslut = $service->grabWithResult($redpacketId, $this->uid, $password); + + return V($reslut['code'], $reslut['msg'], $reslut['data']); + } + + /** + * 获取抢红包结果 + */ + public function grabResult() + { + $user = $this->auth->getUser(); + $redpacketId = $this->request->get('redpacket_id'); + + if (empty($redpacketId)) { + $this->error('红包ID不能为空'); + } + + $service = new RedpacketService(); + $result = $service->getGrabResult($redpacketId, $user->id); + + if (!$result) { + $this->error('红包不存在'); + } + + $this->success('获取成功', $result); + } + + /** + * 红包详情 + */ + public function detail() + { + $redpacketId = $this->request->get('redpacket_id'); + $currentUserId = $this->auth->isLogin() ? $this->auth->id : 0; + + if (empty($redpacketId)) { + $this->error('红包ID不能为空'); + } + + try { + $service = new RedpacketService(); + $detail = $service->getDetail($redpacketId, $currentUserId); + + if (!$detail) { + $this->error('红包不存在'); + } + + $this->success('获取成功', $detail); + + } catch (\Exception $e) { + $this->error($e->getMessage()); + } + } + + /** + * 获取倒计时选项 + */ + public function countdownOptions() + { + $options = \app\common\model\Redpacket::$countdownOptions; + $this->success('获取成功', $options); + } +} \ No newline at end of file diff --git a/application/common/library/RedpacketLua.php b/application/common/library/RedpacketLua.php new file mode 100644 index 0000000..1ec11be --- /dev/null +++ b/application/common/library/RedpacketLua.php @@ -0,0 +1,105 @@ + endTime then + redis.call('HSET', redpacketKey, 'status', 2) + return {0, "红包已结束"} +end + +-- 检查是否已经抢过 +local hasGrabbed = redis.call('SISMEMBER', userSetKey, userId) +if hasGrabbed == 1 then + return {0, "已经抢过该红包"} +end + +-- 检查是否还有剩余 +local leftAmount = tonumber(redpacket['left_amount']) +local leftCount = tonumber(redpacket['left_count']) + +if leftCount <= 0 or leftAmount <= 0 then + return {0, "红包已抢完"} +end + +-- 计算红包金额 +local amount = 0 +if leftCount == 1 then + -- 最后一个红包,获得剩余所有金额 + amount = leftAmount +else + -- 随机算法:二倍均值法,保证公平性 + local maxAmount = leftAmount / leftCount * 2 + amount = math.random(1, math.floor(maxAmount * 100)) / 100 + -- 确保金额不会超过剩余金额 + if amount > leftAmount then + amount = leftAmount + end +end + +-- 更新红包数据 +local newLeftAmount = leftAmount - amount +local newLeftCount = leftCount - 1 + +redis.call('HSET', redpacketKey, 'left_amount', newLeftAmount) +redis.call('HSET', redpacketKey, 'left_count', newLeftCount) + +-- 标记用户已抢 +redis.call('SADD', userSetKey, userId) + +-- 如果抢完了,更新状态 +if newLeftCount == 0 then + redis.call('HSET', redpacketKey, 'status', 2) +end + +return {1, tostring(amount)} +LUA; + } +} \ No newline at end of file diff --git a/application/common/model/Redpacket.php b/application/common/model/Redpacket.php new file mode 100644 index 0000000..e061387 --- /dev/null +++ b/application/common/model/Redpacket.php @@ -0,0 +1,130 @@ + '立刻', + 60 => '1分钟', + 120 => '2分钟', + 300 => '5分钟', + 600 => '10分钟' + ]; + + // 领取条件 + const CONDITION_NONE = 0; + const CONDITION_COLLECT_ROOM = 1; + const CONDITION_MIC_USER = 2; + + protected $autoWriteTimestamp = true; + protected $createTime = 'createtime'; + protected $updateTime = 'updatetime'; + + /** + * 发红包 + */ + public function createRedpacket($data) + { + Db::startTrans(); + try { + // 验证用户余额 + $wallet = Db::name('user_wallet')->where('user_id', $data['user_id'])->find(); + + $coinField = $data['coin_type'] == self::COIN_GOLD ? 'coin' : 'earnings'; + if ($wallet[$coinField] < $data['total_amount']) { + return V(0, '余额不足'); + } + + // 扣除余额 + Db::name('user_wallet') + ->where('user_id', $data['user_id']) + ->dec($coinField, $data['total_amount']) + ->update(); + + // 计算开始时间 + $startTime = $data['countdown'] > 0 ? (time() + $data['countdown']) : time(); + $endTime = $startTime + 120; // 2分钟后结束 + + // 创建红包 + $redpacketData = [ + 'user_id' => $data['user_id'], + 'room_id' => $data['room_id'], + 'type' => $data['type'], + 'password' => $data['password'] ?? '', + 'countdown' => $data['countdown'], + 'coin_type' => $data['coin_type'], + 'total_amount' => $data['total_amount'], + 'total_count' => $data['total_count'], + 'left_amount' => $data['total_amount'], + 'left_count' => $data['total_count'], + 'conditions' => $data['conditions'] ?? '', + 'status' => $data['countdown'] > 0 ? self::STATUS_PENDING : self::STATUS_ACTIVE, + 'start_time' => $startTime, + 'end_time' => $endTime, + 'createtime' => time() + ]; + + $redpacketId = $this->insertGetId($redpacketData); + + // 设置Redis缓存 + $redis = \think\Cache::store('redis')->handler(); + $redisKey = "redpacket:{$redpacketId}"; + $redis->hMSet($redisKey, [ + 'total_amount' => $data['total_amount'], + 'left_amount' => $data['total_amount'], + 'total_count' => $data['total_count'], + 'left_count' => $data['total_count'], + 'status' => $redpacketData['status'], + 'start_time' => $startTime, + 'end_time' => $endTime + ]); + + // 设置过期时间 + $redis->expireAt($redisKey, $endTime + 3600); // 结束后保留1小时 + + Db::commit(); +// return $redpacketId; + return V(1, '发红包成功', $redpacketId); + + } catch (\Exception $e) { + Db::rollback(); + return V(0, $e); +// throw $e; + } + } + + /** + * 获取红包信息 + */ + public function getRedpacketInfo($id) + { + $redpacket = $this->find($id); + if (!$redpacket) { + return null; + } + + $redpacket = $redpacket->toArray(); + $redpacket['nickname'] = Db::name('user')->where('id', $redpacket['user_id'])->value('nickname'); + + return $redpacket; + } +} \ No newline at end of file diff --git a/application/common/model/RedpacketRecord.php b/application/common/model/RedpacketRecord.php new file mode 100644 index 0000000..3bf1f2f --- /dev/null +++ b/application/common/model/RedpacketRecord.php @@ -0,0 +1,12 @@ +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; + } +} \ No newline at end of file diff --git a/application/extra/redis.php b/application/extra/redis.php new file mode 100644 index 0000000..9335dd0 --- /dev/null +++ b/application/extra/redis.php @@ -0,0 +1,12 @@ + '127.0.0.1', + 'port' => 6379, + 'password' => '', + 'select' => 0, + 'timeout' => 0, + 'expire' => 0, + 'persistent' => false, + 'prefix' => 'fa_redpacket_', +]; \ No newline at end of file