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:43:32 +08:00
|
|
|
|
-- 计算红包金额(核心修改:适配整数金币,避免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:43:32 +08:00
|
|
|
|
-- 随机算法:二倍均值法(适配整数,保证最低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
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|