文章目录
一,问题描述
我们加购的提交的地址是:
http://cart.gulimall.com/addCartItem?skuId=1&num=2
这个请求发出后,后台执行加购逻辑,如果直接返回success页面。
@GetMapping(value = "/addCartItem")
public String addToCart() {
return "success";
}
如果addCartItem
接口直接返回success界面,那么浏览器地址栏的地址就是:
http://cart.gulimall.com/addCartItem?skuId=1&num=2
用户刷新浏览器,就会提交一个加购请求。
会导致一个问题,如果用户刷新这个页面,就会重复提交加购请求,这个商品会被重复加购,导致不好的客户体验。
二,解决方案
所以不能把这个地址暴露在浏览器的地址栏。
参考京东的做法,我们可以在用户点击加购请求后不直接返回success页面,而是让浏览器重定向到一个新的地址,这个地址会返回sucess页面,但是这个地址不会有其他的业务逻辑。
具体的逻辑是这样:
- 用户点击加购,浏览器发出加购请求
- 后台接收处理加购请求,让浏览器重定向到一个新的地址
- 这个地址对应的接口会返回success页面,但不执行其他逻辑,用户刷新不会导致加购的重复提交
代码如下。
@GetMapping(value = "/addCartItem")
public String addCartItem(@RequestParam("skuId") Long skuId,
@RequestParam("num") Integer num,
RedirectAttributes attributes) throws ExecutionException, InterruptedException {
cartService.addToCart(skuId,num);
attributes.addAttribute("skuId",skuId);
return "redirect:http://cart.gulimall.com/addToCartSuccessPage.html";
}
/**
* 跳转到添加购物车成功页面
* @param skuId
* @param model
* @return
*/
@GetMapping(value = "/addToCartSuccessPage.html")
public String addToCartSuccessPage(@RequestParam("skuId") Long skuId,
Model model) {
//重定向到成功页面。再次查询购物车数据即可
CartItemVo cartItemVo = cartService.getCartItem( );
model.addAttribute("cartItem",cartItemVo);
return "success";
}
分析说明
如上述代码,如果addCartItem
接口直接返回success
界面,那么浏览器地址栏的地址就是:
http://cart.gulimall.com/addCartItem?skuId=1&num=2
用户刷新浏览器,就会提交一个加购请求。
如果addCartItem
接口重定向到接口addToCartSuccessPage.html
,那么浏览器显示的地址就是:
http://cart.gulimall.com/addToCartSuccessPage.html?skuId=1
用户刷新页面,并不会导致加购的重复提交。
RedirectAttributes 的作用
RedirectAttributes 是一个用于向重定向的目标 URL 传递数据的工具。它允许在重定向时将属性添加到模型中,这些属性可以在重定向的目标控制器中通过 @ModelAttribute
或 @RequestParam
注解接收。
在这种情况下,RedirectAttributes
用来传递 skuId
,以便在下一个页面中查询并展示购物车项的具体信息。
使用 RedirectAttributes
避免了表单重复提交的问题,因为浏览器会对相同的 GET 请求进行缓存,而重定向请求会生成一个新的请求,因此不会触发重复提交。
这段代码展示了如何处理购物车商品添加的请求,并防止重复提交。这里采用了一种结合重定向和 RedirectAttributes
的方式来解决这个问题。下面是详细的解释:
addCartItem
方法
-
处理请求:
- 接收前端传来的参数
skuId
和num
,分别表示要添加到购物车的商品 ID 和数量。 - 调用
cartService.addToCart(skuId, num)
方法来处理添加商品到购物车的逻辑。
- 接收前端传来的参数
-
使用
RedirectAttributes
:- 通过
RedirectAttributes
添加一个名为skuId
的属性,其值为传入的skuId
。 - 发送一个重定向请求到
/addToCartSuccessPage.html
页面。
- 通过
addToCartSuccessPage
方法
-
处理重定向请求:
- 接收前端通过重定向传来的
skuId
参数。 - 通过调用
cartService.getCartItem(skuId)
方法获取相应的购物车项信息。 - 将
CartItemVo
对象添加到模型中,以便在成功页面展示商品信息。
- 接收前端通过重定向传来的
-
返回视图:
- 返回视图名称
"success"
,这通常对应于一个 JSP 或 Thymeleaf 模板文件,用于展示添加成功的页面。
- 返回视图名称
RedirectAttributes
的作用
RedirectAttributes
是一个用于向重定向的目标 URL 传递数据的工具。它允许你在重定向时将属性添加到模型中,这些属性可以在重定向的目标控制器中通过@ModelAttribute
或@RequestParam
注解接收。- 在这种情况下,
RedirectAttributes
用来传递skuId
,以便在下一个页面中查询并展示购物车项的具体信息。 - 使用
RedirectAttributes
避免了表单重复提交的问题,因为浏览器会对相同的 GET 请求进行缓存,而重定向请求会生成一个新的请求,因此不会触发重复提交。
三,其他解决重复提交的方案
除了上述的重定向方案外,还有几种常见的防止重复提交的方法:
-
Token 方案:
- 在客户端生成一个唯一的 Token,并将其存储在 Cookie 或隐藏字段中。
- 在服务器端为每个用户生成并存储一个 Token。
- 提交表单时,将客户端的 Token 一并发送至服务器。
- 服务器端验证 Token 的有效性和唯一性,如果有效则处理请求,并删除或失效该 Token。
- 如果 Token 无效或已使用,则拒绝请求。
-
Post/Redirect/Get 方案:
- 处理完 POST 请求后,发送一个重定向响应(通常使用 302 状态码)。
- 浏览器会发起一个新的 GET 请求来加载重定向的目标 URL。
- 这样可以避免表单重复提交,因为浏览器会发送一个新的请求,而不是重发原来的 POST 请求。
-
前端禁用按钮:
- 在前端表单提交后立即禁用提交按钮,直到收到服务器响应后再启用。
- 这种方法简单易行,但依赖于前端实现,可能不适用于所有情况。
-
使用时间戳:
- 在表单中包含一个时间戳字段。
- 服务器端验证时间戳,如果时间差超过某个阈值,则认为是重复提交。
-
使用状态码:
- 服务器返回特定的状态码(如 409 Conflict)来告知客户端请求已经被处理过。
-
基于 Session 的解决方案:
- 在用户提交表单时,在 Session 中设置一个标志位,表示请求已被处理。
- 下一次提交时检查标志位,如果已被处理则拒绝。
课程采用了第二种防止重复提交的方法。
针对这个场景,还可以采用第一种方法:
- 点击“添加到购物车”按钮时,在响应函数中生成一个唯一的id,作为参数传递给后台
- 后台加购成功后,把这个id保存起来
- 下次如果有相同的id,认为是重复提交,直接返回success页面,不做加购的逻辑处理。
还可以使用第五种方法
:
- 服务端处理完成后,返回success页面时,在页面上加一个标识,标识已经加购过,客户刷新页面,携带这个标识,服务端识别这个标识后,不执行加购逻辑,这种方式比第一种更好,无需在服务端存储,客户端存储即可,客户端页面关闭后,cookie就自动删除了。