博客主页: 南来_北往
系列专栏:Spring Boot实战
前言
作为一名老码农,在开发后端Java业务系统,包括各种管理后台和小程序等。在这些项目中,我设计过单/多租户体系系统,对接过许多开放平台,也搞过消息中心这类较为复杂的应用,但幸运的是,我至今还没有遇到过线上系统由于代码崩溃导致资损的情况。这其中的原因有三点:一是业务系统本身并不复杂;二是我一直遵循某大厂代码规约,在开发过程中尽可能按规约编写代码;三是经过多年的开发经验积累,我成为了一名熟练工,掌握了一些实用的技巧。
啥是防抖
所谓防抖,一是防用户手抖,二是防网络抖动。在Web系统中,表单提交是一个非常常见的功能,如果不加控制,容易因为用户的误操作或网络延迟导致同一请求被发送多次,进而生成重复的数据记录。要针对用户的误操作,前端通常会实现按钮的loading状态,阻止用户进行多次点击。而对于网络波动造成的请求重发问题,仅靠前端是不行的。为此,后端也应实施相应的防抖逻辑,确保在网络波动的情况下不会接收并处理同一请求多次。
一个理想的防抖组件或机制,我觉得应该具备以下特点:
-
逻辑正确,也就是不能误判;
-
响应迅速,不能太慢;
-
易于集成,逻辑与业务解耦;
-
良好的用户反馈机制,比如提示“您点击的太快了”
思路解析
前面讲了那么多,我们已经知道接口的防抖是很有必要的了,但是在开发之前,我们需要捋清楚几个问题。
哪一类接口需要防抖?
接口防抖也不是每个接口都需要加,一般需要加防抖的接口有这几类:
-
用户输入类接口:比如搜索框输入、表单输入等,用户输入往往会频繁触发接口请求,但是每次触发并不一定需要立即发送请求,可以等待用户完成输入一段时间后再发送请求。
-
按钮点击类接口:比如提交表单、保存设置等,用户可能会频繁点击按钮,但是每次点击并不一定需要立即发送请求,可以等待用户停止点击一段时间后再发送请求。
-
滚动加载类接口:比如下拉刷新、上拉加载更多等,用户可能在滚动过程中频繁触发接口请求,但是每次触发并不一定需要立即发送请求,可以等待用户停止滚动一段时间后再发送请求。
如何确定接口是重复的?
防抖也即防重复提交,那么如何确定两次接口就是重复的呢?首先,我们需要给这两次接口的调用加一个时间间隔,大于这个时间间隔的一定不是重复提交;其次,两次请求提交的参数比对,不一定要全部参数,选择标识性强的参数即可;最后,如果想做的更好一点,还可以加一个请求地址的对比。
如何做接口防抖?
前端防抖
在前端使用JavaScript进行防抖处理。通过设置一个定时器,在一定时间内只允许用户提交一次请求。如果用户在这段时间内再次触发提交事件,清除之前的定时器并重新设置一个新的定时器。
function debounce(func, wait) {
let timeout;
return function() {
const context = this;
const args = arguments;
clearTimeout(timeout);
timeout = setTimeout(function() {
func.apply(context, args);
}, wait);
};
}
// 使用方法
const handleSubmit = debounce(function() {
// 提交表单的逻辑
}, 500);
document.getElementById('submit-button').addEventListener('click', handleSubmit);
后端防抖
在后端使用SpringBoot的拦截器或者过滤器来实现防抖功能。通过记录用户的请求时间戳,判断两次请求之间的时间间隔是否小于设定的阈值,如果是则拒绝处理请求。
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
public class DebounceInterceptor implements HandlerInterceptor {
private static final ConcurrentHashMap<String, Long> requestMap = new ConcurrentHashMap<>();
private static final long DEBOUNCE_TIME = 500; // 防抖时间,单位毫秒
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String ip = request.getRemoteAddr();
Long lastTime = requestMap.getOrDefault(ip, 0L);
long currentTime = System.currentTimeMillis();
if (currentTime - lastTime < DEBOUNCE_TIME) {
response.setStatus(HttpServletResponse.SC_TOO_MANY_REQUESTS);
return false;
} else {
requestMap.put(ip, currentTime);
return true;
}
}
}
数据库防抖
在数据库层面实现防抖功能。可以在表中添加一个字段来记录最后一次请求的时间戳,每次请求时检查当前时间与上次请求时间的差值,如果小于设定的阈值,则拒绝处理请求。这种方法需要在业务逻辑中进行处理,而不是直接在拦截器或过滤器中实现。
分布式锁
使用分布式锁来防止并发请求。例如,可以使用Redis作为分布式锁的存储,确保同一时间只有一个请求能够被处理。这种方法适用于多实例、分布式部署的场景。
以上就是一些实现SpringBoot接口防抖的方法,可以根据实际需求选择合适的方案。