lua脚本无法查mysql用户角色表,因其运行在redis服务端沙箱中,仅能访问显式传入的keys和argv参数;权限校验需由应用层预查并传入维度数据,脚本只做原子比对。

为什么不能在 Lua 脚本里查用户角色表
Redis Lua 脚本运行在服务端沙箱里,完全隔离于你的应用进程。它读不到 MySQL 里的 agent_user 表,也访问不了 Java 的 AgentPermissionDTO 对象——所谓“查数据库再比对”,必须拆成两步:应用层查、Lua 层只做原子判断。
常见错误是写类似 redis.call('GET', 'user:role:' .. KEYS[1]) 这种拼接 key 的脚本,既违反 Redis 集群的 key 路由规则(报错 ERR no matching key found for specified pattern),又把权限逻辑耦合进脚本,后期改权限模型就得全量更新所有 Lua 脚本。
- 脚本只能访问显式传入的
KEYS数组里的 key,不能动态构造 key 名 - 角色、等级、通道标识等维度数据,应提前由业务代码查好、序列化后作为
ARGV传入 - 比如
ARGV[1]是用户代理等级2(A级),ARGV[2]是通道签名smssign_vip,脚本只负责匹配策略规则
如何用 Lua 原子校验多维权限组合
细粒度权限本质是「多个布尔条件的与运算」,但必须在一次 Redis 请求中完成,否则中间状态可能被并发修改。正确做法是把策略规则预存为 Redis Hash 或 Set,用 Lua 脚本一次性比对。
例如 B 级代理默认只能走普通通道,但若 channel_sign 匹配白名单,则允许升级:
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
local level = tonumber(ARGV[1])
local sign = ARGV[2]
local ok = redis.call('HEXISTS', 'perm:level:channel', level .. ':' .. sign)
if ok == 1 then
return 1
else
return 0
end
-
perm:level:channel是一个 Hash,field 为"2:smssign_vip",value 可为 1 或空 - 避免循环遍历:不要写
for i=1,#ARGV do ...去逐个查 key,性能差且易超时 - 集群环境下,所有涉及的 key 必须落在同一个 slot,所以 key 设计要带固定前缀并哈希一致
KEYS 和 ARGV 的分工必须严格
Redis 强制要求脚本中所有访问的 key 必须显式声明在 KEYS 参数里,这是为了集群路由和安全沙箱。而 ARGV 只能承载非 key 类型的参数,比如用户等级、IP、时间戳、签名字符串等。
典型错误示例:jedis.eval("return redis.call('GET', KEYS[1] .. ':lock')", 1, "user:1001") —— 拼接出来的 user:1001:lock 不在 KEYS 列表中,Redis 7.0+ 直接拒绝执行。
-
KEYS数量必须等于调用时传入的 key 个数,不能少也不能多 - 像 IP 黑名单这种需要动态 key 的场景,得把完整 key 名(如
blacklist:ip:192.168.1.100)作为KEYS[1]传入,而不是只传192.168.1.100再拼接 -
ARGV里禁止出现冒号、空格等可能干扰 key 解析的字符,建议统一 base64 编码或 URL-safe 处理
本地缓存版本号必须由客户端传入
当权限数据同时存在 Redis 和本地缓存(如 Caffeine)时,一致性校验不能靠应用层先 GET 再比对——这会引入竞态窗口。必须让 Lua 脚本原子执行「读 Redis 版本 + 比对客户端传入的本地版本」。
比如权限配置变更后,Redis 中 perm:version:user:1001 更新为 123456,客户端需在请求时把当前本地缓存的版本号(比如 123455)作为 ARGV[1] 传入:
local remote_ver = redis.call('GET', KEYS[1])
return remote_ver == ARGV[1]
- 返回
1表示本地缓存仍有效;返回0表示已过期,需触发全量刷新 - 类型必须严格一致:如果 Redis 存的是整数,客户端传的
"123456"字符串会导致恒不等 - 未命中本地缓存时,
ARGV[1]应为空或特殊标记(如"nil"),脚本需主动跳过比对,直接返回0










