必须构建「请求唯一性 + 后端幂等校验」双层防线:先生成不可预测的 request_id 并存 Redis,再通过 SET NX 加锁校验;前端禁用按钮并每次生成新 ID,重试时也需新 ID,避免重复提交。

PHP 8.1 接入 DeepSeek API 时,避免 AI 接口重复提交不能只靠前端按钮禁用或防抖——因为 AI 请求常耗时较长、用户易刷新重试、或后端重试逻辑未收敛,必须构建「请求唯一性 + 后端幂等校验」双层防线。
生成并传递唯一请求 ID(关键第一步)
每次调用 DeepSeek 前,生成不可预测、一次性的请求标识,绑定到本次会话和业务上下文:
- 使用
bin2hex(random_bytes(16))或uniqid('', true)生成 token,避免md5(time())等可预测值 - 将该 ID 存入
$_SESSION(需已启动 session)或 Redis(推荐,支持分布式)中,设置过期时间(如 5 分钟) - 通过请求头(如
X-Request-ID)或请求体字段(如"request_id": "xxx")传给 DeepSeek 调用逻辑,并在日志/数据库中记录该 ID
服务端幂等校验与状态管理
在真正发起 DeepSeek 请求前,先检查该 request_id 是否已被处理或正在处理:
统一LLM网关 - 一个API对接70+AI模型,使用单一API密钥即可调用GPT、Claude、Gemini、Qwen、Deepseek、Grok等主流模型。
- 查 Redis:若
GET deepseek:rid:{id}返回"processing",直接拒绝(返回 409 Conflict 或自定义错误) - 若不存在,用
SET deepseek:rid:{id} processing EX 300 NX尝试加锁;失败则说明并发提交,拒绝 - 成功加锁后才调用 DeepSeek API;无论成功或失败,最终都执行
DEL deepseek:rid:{id}清理锁 - 若请求成功,可额外写入结果缓存(如
deepseek:result:{id}),后续相同 ID 请求可直接返回缓存结果(适用于幂等场景)
前端配合:真实禁用 + 智能重试控制
PHP 后端无法控制用户刷新或新开标签页,前端要减少无效触发:
立即学习“PHP免费学习笔记(深入)”;
- 提交按钮点击后立即
disabled = true,文字改为“AI 正在思考…” - 不依赖简单防抖(用户等 3 秒后仍可能手动刷新),而应结合 request_id:每次新请求生成新 ID,旧 ID 不再被服务端接受
- 对 DeepSeek 的 HTTP 客户端(如 Guzzle)配置合理超时(如 connect: 5s, timeout: 60s),并关闭自动重试(
'http_errors' => false),由业务层统一控制重试逻辑 - 若需自动重试(如网络抖动),仅对 5xx 错误重试,且限制次数(≤2),每次重试生成**新 request_id**,避免把失败请求反复当新请求提交
补充:利用 DeepSeek 自身能力降低风险
虽然 DeepSeek API 本身不提供原生幂等头(如 Idempotency-Key),但可通过业务设计规避重复副作用:
- 不在 DeepSeek 请求中直接执行扣款、下单等操作;AI 返回建议/文本后,再走独立的、带唯一业务单号的下单接口
- 对长文本生成类请求,可加入上下文哈希(如 prompt 的 sha256 截取)作为辅助标识,辅助识别语义重复请求
- 记录完整请求/响应日志(含 request_id、时间戳、IP、user_id),便于事后排查重复来源


















