
浏览器点击“刷新”时会重放上一次http请求;若上次是post提交,就会导致表单重复提交。prg(post-redirect-get)模式通过将post响应改为302/303重定向,使浏览器后续刷新只重发安全的get请求,从而彻底避免重复提交。
浏览器点击“刷新”时会重放上一次http请求;若上次是post提交,就会导致表单重复提交。prg(post-redirect-get)模式通过将post响应改为302/303重定向,使浏览器后续刷新只重发安全的get请求,从而彻底避免重复提交。
当你使用 <form method="POST"> 提交数据后,浏览器会向服务器发送一个 POST 请求。如果后端直接返回 200 OK 并渲染一个 HTML 页面(例如“提交成功”页),此时浏览器地址栏仍停留在原 POST 路径(如 /submit),而当前页面的“历史状态”就绑定在该 POST 请求上。一旦用户按下 F5 或点击刷新按钮,浏览器会严格复现上一次请求——即再次发送完全相同的 POST 请求,造成重复下单、重复扣款等严重后果。
这就是所谓“刷新导致重复提交”的根本机制:浏览器的刷新行为本质是重放最近一次网络请求,而非重新加载当前页面快照。
✅ 正确做法:采用 PRG 模式(Post-Redirect-Get)
PRG 的核心在于打破“POST → 200 HTML”的链路,改为:
POST /order → 303 See Other → GET /success
后端收到 POST 后,不直接返回页面,而是返回一个重定向响应(推荐使用 303 See Other,语义上明确表示“请用 GET 获取新资源”):
# Flask 示例
@app.route('/order', methods=['POST'])
def handle_order():
# 1. 处理业务逻辑(保存订单、扣库存等)
process_order(request.form)
# 2. 返回 303 重定向,跳转到结果页
return redirect(url_for('order_success'), code=303)// Express.js 示例
app.post('/order', (req, res) => {
processOrder(req.body);
// 使用 303 确保浏览器以 GET 访问目标页
res.redirect(303, '/success');
});✅ 这样做的效果是:
- 用户提交后,浏览器地址栏变为 /success;
- 刷新时重放的是 GET /success 请求——无副作用、可安全重复;
- 浏览器历史记录中,/success 对应的是 GET,不再是原始 POST。
⚠️ 注意事项
- 避免使用 302 Found 作为重定向状态码(部分旧浏览器可能误用 POST 重发);优先选择语义更准确的 303 See Other(HTTP/1.1)或 307 Temporary Redirect(需保持方法不变,但此处我们需要变更为 GET,故 303 更合适);
- 重定向目标页(如 /success)应为纯展示页,不包含任何写操作;
- 若需向结果页传递提示信息(如“订单创建成功”),应通过临时 session、Flash 消息或 URL 查询参数(需注意敏感信息不可暴露)实现,切勿依赖 POST 数据重传。
PRG 不仅解决了刷新问题,还天然支持前进/后退导航、书签收藏和搜索引擎友好性,是 Web 表单设计的基石实践。作为后端开发者,应在所有非幂等操作(尤其是创建、修改、删除)中默认应用此模式。


















