攻城报名按钮被快速连点,客户端一帧内投递了两次触发请求:处理函数没有任何防重,两次都通过校验、两次都扣报名费、名单里出现两条同名记录——玩家多花一倍报名费还以为赚了两个名额。学员以为"按钮点了两次是客户端的事",服务端不设防就是资损。错误场景:
local function onSignup(actor)
actor = getplayerbyname(actor)
takeitem(actor, "祖玛头像", 1, 0, "攻城报名")
setplayvar(actor, "HUMAN", "SiegeJoined", 1, 1)
sendmsg(actor, 1, "报名成功!")
end
一帧两次触发,扣两次头像、发两次成功提示。
处理入口配幂等标记:用毫秒级时间戳做窗口去重,窗口内的重复触发直接丢弃。规范写法。示例代码如下:
local function onSignup(actor)
actor = getplayerbyname(actor)
local now = os.clock() * 1000
local last = tonumber(getplayvar(actor, "HUMAN", "SignupTick")) or 0
if now - last < 200 then
return
end
setplayvar(actor, "HUMAN", "SignupTick", math.floor(now), 0)
if not takeitem(actor, "祖玛头像", 1, 0, "攻城报名") then
sendmsg(actor, 1, "背包中没有祖玛头像。")
return
end
setplayvar(actor, "HUMAN", "SiegeJoined", 1, 1)
sendmsg(actor, 1, "报名成功!")
end
200 毫秒窗口内的第二次触发被静默丢弃;先扣材料后置状态,扣材料失败即终止,状态不会出现半拉子。示例代码如下:
local function alreadyJoined(actor)
return getplayvar(actor, "HUMAN", "SiegeJoined") == 1
end
幂等窗口之外再加一层业务判重(已报名直接返回),两层防线各管一种重复。
三步验证:模拟一帧内两次调用,第二次被窗口拦截只扣一次材料;把窗口调大到 5000 毫秒复测,正常的两次相隔一秒的报名也被误拦——体会窗口大小的取舍;已报名的玩家再次触发,被业务判重直接返回。
不同操作的幂等键要独立:报名、捐献、兑换各用各的标记变量,共用一个时间戳窗口会互相误伤。示例代码如下:
local function guard(name, key, windowMs)
local now = os.clock() * 1000
local last = tonumber(getplayvar(name, "HUMAN", key)) or 0
if now - last < windowMs then
return false
end
setplayvar(name, "HUMAN", key, math.floor(now), 0)
return true
end
guard 把窗口去重抽成公共函数,key 区分操作,三个入口三把锁。
全站技术干货持续更新:996 引擎 / Lua 实战帖,语法、参数与示例一篇讲透。进入文章地图 · 查看全部 →
一、一行代码拆解:local function roll() 里写 local cfg = {500, 200, 50} —…
一、隐蔽陷阱:国库金币累加到 2^53(约 9007199254740992)之后再加 1,数值纹丝不动——双精度浮点在该区…
一、线上事故:仓库存取逻辑散在 6 个脚本,各自写各自的 getsysvar 键名,某次改名漏改 2 处,300 件裁决之杖…
一、抛坑提问:500 件战备装备一次下发必卡,按每页 20 件切片,边界怎么算才不出空页和重页?起点 (page-1) 20…
一、抛坑提问:校验失败在工具函数里 error,日志却指向工具函数那一行,排查总要翻两层。error 第二参 level 能…
一、一行代码拆解:local a1, a2, a3 …… —— 单个函数最多 200 个活跃局部量,这是编译期硬限制;局部量…