红包分发系统:Lua代码逻辑与实现

引言

在线互动和虚拟赠礼的世界中,数字红包的概念已经广受欢迎。这些数字红包代表了一种货币礼物形式,在特殊场合和节日中常常互相赠送。在幕后,一个复杂的系统运作,以确保这些红包在公平分发的同时保持接收者的期待和悬念。在本文中,我们将深入探讨一个Lua脚本,旨在管理数字红包的分发,探讨其逻辑和实现细节。

红包系统

此处提供的Lua脚本概述了数字红包分发的核心逻辑。这些红包可以以三种不同的方式分发:

  1. 均分红包:总金额平均分配给接收者。

  2. 碰运气红包:总金额以随机的方式分配给接收者,带有一定的随机性。

  3. 固定金额:每个接收者都获得固定金额(适用于一对一互动)。

该脚本还具备防止多次领取的功能(即一个接收者多次领取同一个红包),并追踪剩余红包的数量和金额。

代码逻辑解释

  1. 初始化:脚本首先定义了各种结果和红包类型的表,包括GetResultSetResultRedEnvType等。这些表定义了整个脚本中使用的不同结果和类型。

  2. 实用函数:脚本定义了几个实用函数,如GetSetDelHSetnxHGet等。这些函数与Redis交互,执行获取、设置和删除值等操作。

  3. 红包分发逻辑:脚本的核心是用于分发红包的函数。它遵循以下步骤:

    a. 状态检查:脚本首先检查红包的状态。如果状态表明红包已经完全分发完毕,则流程终止。

    b. 接收者检查:脚本检查接收者是否已经领取过红包。如果是,则流程终止,以避免重复领取。

    c. 剩余金额检查:脚本检查红包中剩余的金额。如果剩余金额为零或负数,则流程终止。

    d. 红包数量更新:剩余红包数量递减。如果递减失败,则流程终止。

    e. 金额计算:根据红包类型(均分或随机),为接收者计算适当的金额。

    f. 剩余金额更新:在分配给接收者后,更新红包中的剩余金额。

    g. 接收者记录:记录接收者的用户ID和分配的金额,以防止进一步的领取。

    h. 完成检查:如果剩余金额达到零,将红包的状态更新为已完成。

  4. 响应生成:脚本根据分发过程的结果生成响应。这些响应提供有关分发尝试状态、分配金额和剩余数量的信息。

  5. 输入和键值:脚本从Redis命令中接收键和参数。这些包括红包键、用户ID(UID)和红包类型。键值使用前缀构建,以促进组织。

实施考虑因素

  1. 并发性和竞态条件:由于Redis操作是非阻塞的,存在竞态条件的潜在可能性,特别是在多用户场景中。必须有适当的同步机制,以确保数据一致性。

  2. 错误处理:脚本在各个阶段都包含错误处理,防止不一致的数据状态。在生产环境中,应有全面的错误处理机制,以优雅地处理各种故障情况。

  3. 可扩展性:尽管该脚本演示了基本的红包分发逻辑,一个完整的红包分发系统需要考虑水平扩展,以处理大量同时用户。

  4. 安全性:必须建立认证和授权机制,以确保只有授权用户能够与脚本进行交互。

  5. 监控和日志记录:应使用监控工具来跟踪脚本的性能和行为。此外,日志记录机制可以帮助调试问题。

结论

所提供的Lua脚本示范了在在线生态系统中管理数字红包分发所涉及的复杂性。从检查状态到计算分配金额和防止重复领取,脚本为发送者和接收者提供了无缝的体验。尽管脚本作为基础,但健壮的红包分发系统需要严格的测试、适当的错误处理以及对可扩展性和安全性的考虑。通过深入了解这种系统的逻辑,我们更加深刻地理解了支持我们数字互动的幕后机制。

项目示例

https://github.com/sevtin/lark

local GetResult = {
    Success = 'Success',
    NotFound = 'NotFound',
    Failed = 'Failed'
}

local SetResult = {
    Success = 'Success',
    Failed = 'Failed'
}

local DelResult = {
    Success = 'Success',
    Failed = 'Failed'
}

local HSetnxResult = {
    Success = 'Success',
    Failed = 'Failed'
}

local HGetResult = {
    Success = 'Success',
    Failed = 'Failed'
}

local HDelResult = {
    Success = 'Success',
    Failed = 'Failed'
}

local DecrResult = {
    Success = 'Success',
    Failed = 'Failed'
}

local DecrbyResult = {
    Success = 'Success',
    Failed = 'Failed'
}

local IncrbyResult = {
    Success = 'Success',
    Failed = 'Failed'
}

local IncrResult = {
    Success = 'Success',
    Failed = 'Failed'
}

--AVERAGE = 1; 均分红包
--RANDOM = 2; 碰运气红包
--FIXED = 3; 固定金额(单聊)
local RedEnvType = {
    Average = '1',
    Random = '2',
    Fixed = '3'
}

local function Get(key)
    local result = redis.call('GET', key)
    if result == false then
        return GetResult.NotFound, 0
    elseif result == nil then
        return GetResult.Error, 0
    else
        if type(result) ~= 'string' then
            return GetResult.Error, 0
        end
        return GetResult.Success, tonumber(result)
    end
end

local function Set(key, value, expire)
    local ok, err = redis.call('SET', key, value)
    if not ok then
        return SetResult.Failed
    end

    if expire > 0 then
        redis.call('EXPIRE', key, expire)
    end

    return SetResult.Success
end

local function Del(key)
    local result = redis.call('DEL', key)
    -- 也可用 result >= 0 判断
    if type(result) == 'number' then
        return DelResult.Success
    else
        return DelResult.Failed
    end
end

local function HSetnx(key, field, value)
    local result = redis.call('HSETNX', key, field, value)
    if result == 1 then
        return HSetnxResult.Success
    else
        return HSetnxResult.Failed
    end
end

local function HGet(key, field)
    local result = redis.call('HGET', key, field)
    if result == false then
        return HGetResult.Failed, 0
    else
        return HGetResult.Success, tonumber(result)
    end
end

local function HDel(key, field)
    local result = redis.call('HDEL', key, field)
    if result == 1 then
        return HDelResult.Success
    else
        return HDelResult.Failed
    end
end

local function Decr(key)
    local result = redis.call('DECR', key)
    if type(result) ~= 'number' then
        return DecrResult.Failed, 0
    end
    return DecrResult.Success, tonumber(result)
end

local function Decrby(key, decrement)
    local result = redis.call('DECRBY', key, decrement)
    if result then
        return DecrbyResult.Success, tonumber(result)
    else
        return DecrbyResult.Failed, 0
    end
end

local function Incrby(key, decrement)
    local result = redis.call('INCRBY', key, decrement)
    if result then
        return IncrbyResult.Success, tonumber(result)
    else
        return IncrbyResult.Failed, 0
    end
end

local function Incr(key)
    local result = redis.call('INCR', key)
    if type(result) ~= 'number' then
        return IncrResult.Failed, 0
    end
    return IncrResult.Success,tonumber(result)
end

-- 回滚剩余红包数量
local function rollbackRemainQuantity(key)
    return Incr(key)
end

-- 回滚剩余红包金额
local function rollbackRemainAmount(key, value)
    return Set(key, value)
end

-- 删除红包领取记录
local function rollbackRecord(key, field)
    return HDel(key, field)
end

-- amount:剩余红包金额
-- num:剩余红包个数
local function  getRandomAmount(amount, num)
    if num == 1 or amount == 0 then
        return amount
    end

    local minAmount = 1
    local maxAmount = math.floor(amount/num)*2 - minAmount

    if maxAmount <= 0 then
        maxAmount = minAmount
    end

    return math.random(minAmount, maxAmount)
end

local function response(status, ...)
    return status .. ':' .. table.concat({...}, ':')
end

local red_env_key = KEYS[1]
local uid = ARGV[1]
local red_env_type = ARGV[2]
local remain_quantity_key = 'LK:RED_ENV:REMAIN_QUANTITY:'..red_env_key
local remain_amount_key = 'LK:RED_ENV:REMAIN_AMOUNT:'..red_env_key
local status_key = 'LK:RED_ENV:STATUS:'..red_env_key
local record_key = 'LK:RED_ENV:RECORD:'..red_env_key
local received_status = 2

-- 成功/失败:描述:剩余红包数量:剩余红包金额:本次发放金额
-- 1、获取红包状态
local result, status = Get(status_key)
if result ~= GetResult.Success then
    return response('FAILED','GET_STATUS_FAILED',0,0,0)
end

-- 红包已领完
if status == received_status then
    return response('FAILED','RED_ENV_OVER',0,0,0)
end

-- 2、是否已经领过红包
local result, received_amount = HGet(record_key, uid)
if result == HGetResult.Success then
    -- 已经领取
    return response('FAILED','RED_ENV_RECEIVED',0,0,0)
end

-- 3、获取红包余额
local result, remain_amount = Get(remain_amount_key)
if result ~= GetResult.Success then
    return response('FAILED','GET_REMAIN_AMOUNT_FAILED',0,0,0)
end

-- 红包余额异常
if remain_amount <= 0 then
    return response('FAILED','REMAIN_AMOUNT_ERROR',0,remain_amount,0)
end

-- 4、剩余红包数量
local result, remain_quantity = Decr(remain_quantity_key)
if result == DecrResult.Failed then
    return response('FAILED','DECR_REMAIN_QUANTITY_FAILED',0,0,0)
end

-- 红包数量异常
if remain_quantity < 0 then
    return response('FAILED','REMAIN_QUANTITY_ERROR',remain_quantity,0,0)
end

-- 5、更新红包余额
local red_env_amount = remain_amount
if red_env_type == RedEnvType.Average then
    red_env_amount = math.floor(remain_amount/(remain_quantity+1))
elseif red_env_type == RedEnvType.Random then
    red_env_amount = getRandomAmount(remain_amount, remain_quantity + 1)
end

local new_remain_amount = remain_amount - red_env_amount
local result = Set(remain_amount_key, new_remain_amount, 0)
if result == SetResult.Failed then
    rollbackRemainQuantity(remain_quantity_key)
    return response('FAILED','SET_REMAIN_AMOUNT_FAILED',remain_quantity+1,remain_amount,0)
end

-- 6、记录红包领取人
local result = HSetnx(record_key, uid, red_env_amount)
if result == HSetnxResult.Failed then
    rollbackRemainQuantity(remain_quantity_key)
    rollbackRemainAmount(remain_amount_key, remain_amount)
    return response('FAILED','HSETNX_RECORD_FAILED',remain_quantity+1,remain_amount,0)
end

if new_remain_amount == 0 then
    -- 5、红包已领完
    local result = Set(status_key, received_status, 0)
    if result == SetResult.Failed then
        rollbackRemainQuantity(remain_quantity_key)
        rollbackRemainAmount(remain_amount_key, remain_amount)
        rollbackRecord(record_key, uid)
        return response('FAILED','SET_RED_ENV_STATUS_FAILED',remain_quantity+1,remain_amount,0)
    end
end

return response('SUCCEED','RECEIVED',remain_quantity,new_remain_amount,red_env_amount)
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容