接口幂等性指的是同一个接口,多次发出的同一个请求,必须保证操作只执行一次。比如在极短时间内同一个订单用户点击了2次提交,这时第二次点击应视为无效点击。
此种方式比较常见,大致流程如下:
此种方式改动量小但和业务密切相关,以新增用户功能为例:
此种方式原理类似于token机制, 但比token更加简单,无需修改客户端逻辑,原理如下:
例子:
@WebFilter(filterName = "duplicateFilter", urlPatterns = "/*")
public class DuplicateRequestFilter implements Filter {
//缓存最近2秒内请求的哈希值
private final TtlMap<String, Long> CONTENT_HASH = new TtlMap<>(100_000, 2_000);
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
//TODO 校验
if (!needCheck(request) || !checkDuplicateRq(request)){
filterChain.doFilter(servletRequest, servletResponse);
return;
}
//TODO 输出错误信息
HttpServletResponse response = (HttpServletResponse) servletResponse;
writeErrResult(response);
}
/** 请求是否需要校验 */
private boolean needCheck(HttpServletRequest request){
String method = request.getMethod();
return !Arrays.asList("GET", "HEAD", "OPTIONS", "TRACE", "CONNECT").contains(method);
}
/**
* @description:通过请求体长度,ip,uri等进行校验
* @date 17:07 2022/8/31
* @param request
* @return true:重复/false:不重复
**/
private boolean checkDuplicateRq(HttpServletRequest request) {
//获取请求uri,ip,客户端类型
String uri = request.getRequestURI();
String ip = request.getRemoteAddr();
String userAgent = request.getHeader("User-Agent");
//获取请求体长度
String length = request.getHeader("Content-Length");
String content = new StringBuilder(ip)
.append(uri)
.append(userAgent)
.append(length).toString();
//取sha256哈希
String contentHash = HexUtil.getHexString(content, HexAlgorithmEnum.SHA256);
boolean hit = CONTENT_HASH.get(contentHash) != null;
//刷新有限期
CONTENT_HASH.put(contentHash, System.currentTimeMillis());
//返回是否命中
return hit;
}
private void writeErrResult(HttpServletResponse response ) throws IOException {
BaseResultVo resultVo = new BaseResultVo(409, false, "请求重复");
response.setContentType("application/json;charset=UTF-8");
response.setCharacterEncoding("UTF-8");
PrintWriter writer = response.getWriter();
writer.write( JSON.toJSONString(resultVo) );
writer.flush();
}
}