Files
yusheng-php/application/common/library/RedpacketLua.php
2025-12-22 17:52:31 +08:00

123 lines
3.4 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?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
-- 计算红包金额核心修复解决3金币2红包场景第二个红包为0的问题
local amount = 0
local isFinished = 0
if leftCount == 1 then
-- 最后一个红包获得剩余所有金额确保至少1
amount = leftAmount
isFinished = 1
else
-- 随机算法:二倍均值法(适配整数+保底逻辑彻底避免0
-- 1. 基础二倍均值(整数处理)
local avg = leftAmount / leftCount
local maxAmount = math.floor(avg * 2)
-- 2. 核心修复:保底限制 - 最大金额不能超过「剩余金额 - (剩余个数 - 1)」
-- 确保剩下的每个红包至少能分到1个金币比如3金币2红包3 - (2-1) = 2第一个红包最多抢2
local safeMax = leftAmount - (leftCount - 1)
maxAmount = math.min(maxAmount, safeMax)
-- 3. 强制保证最小金额为1最大金额不小于1
maxAmount = math.max(maxAmount, 1)
-- 4. 随机生成1到maxAmount之间的整数绝对不会出现0
amount = math.random(1, maxAmount)
-- 5. 整数场景下的抢完判断(无需浮点数误差处理)
if (leftCount - 1) == 1 and (leftAmount - amount) == 0 then
isFinished = 1
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)
-- 如果抢完了,更新状态为已结束(2)
if isFinished == 1 then
redis.call('HSET', redpacketKey, 'status', 2)
end
return {1, tostring(amount), isFinished}
LUA;
}
}