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

120 lines
3.3 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
-- 计算红包金额核心修改适配整数金币避免0
local amount = 0
local isFinished = 0
if leftCount == 1 then
-- 最后一个红包获得剩余所有金额确保至少1
amount = leftAmount
isFinished = 1
else
-- 随机算法二倍均值法适配整数保证最低1
-- 1. 计算二倍均值(整数处理,向下取整)
local avg = leftAmount / leftCount
local maxAmount = math.floor(avg * 2)
-- 2. 确保最大金额至少为1且不超过剩余金额-(剩余个数-1避免后续红包无金额可分
-- 剩余金额-(剩余个数-1保证剩下的每个红包至少能分1个金币
local safeMax = leftAmount - (leftCount - 1)
maxAmount = math.min(maxAmount, safeMax)
maxAmount = math.max(maxAmount, 1) -- 确保最小可随机值为1
-- 3. 随机生成1到maxAmount之间的整数避免0
amount = math.random(1, maxAmount)
-- 4. 检查是否抢完(整数判断,无需浮点数误差处理)
if leftCount - 1 == 1 or (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;
}
}