From 4aac4e91091aa0f64d7abced860f20d18df9dae0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E5=8D=8E=E6=B8=85?= <18691022700@163.com> Date: Tue, 14 Oct 2025 18:09:49 +0800 Subject: [PATCH] =?UTF-8?q?=E7=BA=A2=E5=8C=85=E7=8A=B6=E6=80=81=E4=BF=AE?= =?UTF-8?q?=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- application/api/controller/Redpacket.php | 4 + application/common/library/RedpacketLua.php | 39 +++++---- .../common/service/RedpacketService.php | 83 ++++++++++++++++++- .../cron/controller/PerformPerSecond.php | 44 ++++++++-- 4 files changed, 143 insertions(+), 27 deletions(-) diff --git a/application/api/controller/Redpacket.php b/application/api/controller/Redpacket.php index 0315c69..7d12c58 100644 --- a/application/api/controller/Redpacket.php +++ b/application/api/controller/Redpacket.php @@ -39,6 +39,8 @@ class Redpacket extends BaseCom } $service = new RedpacketService(); + // 在抢红包前确保状态正确 + $service->checkAndUpdateRedpacketStatus($redpacketId); $reslut = $service->grabWithResult($redpacketId, $this->uid); return V($reslut['code'], $reslut['msg'], $reslut['data']); @@ -77,6 +79,8 @@ class Redpacket extends BaseCom } $service = new RedpacketService(); + // 在获取详情前确保状态正确 + $service->checkAndUpdateRedpacketStatus($redpacketId); $detail = $service->getDetail($redpacketId, $this->uid); if (!$detail) { diff --git a/application/common/library/RedpacketLua.php b/application/common/library/RedpacketLua.php index c3c02ac..e5359ae 100644 --- a/application/common/library/RedpacketLua.php +++ b/application/common/library/RedpacketLua.php @@ -22,7 +22,7 @@ local currentTime = tonumber(ARGV[1]) -- 检查红包是否存在 local redpacketData = redis.call('HGETALL', redpacketKey) if not redpacketData or #redpacketData == 0 then - return {0, "红包不存在"} + return {0, "红包不存在", 0} end -- 将哈希数据转为table @@ -38,27 +38,27 @@ local endTime = tonumber(redpacket['end_time']) if status == 0 then if currentTime < startTime then - return {0, "红包还未开始"} + return {0, "红包还未开始", 0} else - -- 更新状态为进行中 + -- 更新状态为进行中(1) redis.call('HSET', redpacketKey, 'status', 1) status = 1 end end --- if status ~= 1 then - -- return {0, "红包已结束"} --- end +if status ~= 1 then + return {0, "红包已结束", 0} +end --- if currentTime > endTime then --- redis.call('HSET', redpacketKey, 'status', 2) --- return {0, "红包已结束"} --- end +if currentTime > endTime then + redis.call('HSET', redpacketKey, 'status', 2) + return {0, "红包已结束", 0} +end -- 检查是否已经抢过 local hasGrabbed = redis.call('SISMEMBER', userSetKey, userId) if hasGrabbed == 1 then - return {0, "已经抢过该红包"} + return {0, "已经抢过该红包", 0} end -- 检查是否还有剩余 @@ -66,14 +66,17 @@ local leftAmount = tonumber(redpacket['left_amount']) local leftCount = tonumber(redpacket['left_count']) if leftCount <= 0 or leftAmount <= 0 then - return {0, "红包已抢完"} + return {0, "红包已抢完", 0} end -- 计算红包金额 local amount = 0 +local isFinished = 0 + if leftCount == 1 then -- 最后一个红包,获得剩余所有金额 amount = leftAmount + isFinished = 1 else -- 随机算法:二倍均值法,保证公平性 local maxAmount = leftAmount / leftCount * 2 @@ -82,6 +85,10 @@ else if amount > leftAmount then amount = leftAmount end + -- 检查是否是最后一个(由于浮点数计算可能有误差) + if leftCount == 1 or (leftAmount - amount) < 0.01 then + isFinished = 1 + end end -- 更新红包数据 @@ -94,12 +101,14 @@ redis.call('HSET', redpacketKey, 'left_count', newLeftCount) -- 标记用户已抢 redis.call('SADD', userSetKey, userId) --- 如果抢完了,更新状态 -if newLeftCount == 0 then +-- 如果抢完了,更新状态为已结束(2) +if isFinished == 1 then redis.call('HSET', redpacketKey, 'status', 2) end -return {1, tostring(amount)} +return {1, tostring(amount), isFinished} LUA; } + + } \ No newline at end of file diff --git a/application/common/service/RedpacketService.php b/application/common/service/RedpacketService.php index abef424..bd155da 100644 --- a/application/common/service/RedpacketService.php +++ b/application/common/service/RedpacketService.php @@ -32,6 +32,9 @@ class RedpacketService */ public function grabWithResult($redpacketId, $userId) { + // 首先检查并更新红包状态 + $this->checkAndUpdateRedpacketStatus($redpacketId); + $redpacketModel = new Redpacket(); $redpacket = $redpacketModel->getRedpacketInfo($redpacketId); @@ -103,6 +106,7 @@ class RedpacketService } $amount = floatval($result[1]); + $isFinished = $result[2] == 1; // Lua脚本返回是否抢完 // Lua脚本执行成功,记录到数据库 Db::startTrans(); @@ -142,12 +146,20 @@ class RedpacketService Db::rollback(); } - // 更新红包剩余数量和金额 + // 更新红包剩余数量和金额,如果抢完了更新状态 + $updateData = [ + 'left_amount' => Db::raw('left_amount - ' . $amount), + 'left_count' => Db::raw('left_count - 1'), + 'updatetime' => time() + ]; + + if ($isFinished) { + $updateData['status'] = Redpacket::STATUS_FINISHED; + } + Db::name('redpacket') ->where('id', $redpacketId) - ->dec('left_amount', $amount) - ->dec('left_count', 1) - ->update(); + ->update($updateData); Db::commit(); @@ -585,4 +597,67 @@ class RedpacketService } return true; } + + /** + * 检查并更新红包状态 + * 在抢红包前调用,确保状态正确 + */ + public function checkAndUpdateRedpacketStatus($redpacketId) + { + $redpacket = Db::name('redpacket')->where('id', $redpacketId)->find(); + if (!$redpacket) { + return false; + } + + $now = time(); + $redis = Cache::store('redis')->handler(); + $redpacketKey = "redpacket:{$redpacketId}"; + + // 如果红包状态为未开始(0),但当前时间已超过开始时间,则更新为进行中(1) + if ($redpacket['status'] == Redpacket::STATUS_PENDING && $now >= $redpacket['start_time']) { + Db::name('redpacket') + ->where('id', $redpacketId) + ->update([ + 'status' => Redpacket::STATUS_ACTIVE, + 'updatetime' => $now + ]); + + // 更新Redis中的状态 + $redis->hSet($redpacketKey, 'status', Redpacket::STATUS_ACTIVE); + + return true; + } + + // 如果红包状态为进行中(1),但已抢完,则更新为已结束(2) + if ($redpacket['status'] == Redpacket::STATUS_ACTIVE && $redpacket['left_count'] <= 0) { + Db::name('redpacket') + ->where('id', $redpacketId) + ->update([ + 'status' => Redpacket::STATUS_FINISHED, + 'updatetime' => $now + ]); + + // 更新Redis中的状态 + $redis->hSet($redpacketKey, 'status', Redpacket::STATUS_FINISHED); + + return true; + } + + // 如果红包状态为进行中(1),但已超过结束时间,则更新为已结束(2) + if ($redpacket['status'] == Redpacket::STATUS_ACTIVE && $now > $redpacket['end_time']) { + Db::name('redpacket') + ->where('id', $redpacketId) + ->update([ + 'status' => Redpacket::STATUS_FINISHED, + 'updatetime' => $now + ]); + + // 更新Redis中的状态 + $redis->hSet($redpacketKey, 'status', Redpacket::STATUS_FINISHED); + + return true; + } + + return false; + } } \ No newline at end of file diff --git a/application/cron/controller/PerformPerSecond.php b/application/cron/controller/PerformPerSecond.php index b2f5dfd..3062313 100644 --- a/application/cron/controller/PerformPerSecond.php +++ b/application/cron/controller/PerformPerSecond.php @@ -240,9 +240,36 @@ class PerformPerSecond */ public function processExpiredRedpackets() { - // 查找已结束但未退款的红包 + $now = time(); + $processedCount = 0; + + // 1. 处理到时间的未开始红包,更新为进行中 + $pendingRedpackets = Db::name('redpacket') + ->where('status', Redpacket::STATUS_PENDING) + ->where('start_time', '<=', $now) + ->select(); + + foreach ($pendingRedpackets as $redpacket) { + Db::name('redpacket') + ->where('id', $redpacket['id']) + ->update([ + 'status' => Redpacket::STATUS_ACTIVE, + 'updatetime' => $now + ]); + + // 更新Redis缓存 + $redis = Cache::store('redis')->handler(); + $redisKey = "redpacket:{$redpacket['id']}"; + $redis->hSet($redisKey, 'status', Redpacket::STATUS_ACTIVE); + + $processedCount++; + } + + // 2. 处理已过期的进行中红包,更新为已结束并退款 $expiredRedpackets = Db::name('redpacket') - ->where(['end_time' => ['<',time()], 'status' => 1]) + ->where('status', Redpacket::STATUS_ACTIVE) + ->where('end_time', '<', $now) + ->where('left_count', '>', 0) ->select(); foreach ($expiredRedpackets as $redpacket) { @@ -276,27 +303,28 @@ class PerformPerSecond } } - // 更新红包状态 + // 更新红包状态为已结束 Db::name('redpacket') ->where('id', $redpacket['id']) ->update([ - 'status' => 3, - 'updatetime' => time() + 'status' => Redpacket::STATUS_FINISHED, + 'updatetime' => $now ]); - // 清理Redis缓存 + // 更新Redis缓存 $redis = Cache::store('redis')->handler(); $redisKey = "redpacket:{$redpacket['id']}"; - $redis->del($redisKey); + $redis->hSet($redisKey, 'status', Redpacket::STATUS_FINISHED); Db::commit(); - + $processedCount++; } catch (\Exception $e) { Db::rollback(); // 记录日志 \think\Log::error("红包退款失败: {$redpacket['id']}, 错误: " . $e->getMessage()); } } + echo "处理过期红包-共". $processedCount . "条数据\n"; } } \ No newline at end of file