Files
yusheng-php/application/common/library/RedpacketLua.php

123 lines
3.4 KiB
PHP
Raw Permalink Normal View History

2025-10-20 10:02:41 +08:00
<?php
namespace app\common\library;
class RedpacketLua
{
/**
* 抢红包Lua脚本
* 保证原子性操作,防止超抢
*/
public static function grabRedpacketScript()
{
return <<<LUA
-- KEYS[1]: 红包key, KEYS[2]: 用户集合key, KEYS[3]: 用户ID
-- ARGV[1]: 当前时间
local redpacketKey = KEYS[1]
local userSetKey = KEYS[2]
local userId = KEYS[3]
local currentTime = tonumber(ARGV[1])
-- 检查红包是否存在
local redpacketData = redis.call('HGETALL', redpacketKey)
if not redpacketData or #redpacketData == 0 then
return {0, "红包不存在", 0}
end
-- 将哈希数据转为table
local redpacket = {}
for i = 1, #redpacketData, 2 do
redpacket[redpacketData[i]] = redpacketData[i + 1]
end
-- 检查红包状态
local status = tonumber(redpacket['status'])
local startTime = tonumber(redpacket['start_time'])
local endTime = tonumber(redpacket['end_time'])
if status == 0 then
if currentTime < startTime then
return {0, "红包还未开始", 0}
else
-- 更新状态为进行中(1)
redis.call('HSET', redpacketKey, 'status', 1)
status = 1
end
end
if status ~= 1 then
return {0, "红包已结束", 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, "已经抢过该红包", 0}
end
-- 检查是否还有剩余
local leftAmount = tonumber(redpacket['left_amount'])
local leftCount = tonumber(redpacket['left_count'])
if leftCount <= 0 or leftAmount <= 0 then
return {0, "红包已抢完", 0}
end
2025-12-22 17:52:31 +08:00
-- 计算红包金额核心修复解决3金币2红包场景第二个红包为0的问题
2025-10-20 10:02:41 +08:00
local amount = 0
local isFinished = 0
if leftCount == 1 then
2025-12-22 17:43:32 +08:00
-- 最后一个红包获得剩余所有金额确保至少1
2025-10-20 10:02:41 +08:00
amount = leftAmount
isFinished = 1
else
2025-12-22 17:52:31 +08:00
-- 随机算法:二倍均值法(适配整数+保底逻辑彻底避免0
-- 1. 基础二倍均值(整数处理)
2025-12-22 17:43:32 +08:00
local avg = leftAmount / leftCount
local maxAmount = math.floor(avg * 2)
2025-12-22 17:52:31 +08:00
-- 2. 核心修复:保底限制 - 最大金额不能超过「剩余金额 - (剩余个数 - 1)
-- 确保剩下的每个红包至少能分到1个金币比如3金币2红包3 - (2-1) = 2第一个红包最多抢2
2025-12-22 17:43:32 +08:00
local safeMax = leftAmount - (leftCount - 1)
maxAmount = math.min(maxAmount, safeMax)
2025-12-22 17:52:31 +08:00
-- 3. 强制保证最小金额为1最大金额不小于1
maxAmount = math.max(maxAmount, 1)
-- 4. 随机生成1到maxAmount之间的整数绝对不会出现0
2025-12-22 17:43:32 +08:00
amount = math.random(1, maxAmount)
2025-12-22 17:52:31 +08:00
-- 5. 整数场景下的抢完判断(无需浮点数误差处理)
if (leftCount - 1) == 1 and (leftAmount - amount) == 0 then
2025-10-20 10:02:41 +08:00
isFinished = 1
end
end
2025-12-22 17:43:32 +08:00
-- 更新红包数据(整数处理,无小数)
2025-10-20 10:02:41 +08:00
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)
-- 如果抢完了,更新状态为已结束(2)
if isFinished == 1 then
redis.call('HSET', redpacketKey, 'status', 2)
end
return {1, tostring(amount), isFinished}
LUA;
}
}