糯麦 NurMai

400-158-5662

糯麦科技

/

新闻资讯

/

行业洞察

/

面试官询问:“如何有效防止表单重复提交,以确保系统稳健性?这是一个常见的前端开发挑战,我们期望听到你的策略!”

面试官询问:“如何有效防止表单重复提交,以确保系统稳健性?这是一个常见的前端开发挑战,我们期望听到你的策略!”

原创 新闻资讯

于 2024-06-05 08:56:04 发布

15063 浏览

如何有效地防止接口请求重复提交。在开发中,我们常常会遇到这样的问题。在面试场合,也是面试官经常问的问题。但很多新手却经常忽略。因为防重处理需要对应具体业务操作。


下面说的防重操作,相信很多同学都遇到过。如支付功能订单提交业务、表单提交、手机验证码功能。


订单提交为什么需要防重呢?想像一下你在商城购物,你选中商品点击提交订单,如果这时网络延迟没有返回成功提示,你又多点了几次。每点一次都会发送提交订单请求,若是没做防重处理,会出现生成多个订单情况。同样商城系统商品添加功能,用户不小心点了多次表单提交,若防重处理没做,将会添加多个商品。


如何实现防止重复提次请求操作,确保Web应用或API的健壮性和用户体验呢?我们分别从前后端操作上来说一说:


01 前端防重处理


1. 禁用按钮:在用户点击提交按钮后立即禁用它,直到服务器响应完成。

禁用按钮在注册获取手机验证码场景经常用到。点击获取验证码按钮后,按钮立即变为灰色显示禁用状态,读秒结束后恢复正常。

1.jpg

禁用按钮代码示例

2.jpg


2. 显示加载指示:提交过程中显示加载动画或提示,防止用户再次点击。

在登录界面,用户点击登录按钮,请求登录接口后,出现加载动画,防止用户重复点击。

3.jpg


加载loading代码示例

4.jpg


3. 限制提交频率:使用防抖(debounce)和节流(throttle)技术来限制事件触发的频率。

在用户点击提交按钮后,使用防抖或节流的技术延迟发送请求,确保只发送一次请求。防抖和节流是一种常见的前端性能优化技术,可以控制函数的执行频率。


在搜索框输入内容时,可能需要在用户停止输入一段时间后才发送请求,以减少请求的次数。在用户调整浏览器窗口大小时,可能需要在用户停止调整后计算布局或重新渲染页面。在用户连续按键时,例如在输入密码时,可以限制按键事件的处理频率。


5.jpg


02 后端防重处理


1. 唯一ID机制:生成一个唯一的ID,并在客户端提交时一并发送,服务器验证ID后进行处理。


以订单业务为例,实现的逻辑流程如下:


1. 当用户进入订单提交界面的时候,调用后端获取请求唯一ID,并将唯一ID值埋点在页面里面;

2. 当用户点击提交按钮时,后端检查这个唯一ID是否用过,如果没有用过,继续后续逻辑;如果用过,就提示重复提交

3. 最关键的一步操作,就是把这个唯一ID 存入业务表中,同时设置这个字段为唯一索引类型,从数据库层面做防止重复提交

6.jpg

防止重复提交的大体思路如上,实践代码如下!

1. 给数据库表增加唯一键约束

CREATE TABLE tb_order (
  id bigint(20) unsigned NOT NULL,
  order_no varchar(100) NOT NULL,
  ....
  request_id varchar(36) NOT NULL,
  PRIMARY KEY (id),
  UNIQUE KEY uniq_request_id (request_id) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

2. 编写获取请求唯一ID的接口

@RestController
@RequestMapping("api")
public class CommonController {

    /**
     * 获取getRequestId
     * @return
     */
    @RequestMapping("getRequestId")
    public ResResult getRequestId(){
        String uuid = UUID.randomUUID().toString();
        return ResResult.getSuccess(uuid);
    }
}

3. 业务提交的时候,检查唯一ID

@RestController
@RequestMapping("order")
public class OrderController {

    @Autowired
    private OrderService orderService;

    /**
     * 下单
     * @param request
     * @return
     */
    @PostMapping(value = "order/confirm")
    public ResResult confirm(@RequestBody OrderConfirmRequest request){
        //调用订单下单相关逻辑
        if(StringUtils.isEmpty(request.getRequestId())){
            return ResResult.getSysError("请求ID不能为空!");
        }
        if(request.getRequestId().length() != 36){
            return ResResult.getSysError("请求ID格式错误!");
        }
        //检查当前请求唯一ID,是否已经存在,如果存在,再提交就是重复下单
        Order source = orderService.queryByRequestId(request.getRequestId());
        if(Objects.nonNull(source)){
            return ResResult.getSysError("当前订单已经提交成功,请勿重复提交");
        }
        orderService.confirm(request);
        return ResResult.getSuccess();
    }
}

对于下单流量不算高的系统,可以采用这种请求唯一ID+数据表增加唯一索引约束的方式,来防止接口重复提交!虽然简单粗暴,但是十分有效!


2. 分布式锁+全局唯一的ID=Redis+Token: 分布式锁实现解决JVM锁实现单机锁局限问题

具体流程步骤:

1. 客户端先请求服务端,会拿到一个能代表这次请求业务的唯一字段

2. 将该字段以 SETNX 的方式存入 redis 中,并根据业务设置相应的超时时间

3.   如果设置成功,证明这是第一次请求,则执行后续的业务逻辑

4. 如果设置失败,则代表已经执行过当前请求,直接返回

7.jpg

Token实现:生成唯一ID

8.jpg


3. 幂等性设计:

幂等设计,即多次执行同一操作的结果与执行一次相同。这通常通过在接口设计时考虑实现。

封装一个公共的方法,以供所有类使用:

import org.apache.commons.collections4.map.LRUMap;

/**
 * 幂等性判断
 */
public class IdempotentUtils {

    // 根据 LRU(Least Recently Used,最近最少使用)算法淘汰数据的 Map 集合,最大容量 100 个
    private static LRUMap<String, Integer> reqCache = new LRUMap<>(100);

    /**
     * 幂等性判断
     * @return
     */
    public static boolean judge(String id, Object lockClass) {
        synchronized (lockClass) {
            // 重复请求判断
            if (reqCache.containsKey(id)) {
                // 重复请求
                System.out.println("请勿重复提交!!!" + id);
                return false;
            }
            // 非重复请求,存储请求 ID
            reqCache.put(id, 1);
        }
        return true;
    }

调用代码如下:

import com.example.idempote.util.IdempotentUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RequestMapping("/user")
@RestController
public class UserController4 {
    @RequestMapping("/add")
    public String addUser(String id) {
        // 非空判断(忽略)...
        // -------------- 幂等性调用(开始) --------------
        if (!IdempotentUtils.judge(id, this.getClass())) {
            return "执行失败";
        }
        // -------------- 幂等性调用(结束) --------------
        // 业务代码...
        System.out.println("添加用户ID:" + id);
        return "执行成功!";
    }
}

LRUMap在 Apache 提供的 commons-collections 框架中,可以保存指定数量的固定的数据,并且它会按照 LRU 算法,帮你清除最不常用的数据。


LRUMap 的本质是持有头结点的环回双链表结构,当使用元素时,就将该元素放在双链表 header 的前一个位置,在新增元素时,如果容量满了就会移除 header 的后一个元素。


继续完善代码,可以通过自定义注解,将业务代码写到注解中,需要调用的方法只需要写一行注解就可以防止重复提交了。


03 总结

防止重复提交是确保Web应用或API的健壮性和用户体验的重要措施。为了防止绕过前端限制通过工具重复请求接口,在防重处理时需要前后端配合。


除了我们介绍的几种方式外还有其它方式和封装好的工具,如:react中可以用swr、ahook,vue中用VueRequest。每种方法都有其适用场景,通常需要根据具体的业务需求和系统架构来选择最合适的策略。在设计系统时,应该综合考虑多种方法,以实现最佳的效果。

web前端

网站开发

网站建设

阅读排行

  • 1. 几行代码就能实现Html大转盘抽奖

    大转盘抽奖是网络互动营销的一种常见形式,其通过简单易懂的界面设计,让用户在游戏中体验到乐趣,同时也能增加商家与用户之间的互动。本文将详细介绍如何使用HTML,CSS和JavaScript来实现大转盘抽奖的功能。

    查看详情
  • 2. 浙江省同区域公司地址变更详细流程

    提前准备好所有需要的资料,包含:房屋租赁合同、房产证、营业执照正副本、代理人身份证正反面、承诺书(由于我们公司其中一区域已有注册另外一公司,所以必须需要承诺书)

    查看详情
  • 3. 微信支付商户申请接入流程

    微信支付,是微信向有出售物品/提供服务需求的商家提供推广销售、支付收款、经营分析的整套解决方案,包括多种支付方式,如JSAPI支付、小程序支付、APP支付H5支付等支付方式接入。

    查看详情
  • 4. 阿里云域名ICP网络备案流程

    根据《互联网信息服务管理办法》以及《非经营性互联网信息服务备案管理办法》,国家对非经营性互联网信息服务实行备案制度,对经营性互联网信息服务实行许可制度。

    查看详情
  • 5. 微信小程序申请注册流程

    微信小程序注册流程与微信公众号较为相似,同时微信小程序支持通过已认证的微信公众号进行注册申请,无需进行单独认证即可使用,同一个已认证微信公众号可同时绑定注册多个小程序。

    查看详情