Redis Lua 里用 ARGV 传 JSON 时怎么避免类型误判
来源:17golang原创
时间:2026-09-08 03:46:52 104浏览 收藏
Redis Lua 里通过 ARGV 传入 JSON 时,最稳妥的做法是把它当作原始字符串接收,只在脚本入口用一次 cjson.decode,随后按对象和字段逐个校验类型。不要把“客户端传来的 JSON 字符串”“解码后的 Lua 表”和“要写入 Redis 的字符串”混成一个变量。这样既能保留脚本的原子性,也能避免数字、字符串、布尔值和空值在不同边界发生误判。
KEYS只放脚本实际访问的键,业务 JSON 放在ARGV。ARGV[1]先按字符串处理,cjson.decode只做一次,并用pcall兜住非法 JSON。- 写回 Redis 前重新编码对象,返回给客户端时明确区分字符串、整数和错误表。
ARGV 到底是什么:先把原始字符串留在边界外

EVAL script numkeys key [key ...] arg [arg ...] 中,前面的键名进入 KEYS,后面的普通参数进入 ARGV。因此一次调用可以写成下面这样:
-- KEYS[1] 是脚本要操作的键,ARGV[1] 是完整 JSON 字符串
EVAL "return ARGV[1]" 1 order:1001 '{"amount":12,"source":"api"}'
这里的 ARGV[1] 仍然是字符串。它不是 Lua 表,也不会因为内容长得像数字就自动变成数字。真正的类型变化发生在 cjson.decode 之后:JSON 对象会成为 Lua 表,JSON 数字会成为 Lua number,JSON 字符串仍是 Lua string。把这个边界写清楚,后面的判断才有依据。
先解码一次,再按业务类型收口
生产脚本不要到处重复解码,也不要拿 tostring 代替类型校验。下面的入口只接受一个 JSON 对象,并要求 amount 是数字、source 是字符串;任何不符合约定的输入都会在写入前结束。
-- 入口只负责解析和收口,不把未校验的字段写入 Redis
local raw = ARGV[1]
if type(raw) ~= "string" or raw == "" then
return { err = "ARGV[1] must be a non-empty JSON string" }
end
-- cjson.decode 可能抛出异常,用 pcall 把坏输入变成可识别的错误
local decoded_ok, payload = pcall(cjson.decode, raw)
if not decoded_ok or type(payload) ~= "table" then
return { err = "payload must be a JSON object" }
end
local amount = payload.amount
local source = payload.source
if type(amount) ~= "number" or type(source) ~= "string" then
return { err = "amount must be number and source must be string" }
end
-- Redis 命令参数使用明确的字符串表示,避免把 Lua 表直接传给 HSET
redis.call("HSET", KEYS[1], "amount", tostring(amount), "source", source)
return { "ok", tostring(amount) }
如果业务允许 amount 既可以是 JSON 数字也可以是数字字符串,要在协议层明确选择,而不是在脚本里无条件转换。否则 12 与 "12" 会悄悄失去区别,调用方也无法判断这是客户端传错类型,还是服务端主动兼容。
KEYS、ARGV 和返回值要分开设计

JSON 对象不能直接作为 Redis 命令的一个参数。需要保存整个对象时,先在 Lua 中完成校验,再用 cjson.encode 重新得到字符串;只保存字段时,则把每个字段显式转为命令接受的值。脚本返回值也要提前约定,因为 RESP2 下 Lua number 会按整数回复,带小数的结果不能直接这样返回。
-- 将已校验的数据重新编码后保存,避免把 Lua table 当成命令参数
local record = {
amount = amount,
source = source,
state = "accepted"
}
redis.call("SET", KEYS[1], cjson.encode(record))
-- 用字符串返回小数或需要保持格式的值,避免 RESP2 的整数转换
return {
status = "ok",
value = cjson.encode(record)
}
上面的关联表返回方式适合在脚本内部表达状态,但客户端看到的最终结构仍受 RESP2/RESP3 协议转换影响。若接口需要稳定的跨客户端响应,建议返回短字符串或定长数组,例如 {"ok", cjson.encode(record)},并在客户端统一解析。
发布前用边界清单挡住类型误判
| 检查位置 | 应保持的类型 | 常见错误 |
|---|---|---|
| KEYS | 真实键名 | 把 JSON 字段拼成动态键,集群路由和审计都变得不清晰 |
| ARGV | 原始字符串 | 同一参数重复解码,或未校验就写入 |
| Lua 表 | 脚本内部对象 | 直接作为 HSET/SET 参数传递 |
| 返回值 | 已约定的字符串/数组 | 把小数 Lua number 当作精确小数返回 |
上线前至少检查四件事:所有访问的键是否都在 KEYS;JSON 是否只解码一次;每个写入字段是否经过 type 判断;返回值是否在目标 RESP 协议下仍保持客户端约定。脚本仍然是一次原子执行,但原子性不会替你修复输入类型,边界校验必须放在命令写入之前。
常见问题
ARGV[1] 是不是可以直接当 Lua 表使用?
不可以。它先是客户端传入的字符串,必须经过 cjson.decode 才会得到 Lua 值;解码前应先检查空串和输入来源。
JSON 对象为什么不能直接传给 HSET?
解码后的对象是 Lua table,不是 Redis 命令参数。要么取出字段并逐项转换,要么用 cjson.encode 把完整对象重新变回字符串。
为什么返回小数时建议转字符串?
Lua 只有一种 number 类型,RESP2 返回 Lua number 时会按整数规则转换。需要保留小数文本时,应按字符串返回并由客户端解析。
-
114 收藏
-
数据库 · Redis | 2小时前 | 消息队列 · 消费组 · Redis Streams · 故障接管 · redis streams 消费组 XREADGROUP XACK XPENDING XAUTOCLAIM192 收藏
-
408 收藏
-
279 收藏
-
325 收藏
-
460 收藏
-
480 收藏
-
152 收藏
-
数据库 · Redis | 13小时前 | Redis · cluster · slot迁移 · MOVED · ASK · 客户端路由 · redis slot Redis Cluster MOVED ASK reshard 拓扑刷新157 收藏
-
188 收藏
-
470 收藏
-
251 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习