RedisToken 是 PHP 实现接口幂等性最可控、低侵入的方式,因其在入口层原子性拦截重复请求,避免数据库唯一索引无法覆盖“未入库前重复提交”的缺陷。

直接说结论:用 Redis 存 token 并配合前端主动传递 + 后端校验删除,是 PHP 实现接口幂等性最可控、低侵入的方式;但 token 有效期、存储结构、并发删除时机这三点没处理好,就等于没做。
为什么 RedisToken 比数据库唯一索引更合适防重复提交
数据库唯一索引(比如对 order_no 加 UNIQUE)只能拦住“已入库”的重复,但用户连续点两次提交,第一次还没写库、第二次请求就进来了,照样会走到业务逻辑里——可能扣两次库存、发两遍消息。而 RedisToken 是在入口层拦截:token 生成即锁定,校验通过才放行,天然卡在业务执行前。
实操建议:
- token 必须带过期时间(
SETEX或SET ... EX),推荐 5–10 分钟,太短导致用户操作中断后刷新页面失败,太长占用内存且失效延迟高 - 不要用
INCR或GETSET替代原子性校验删除,PHP 的redis->get() + redis->del()非原子,在并发下会漏判 - token 值建议用加密随机串(如
bin2hex(random_bytes(16))),别用时间戳或自增 ID,避免被预测
generateToken() 和 validateToken() 的关键实现细节
生成 token 是轻量操作,但校验环节必须保证“读-删”原子性,否则高并发下两个请求同时读到同一个 token,都校验成功。
立即学习“PHP免费学习笔记(深入)”;
实操建议:
- 生成时用
$redis->setex('token:' . $userId, 300, $tokenValue),key 加前缀隔离作用域,避免 key 冲突 - 校验必须用 Lua 脚本:
if redis.call("GET", KEYS[1]) == ARGV[1] then return redis.call("DEL", KEYS[1]) else return 0 end调用时传入['token:123']和['abc456...'],由 Redis 保证原子执行 - PHP 中不要对返回值做
== true判断,Lua 返回1或0,要严格用=== 1
前端怎么配、后端怎么接,才能让 token 真正起作用
token 不是后端自己玩一遍就完事,它是一套前后端协作流程:前端要能取、要能传、要能处理失败;后端要能验、要能清、要能给新 token。
实操建议:
- 登录/进入表单页时,前端调用
/api/v1/token接口获取 token,并存入页面隐藏域或 JS 变量,**不要存在 localStorage 里跨会话复用** - 提交时把 token 放在请求 header(如
X-Token)或 body 字段(如form_data.token),避免拼在 URL 里被日志或代理泄露 - 后端校验失败(Lua 返回 0)必须返回明确错误码(如
409 Conflict+{"code": "TOKEN_INVALID"}),前端收到后应禁用按钮、提示“请勿重复提交”,而不是自动重试
真正难的不是写几行 Redis 代码,而是 token 生命周期和业务状态的对齐:比如支付回调里用了 token,但回调可能重试多次,这时 token 是该复用还是拒绝?这类场景得结合业务语义加二次判断,光靠一个 DEL 解决不了。



















