SpringBoot+Redis实现后端接口防重复提交校验的示例

网友投稿 648 2023-01-05

SpringBoot+Redis实现后端接口防重复提交校验的示例

SpringBoot+Redis实现后端接口防重复提交校验的示例

目录1 Maven依赖2 RepeatedlyRequestWrapper3 RepeatableFilter4 RepeatSubmit5 RepeatSubmitInterceptor6 RepeatSubmitConfig7 RepeatSubmitController

1 Maven依赖

org.springframework.boot

spring-boot-starter-data-redis

com.alibaba

fastjson

1.2.76

cn.hutool

hutool-all

5.5.1

2 RepeatedlyRequestWrapper

构建可重复读取inputStream的request。

package com.servlet;

import javax.servlet.*;

import javax.servlet.http.*;

import java.io.*;

import java.nio.charset.Charset;

/**

* 构建可重复读取inputStream的request

*/

public class RepeatedlyRequestWrapper extends HttpServletRequestWrapper {

private final String body;

public RepeatedlyRequestWrapper(HttpServletRequest request, ServletResponse response) throws IOException {

super(request);

request.setCharacterEncoding("UTF-8");

response.setCharacterEncoding("UTF-8");

body = getBodyString(request);

}

@Override

public BufferedReader getReader() throws IOException {

return new BufferedReader(new InputStreamReader(getInputStream()));

}

public String getBody() {

return body;

}

@Override

public ServletInputStream getInputStream() throws IOException {

final ByteArrayInputStream bais = new ByteArrayInputStream(body.getBytes("UTF-8"));

return new ServletInputStream() {

@Override

public int read() throws IOException {

return bais.read();

}

@Override

public int available() throws IOException {

return body.length();

}

@Override

public boolean isFinished() {

return false;

}

@Override

public boolean isReady() {

return false;

}

@Override

public void setReadListener(ReadListener readListener) {

}

};

}

/**

* 获取Request请求body内容

*

* @param request

* @return

*/

private String getBodyString(ServletRequest request) {

StringBuilder sb = new StringBuilder();

BufferedReader reader = null;

try (InputStream inputStream = request.getInputStream()) {

reader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8")));

String line = "";

while ((line = reader.readLine()) != null) {

sb.append(line);

}

} catch (IOException e) {

e.printStackTrace();

} finally {

if (reader != null) {

try {

reader.close();

} catch (IOException e) {

http:// e.printStackTrace();

}

}

}

return sb.toString();

}

}

3 RepeatableFilter

将原生的request变为可重复读取inputStream的request。

package com.filter;

import com.servlet.RepeatedlyRequestWrapper;

import org.springframework.http.MediaType;

import org.springframework.util.StringUtils;

import javax.servlet.*;

import javax.servlet.http.HttpServletRequest;

import java.io.IOException;

/**

* 过滤器(重写request)

*/

public class RepeatableFilter implements Filter {

@Override

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {

ServletRequest requestWrapper = null;

//将原生的request变为可重复读取inputStream的request

if (request instanceof HttpServletRequest

&& StringUtils.startsWithIgnoreCase(request.getContentType(), MediaType.APPLICATION_JSON_VALUE)) {

requestWrapper = new RepeatedlyRequestWrapper((HttpServletRequest) request, response);

}

if (null == requestWrapper) {

chain.doFilter(request, response);

} else {

chain.doFilter(requestWrapper, response);

}

}

}

4 RepeatSubmit

自定义注解(防止表单重复提交)。

package com.annotation;

import java.lang.annotation.*;

/**

* 自定义注解防止表单重复提交

*/

@Inherited

@Target(ElementType.METHOD)

@Retention(RetentionPolicy.RUNTIME)

@Documented

public @interface RepeatSubmit {

}

5 RepeatSubmitInterceptor

防止重复提交-。

package com.interceptor;

import cn.hutool.core.util.StrUtil;

import com.alibaba.fastjson.JSONObject;

import com.annotation.RepeatSubmit;

import com.service.*;

import com.servlet.RepeatedlyRequestWrapper;

import org.springframework.beans.factory.annotation.*;

import org.springframework.stereotype.Component;

import org.springframework.web.method.HandlerMethod;

import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.*;

import java.io.IOException;

import java.lang.reflect.Method;

import java.util.*;

import java.util.concurrent.TimeUnit;

/**

* 防止重复提交-

*/

@Component

public class RepeatSubmitInterceptor implements HandlerInterceptor {

public final String REPEAT_PARAMS = "repeatParams";

public final String REPEAT_TIME = "repeatTime";

/**

* 防重提交 redis key

*/

public final String REPEAT_SUBMIT_KEY = "repeat_submit:";

// 令牌自定义标识

@Value("${token.header}")

private String header;

@Autowired

private RedisService redisService;

/**

* 间隔时间,单位:秒

*

* 两次相同参数的请求,如果间隔时间大于该参数,系统不会认定为重复提交的数据

*/

@Value("${repeatSubmit.intervalTime}")

private int intervalTime;

@Override

public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

if (handler instanceof HandlerMethod) {

HandlerMethod handlerMethod = (HandlerMethod) handler;

Method method = handlerMethod.getMethod();

RepeatSubmit annotation = method.getAnnotation(RepeatSubmit.class);

if (annotation != null) {

if (this.isRepeatSubmit(request)) {

//返回重复提交提示

Map resultMap = new HashMap<>();

resultMap.put("code", "500");

resultMap.put("msg", request.getRequestURI() + "不允许重复提交,请稍后再试");

try {

response.setStatus(200);

response.setContentType("application/json");

response.setCharacterEncoding("utf-8");

response.getWriter().print(JSONObject.toJSONString(resultMap));

} catch (IOException e) {

e.printStackTrace();

}

return false;

}

}

return true;

} else {

return preHandle(request, response, handler);

}

}

/**

* 验证是否重复提交由子类实现具体的防重复提交的规则

*

* @param request

* @return

* @throws Exception

*/

public boolean isRepeatSubmit(HttpServletRequest request) {

String nowParams = "";

if (request instanceof RepeatedlyRequestWrapper) {

RepeatedlyRequestWrapper repeatedlyRequest = (RepeatedlyRequestWrapper) request;

nowParams = repeatedlyRequest.getBody();

}

// body参数为空,获取Parameter的数据

if (StrUtil.isBlank(nowParams)) {

nowParams = JSONObject.toJSONString(request.getParameterMap());

}

Map nowDataMap = new HashMap();

nowDataMap.put(REPEAT_PARAMS, nowParams);

nowDataMap.put(REPEAT_TIME, System.currentTimeMhttp://illis());

// 请求地址(作为存放cache的key值)

String url = request.getRequestURI();

// 唯一值(没有消息头则使用请求地址)

String submitKey = request.getHeader(header);

if (StrUtil.isBlank(submitKey)) {

submitKey = url;

}

// 唯一标识(指定key + 消息头)

String cacheRepeatKey = REPEAT_SUBMIT_KEY + submitKey;

Object sessionObj = redisService.getCacheObject(cacheRepeatKey);

if (sessionObj != null) {

Map sessionMap = (Map) sessionObj;

if (sessionMap.containsKey(url)) {

Map preDataMap = (Map) sessionMap.get(url);

if (compareParams(nowDataMap, preDataMap) && compareTime(nowDataMap, preDataMap)) {

return true;

}

}

}

Map cacheMap = new HashMap();

cacheMap.put(url, nowDataMap);

redisService.setCacheObject(cacheRepeatKey, cacheMap, intervalTime, TimeUnit.SECONDS);

return false;

}

/**

* 判断参数是否相同

*/

private boolean compareParams(Map nowMap, Map preMap) {

String nowParams = (String) nowMap.get(REPEAT_PARAMS);

String preParams = (String) preMap.get(REPEAT_PARAMS);

return nowParams.equals(preParams);

}

/**

* 判断两次间隔时间

*/

private boolean compareTime(Map nowMap, Map preMap) {

long time1 = (Long) nowMap.get(REPEAT_TIME);

long time2 = (Long) preMap.get(REPEAT_TIME);

if ((time1 - time2) < (this.intervalTime * 1000)) {

return true;

}

return false;

}

}

6 RepeatSubmitConfig

防重复提交配置

package com.config;

import com.filter.RepeatableFilter;

import com.interceptor.RepeatSubmitInterceptor;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.boot.web.servlet.FilterRegistrationBean;

import org.springframework.context.annotation.*;

import org.springframework.web.servlet.config.annotation.*;

/**

* 防重复提交配置

*/

@Configuration

public class RepeatSubmitConfig implements WebMvcConfigurer {

@Autowired

private RepeatSubmitInterceptor repeatSubmitInterceptor;

/**

* 添加防重复提交拦截

*/

@Override

public void addInterceptors(InterceptorRegistry registry)

{

registry.addInterceptor(repeatSubmitInterceptor).addPathPatterns("/**");

}

/**

* 生成防重复提交过滤器(重写request)

* @return

*/

@SuppressWarnings({ "rawtypes", "unchecked" })

@Bean

public FilterRegistrationBean createRepeatableFilter()

{

FilterRegistrationBean registration = new FilterRegistrationBean();

registration.setFilter(new RepeatableFilter());

registration.addUrlPatterns("/*");

registration.setName("repeatableFilter");

registration.setOrder(FilterRegistrationBean.LOWEST_PRECEDENCE);

return registration;

}

}

7 RepeatSubmitController

调试代码。

package com.controller;

import com.annotation.RepeatSubmit;

import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController

@RequestMapping("/repeatSubmit")

public class RepeatSubmitController {

/**

* 保存Param

* @param name

* @return

*/

@RepeatSubmit

@PostMapping("/saveParam/{name}")

public String saveParam(@PathVariable("name")String name){

return "保存Param成功";

}

/**

* 保存Param

* @param name

* @return

*/

@RepeatSubmit

@PostMapping("/saveBody")

public String saveBody(@RequestBody List name){

return "保存Body成功";

}

}

8 调试结果

param传参:

body传参:

注:

(1)RedisService源码请查看以下博客。

Spring Boot 配置Redis(缓存数据库)实现保存、获取、删除数据

(2)只有加上@RepeatSubmit的接口才会进行防重复提交校验。

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:前端移动应用开发(移动应用开发和web前端开发)
下一篇:小程序生态运营大讲堂心得(小程序平台运营方案)
相关文章

 发表评论

暂时没有评论,来抢沙发吧~