当前位置:网站首页>基于注解和拦截器防止表单重复提交
基于注解和拦截器防止表单重复提交
2022-06-29 17:38:00 【llp1110】
基于注解和拦截器防止表单重复提交
1.定义防止重复提交标识注解
/** * 定义一个需要做重复提交校验标识的注解 * 该注解用于修饰控制层方法 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RepeatSubmit {
/** * 两个请求之间的间隔时间(时间的单位在redis获取interval值时进行设置) * @return */
int interval() default 5000;
/** * 重复提交时候的提示文本 * @return */
String message() default "不允许重复提交,请稍后再试";
}
2.重写HttpServletRequestWrapper
默认是不允许通过request.getInputStream获取请求数据的,这里通过自定义的HttpServletRequestWrapper 备份一下流的数据,自定义HttpServletRequestWrapper 调用父类request.getInputStream()读取全部数据出来保存在一个byte数组内,当再次获取流数据的时候,自定义的HttpServletRequestWrapper 就会用byte数组重新生成一个新的流。备份的流数据仍然保留在byte数组中。
public class RepeatableReadRequestWrapper extends HttpServletRequestWrapper {
private final byte[] bytes;
public RepeatableReadRequestWrapper(HttpServletRequest request, HttpServletResponse response) throws IOException {
super(request);
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
bytes = request.getReader().readLine().getBytes();
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream()));
}
@Override
public ServletInputStream getInputStream() throws IOException {
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
return new ServletInputStream() {
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener readListener) {
}
@Override
public int read() throws IOException {
return bais.read();
}
@Override
public int available() throws IOException {
return bytes.length;
}
};
}
}
3.定义过滤器
public class RepeatableRequestFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
if (StringUtils.startsWithIgnoreCase(request.getContentType(), "application/json")) {
RepeatableReadRequestWrapper requestWrapper = new RepeatableReadRequestWrapper(request, (HttpServletResponse) servletResponse);
filterChain.doFilter(requestWrapper,servletResponse);
return;
}
filterChain.doFilter(servletRequest, servletResponse);
}
}
4.封装一个Redis缓存工具类
@Component
public class RedisCache {
@Autowired
RedisTemplate redisTemplate;
public <T> void setCacheObject(final String key, final T value, Integer timeout, final TimeUnit timeUnit) {
redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
}
public <T> T getCacheObject(final String key) {
ValueOperations<String,T> valueOperations = redisTemplate.opsForValue();
return valueOperations.get(key);
}
}
5.定义重复提交校验拦截器
@Component
public class RepeatSubmitInterceptor implements HandlerInterceptor {
public static final String REPEAT_PARAMS = "repeat_params";
public static final String REPEAT_TIME = "repeat_time";
public static final String REPEAT_SUBMIT_KEY = "repeat_submit_key";
public static final String HEADER = "Authorization";
@Autowired
RedisCache redisCache;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//判断是否是映射controller层的方法直接通过
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
//获取Method对象
Method method = handlerMethod.getMethod();
//获取@RepeatSubmit 注解
RepeatSubmit repeatSubmit = method.getAnnotation(RepeatSubmit.class);
//判断是否为空,如果为空则直接放行,如果不为空则进行重复提交校验
if (repeatSubmit != null) {
//校验是否重复提交,如果没有则放行,如果是重复提交则返回状态码:500 message:"重复提交时的文本描述"
if (isRepeatSubmit(request, repeatSubmit)) {
Map<String, Object> map = new HashMap<>();
//设置状态码
map.put("status", 500);
//设置描述信息
map.put("message", repeatSubmit.message());
//设置响应的contentType和编码格式
response.setContentType("application/json;charset=utf-8");
//new ObjectMapper().writeValueAsString(map)将map对象转换成json字符串
response.getWriter().write(new ObjectMapper().writeValueAsString(map));
return false;
}
}
}
return true;
}
/** * 判断是否重复提交,返回 true 表示是重复提交 * * @param request * @param repeatSubmit * @return */
private boolean isRepeatSubmit(HttpServletRequest request, RepeatSubmit repeatSubmit) {
//请求参数字符串
String nowParams = "";
if (request instanceof RepeatableReadRequestWrapper) {
try {
//获取请求体
nowParams = ((RepeatableReadRequestWrapper) request).getReader().readLine();
} catch (IOException e) {
e.printStackTrace();
}
}
//否则说明请求参数是 key-value 格式的
if (StringUtils.isEmpty(nowParams)) {
try {
//获取所有请求参数并转换为json字符串
nowParams = new ObjectMapper().writeValueAsString(request.getParameterMap());
} catch (JsonProcessingException e) {
e.printStackTrace();
}
}
Map<String, Object> nowDataMap = new HashMap<>();
//key: repeat_params value: nowParams
nowDataMap.put(REPEAT_PARAMS, nowParams);
//key: repeat_time value: 时间戳
nowDataMap.put(REPEAT_TIME, System.currentTimeMillis());
// /工程路径/controller层路径
String requestURI = request.getRequestURI();
//request.getHeader(“Authorization”) Bearer xxxx
String header = request.getHeader(HEADER);
//cacheKey = repeat_submit_key + /工程路径/controller层路径 / xxxx
String cacheKey = REPEAT_SUBMIT_KEY + requestURI + header.replace("Bearer ", "");
//从缓存中获取cacheKey的值
Object cacheObject = redisCache.getCacheObject(cacheKey);
if (cacheObject != null) {
Map<String, Object> map = (Map<String, Object>) cacheObject;
//校验参数、间隔时间是否满足重复提交的校验条件,均满足则返回true,否则返回false
if (compareParams(map, nowDataMap) && compareTime(map, nowDataMap, repeatSubmit.interval())) {
return true;
}
}
//从缓存中没有获取到cacheKey的值,设置到redis中
/** * @param1: cacheKey = repeat_submit_key + /工程路径/controller层路径 / xxxx * @parma2: 请求数据Map对象:包含请求参数、请求时间戳 * @parma3: 两次请求的间隔时间,也是redis key的过期时间 * @parma4: 过期时间的单位MILLISECONDS 毫秒 */
redisCache.setCacheObject(cacheKey, nowDataMap, repeatSubmit.interval(), TimeUnit.MILLISECONDS);
return false;
}
/** * 对应本次请求和上一次请求的相隔时间是否超过我们设置的间隔时间interval * 如果不超过则满足重复提交的校验条件返回true * 如果超过则不满足,返回false * @param map * @param nowDataMap * @param interval * @return */
private boolean compareTime(Map<String, Object> map, Map<String, Object> nowDataMap, int interval) {
// 上一次请求的时间
Long time1 = (Long) map.get(REPEAT_TIME);
// 本次请求的时间
Long time2 = (Long) nowDataMap.get(REPEAT_TIME);
//对比时间差,如果小于我们设置的两个请求之间的间隔时间,则说明满足重复提交的条件返回true
if ((time2 - time1) < interval) {
return true;
}
//不满足重复提交的间隔时间则返回false
return false;
}
/** * 比较本次请求和上一次请求的数据是否一样 * @param map * @param nowDataMap * @return */
private boolean compareParams(Map<String, Object> map, Map<String, Object> nowDataMap) {
String nowParams = (String) nowDataMap.get(REPEAT_PARAMS);
String dataParams = (String) map.get(REPEAT_PARAMS);
return nowParams.equals(dataParams);
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
6.定义一个Dto
@Data
public class UserDto {
private String name;
private Integer age;
private String nickName;
private String skill;
}
7.Controller测试类
@RestController
public class HelloController {
@PostMapping("/hello")
@RepeatSubmit(interval = 10000)
public String hello(@RequestBody String json) {
return json;
}
@RepeatSubmit
@Authorization
@PostMapping("/test")
public String authorization(@RequestBody UserDto userDto) {
System.out.println(userDto);
return "succes";
}
}
测试结果

边栏推荐
- 0 basic self-study STM32 (wildfire) - register lit LED
- R language uses GLM function to build Poisson logarithm linear regression model, processes three-dimensional contingency table data to build saturation model, uses exp function and coef function to ob
- Opencv+YOLO-V3实现目标跟踪
- Split palindrome string [dp + DFS combination]
- selenium 组合键操作
- 位图的详细介绍及模拟实现
- OpenFeign使用步骤 轮询策略与权重 log4j使用 openFeign拦截器的配置
- Collaborative development of epidemic home outsourcing project 𞓜 community essay solicitation
- R语言使用MASS包的glm.nb函数建立负二项广义线性模型(negative binomial)、summary函数获取负二项广义线性模型模型汇总统计信息
- C语言练习----指针字符串、链表
猜你喜欢

Digital twin energy system, creating a "perspective" in the low-carbon era

育润多维发力慈善领域,勇抗企业公益大旗

mysql. What is the concept of sock

DevCloud加持下的青软,让教育“智”上云端

Redis principle - sorted set (Zset)
![Split palindrome string [dp + DFS combination]](/img/7b/221b000984977508f849e19802c2c2.png)
Split palindrome string [dp + DFS combination]

selenium上传文件

Openfeign use step polling strategy and weight log4j configuration of openfeign interceptor
![[the sixth operation of modern signal processing]](/img/49/7844a00077e56fd4d73e3ba515f8a6.png)
[the sixth operation of modern signal processing]

Basic operations such as MySQL startup under Windows platform
随机推荐
Set double click to run the jar file
Mysql database literacy, do you really know what a database is
分割回文串[dp + dfs组合]
Repair of JSON parsing errors in a collection
R language ggplot2 visualization: use the patchwork package (directly use the plus sign +) to horizontally combine the two ggplot2 visualization results, and then horizontally combine them with the th
R语言使用epiDisplay包的kap函数(kap.2.raters函数)计算Kappa统计量的值(总一致性、期望一致性)、对两个评分对象的结果进行一致性分析、评分的类别为多个类别
Openfeign use step polling strategy and weight log4j configuration of openfeign interceptor
育润多维发力慈善领域,勇抗企业公益大旗
剖析下零拷贝机制的实现原理,适用场景和代码实现
Split palindrome string [dp + DFS combination]
mysql视图能不能创建索引
Multi mode concurrent implementation of tortoise and rabbit race in go language
L'intercepteur handlerinterceptor personnalisé permet l'authentification de l'utilisateur
R语言ggplot2可视化:使用patchwork包(直接使用加号+)将一个ggplot2可视化结果和一个plot函数可视化结果横向组合起来形成最终结果图
力扣今日题-535. TinyURL 的加密与解密
PCB frame drawing - ad19
位图的详细介绍及模拟实现
基于STM32F103ZET6库函数串口实验
Freedom自由协议质押挖矿系统开发
关于日期相加减问题