阿粉因为一个重复提交,被面试官疯狂diss(上)

简介: 最近大家应该发现微信公众号信息流改版了吧,再也不是按照时间顺序展示了。这就对阿粉这样的坚持的原创小号主,可以说非常打击,阅读量直线下降,正反馈持续减弱。所以看完文章,哥哥姐姐们给阿粉来个在看吧,让阿粉拥有更加大的动力,写出更好的文章,拒绝白嫖,来点正反馈呗~。如果想在第一时间收到阿粉的文章,不被公号的信息流影响,那么可以给Java极客技术设为一个星标。

平时开发项目的时候,你是否遇到这样的困惑,用户不停的点击按钮向后端提交数据,而你却束手无策!

一、故事

记得以前面试的时候,面试官抛出来这么一个问题,就是后端如何防止重复提交订单

当时的我刚工作一年多,工作经历也不是很丰富,脑子里第一个想到的就是,这个前端就可以解决吧,然后面试官说必须要在后台处理这个问题,之后这场面试也就凉了。

面试结束之后,就开始百度查询资料,除了广告占头条比较吸引人以外,也没找到啥可行的答案,然后请教各路大佬之后,终算是有了一个比较可靠的解决方案。(后文会详细分享)

前些天在群里也看到有个朋友在讨论这个问题,这让我也想起了之前的那段经历,今天小编就和大家一起来讨论一下如何防止重复提交这个问题!

二、问题场景

重复提交,从名字上看,顾名思义,就是多次提交数据,例如支付的时候,假如同一笔订单多次支付,就会造成多次扣款,其后果可想而知!

像这样的案例比比皆是,如果将场景进行归纳,我们会发现主要有两类:

  • 第一类:由于用户误操作或者网络卡顿,可能会造成多次点击表单提交按钮或者刷新提交页面,就会造成重复提交;
  • 第二类:黑客或恶意用户使用postman、jmeter等工具重复恶意提交表单,攻击网站,从而造成重复提交;

这两类严重的时候,甚至会直接造成系统宕机!

三、解决方案

说了这么多,那如何防止重复提交数据呢?

毫无疑问,肯定是从前端、后端同时入手!

3.1、前端解决方法

通过 JavaScript 来屏蔽提交按钮,当用户点击提交按钮后,屏幕弹出遮罩层提示数据加载中....

直到后端返回结果或者前端请求超时时,再将其遮罩层关闭,从而实现防止表单重复提交!

3.2、后端解决方法

虽然前端通过屏蔽操作按钮,防止用户重复提交数据,但是如果黑客直接绕过前端给后端提交数据时,那么后端肯定也必须要做防止重复提交的验证。

方案一:给数据库增加唯一键约束(不推荐)

起初,最开始想到的就是,在控制层给数据做验证,例如用户注册,当用户手机号或者邮箱已经存在,则直接提示提交失败。

@RequestMapping(value = "/register")
public boolean register(@RequestBody UserDto userDto) throws Exception {
    //检查邮件是否已经注册
    QueryWrapper<User> queryWrapper = new QueryWrapper();
    queryWrapper.eq("user_email",userDto.getUserEmail());
    User dbUser = userService.getOne(queryWrapper);
    if(dbUser ! = null){
        throw new CommonExecption("当前邮箱已被注册,请使用新的邮箱注册或者通过密码找回操作!");
    }
    return userService.insert(userDto);
}

如果想更加安全一点,可以在数据库中给关键字段增加唯一键约束,如果用户邮箱已经插入到数据库,会直接抛异常,提示当前邮箱已经注册!

try {
    userService.insert(userDto);
} catch (Exception e) {
    log.error("用户插入失败",e);
    throw new CommonExecption("当前邮箱已被注册,请使用新的邮箱注册!");
}

这种方案在某些场景下是有效果的,例如请求不是非常频繁,可以采用这种方式。

那如果请求非常频繁,而且服务层需要处理的逻辑非常多的时候,这种方案就会遇到很大的瓶颈。

以订单支付为例,当用户支付时,首先会对订单数据做各种基础验证,接着走风控系统,鉴别是否是机器人操作,风控系统通过之后,再对接银行系统查询用户金额是否充足,如果充足就申请扣款,扣款成功之后,更新订单状态,同时将订单的数据推送给中心仓库,等待发货。

当然这个只是一个基础的流程,实际的处理逻辑比这个要复杂的多,此时我们也不能像上面介绍的那样对某个关键字做唯一约束,同时整个处理逻辑所需的时间也相对比较长,假如有几个请求同时过来,其结果可想而知!

方案二:利用缓存ID防止重复提交(推荐)

设想一下,前端在请求后端的时候,先从后端缓存中获取一个唯一的ID,在请求提交数据的时候带上这个唯一的ID,后端检查缓存中是否存在这个ID,如果存在,就进行业务处理,处理完毕之后,从缓存中将这个ID移除掉,如果在处理过程中,前端又再次提交,此时缓存中的ID状态还没有被移除,直接提示:数据处理中,不要重复提交....,具体流程如下!

69.jpg

先编写一个缓存工具类

/**
 * 缓存工具类
 */
public class CacheUtil {
    //hashMap线程安全类
    private static Map<String,Object> cacheMap = new ConcurrentHashMap<>();
    /**
     * 添加缓存
     * @param key
     * @param value
     */
    public static void addCache(String key,Object value){
        cacheMap.put(key, value);
    }
    /**
     * 设置缓存
     * @param key
     * @param value
     */
    public static void setValue(String key,Object value){
        cacheMap.put(key, value);
    }
    /**
     * 获取缓存
     * @param key
     * @return
     */
    public static Object getValue(String key){
        return cacheMap.get(key);
    }
    /**
     * 判断key是存在
     * @param key
     * @return
     */
    public static boolean containKey(String key){
        return cacheMap.containsKey(key);
    }
    /**
     * 移除缓存
     * @param key
     */
    public static void removeCache(String key){
        cacheMap.remove(key);
    }
}
相关文章
|
SQL 缓存 分布式计算
Java面试"整活"自己会一点大数据被疯狂diss
阿粉不知道大家面试的时候,有没有被面试官问到,我看你简历上写了了解一点某某某,你说一下这块的内容吧?当我们听到这段话的时候,是不是感觉非常的熟悉,对,就是这么熟悉,因为有很多面试官看你是 Java 开发的时候,问过了 Java 相关的知识,既然看到了你自己敢在自己的简历上面写了解一点其他的技术的时候,都会很 “热心” 的问候一波,而阿粉的朋友,就被简历上写的了解大数据的相关内容被疯狂 diss 了一波,阿粉顺便也给大家说说面试都问了些啥?
Java面试"整活"自己会一点大数据被疯狂diss
|
2月前
|
存储 Java
【IO面试题 四】、介绍一下Java的序列化与反序列化
Java的序列化与反序列化允许对象通过实现Serializable接口转换成字节序列并存储或传输,之后可以通过ObjectInputStream和ObjectOutputStream的方法将这些字节序列恢复成对象。
|
2月前
|
Java 编译器 C++
【Java基础面试一】、为什么Java代码可以实现一次编写、到处运行?
这篇文章解释了Java能够实现“一次编写,到处运行”的原因,主要归功于Java虚拟机(JVM),它能够在不同平台上将Java源代码编译成的字节码转换成对应平台的机器码,实现跨平台运行。
【Java基础面试一】、为什么Java代码可以实现一次编写、到处运行?
|
2月前
|
Java
【Java基础面试四】、介绍一下Java的数据类型
这篇文章介绍了Java的数据类型,包括8种基本数据类型(整数、浮点、字符、布尔)和3类引用数据类型(数组、类、接口),并提供了基本数据类型所占内存空间和数据范围的详细信息。
|
2月前
|
Java C++
【Java基础面试十七】、Java为什么是单继承,为什么不能多继承?
这篇文章讨论了Java单继承的设计原因,指出Java不支持多继承主要是为了避免方法名冲突等混淆问题,尽管Java类不能直接继承多个父类,但可以通过接口和继承链实现类似多继承的效果。
【Java基础面试十七】、Java为什么是单继承,为什么不能多继承?
|
2月前
|
Java
【Java基础面试三】、说一说你对Java访问权限的了解
这篇文章介绍了Java中的四种访问权限:private、default(无修饰符时的访问权限)、protected和public,以及它们分别在修饰成员变量/方法和类时的不同访问级别和规则。
【Java基础面试三】、说一说你对Java访问权限的了解
|
2月前
|
Java
【Java基础面试二】、个Java文件里可以有多个类吗(不含内部类)?
这篇文章讨论了Java文件中类的定义规则,指出一个Java文件可以包含多个类(不包含内部类),但其中最多只能有一个public类,且如果有public类,它的名称必须与文件名一致。
|
2月前
|
XML 存储 JSON
【IO面试题 六】、 除了Java自带的序列化之外,你还了解哪些序列化工具?
除了Java自带的序列化,常见的序列化工具还包括JSON(如jackson、gson、fastjson)、Protobuf、Thrift和Avro,各具特点,适用于不同的应用场景和性能需求。
|
2月前
|
Java
【Java基础面试三十七】、说一说Java的异常机制
这篇文章介绍了Java异常机制的三个主要方面:异常处理(使用try、catch、finally语句)、抛出异常(使用throw和throws关键字)、以及异常跟踪栈(异常传播和程序终止时的栈信息输出)。
|
2月前
|
Java
【Java基础面试三十八】、请介绍Java的异常接口
这篇文章介绍了Java的异常体系结构,主要讲述了Throwable作为异常的顶层父类,以及其子类Error和Exception的区别和处理方式。

相关实验场景

更多