SpringBoot基于redis自定义注解实现后端接口防重复提交校验
目录
- 一、添加依赖
- 二、代码实现
- 三、测试
一、添加依赖
org.springframework.boot spring-boot-starter-redis1.4.4.RELEASE redis.clients jedis3.6.3 com.alibaba fastjson1.2.75
二、代码实现 1.构建可重复读取inputStream的request
package com.hl.springbootrepetitionsubmit.servlet; import javax.servlet.*; import javax.servlet.http.*; import java.io.*; import java.nio.charset.StandardCharsets; /** * 构建可重复读取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); } @Overridepublic BufferedReader getReader() throws IOException {return new BufferedReader(new InputStreamReader(getInputStream())); } public String getBody() {return body; } @Overridepublic ServletInputStream getInputStream() throws IOException {final ByteArrayInputStream bais = new ByteArrayInputStream(body.getBytes(StandardCharsets.UTF_8)); return new ServletInputStream() {@Overridepublic int read() throws IOException {return bais.read(); } @Overridepublic int available() throws IOException {return body.length(); } @Overridepublic boolean isFinished() {return false; } @Overridepublic boolean isReady() {return false; } @Overridepublic 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, StandardCharsets.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) {e.printStackTrace(); }}}return sb.toString(); }}
2.重写request
package com.hl.springbootrepetitionsubmit.filter; import com.hl.springbootrepetitionsubmit.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 {@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {ServletRequest requestWrapper = null; //将原生的request变为可重复读取inputStream的requestif (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); }}}
3.自定义注解
/** * 自定义注解防止表单重复提交 */@Inherited@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface RepeatSubmit {}
4.创建防止重复提交拦截器
package com.hl.springbootrepetitionsubmit.interceptor; import cn.hutool.core.util.StrUtil; import com.alibaba.fastjson.JSONObject; import com.hl.springbootrepetitionsubmit.annotation.RepeatSubmit; import com.hl.springbootrepetitionsubmit.config.RedisUtil; import com.hl.springbootrepetitionsubmit.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; /** * 防止重复提交拦截器 */@Componentpublic 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; @Autowiredprivate RedisUtil redisUtil; /*** 间隔时间,单位:秒* * 两次相同参数的请求,如果间隔时间大于该参数,系统不会认定为重复提交的数据*/@Value("${repeatSubmit.intervalTime}")private int intervalTime; @Overridepublic 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); }} /*** 验证是否重复提交由子类实现具体的防重复提交的规则*/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.currentTimeMillis()); // 请求地址(作为存放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 = redisUtil.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); redisUtil.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 * 1000L)) {return true; }return false; } }
5.redis配置
package com.hl.springbootrepetitionsubmit.config; import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.CachingConfigurerSupport; import org.springframework.cache.interceptor.KeyGenerator; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.cache.RedisCacheConfiguration; import org.springframework.data.redis.cache.RedisCacheManager; import org.springframework.data.redis.connection.RedisStandaloneConfiguration; import org.springframework.data.redis.connection.jedis.JedisClientConfiguration; import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.RedisSerializationContext; import org.springframework.data.redis.serializer.StringRedisSerializer; import redis.clients.jedis.JedisPool; import redis.clients.jedis.JedisPoolConfig; import java.time.Duration; @Configurationpublic class RedisConfig extends CachingConfigurerSupport {//配置redis的过期时间private static final Duration TIME_TO_LIVE = Duration.ZERO; @Bean(name = "jedisPoolConfig")public JedisPoolConfig jedisPoolConfig() {JedisPoolConfig jedisPoolConfig = new JedisPoolConfig(); //控制一个pool可分配多少个jedis实例jedisPoolConfig.setMaxTotal(500); //最大空闲数jedisPoolConfig.setMaxIdle(200); //每次释放连接的最大数目,默认是3jedisPoolConfig.setNumTestsPerEvictionRun(1024); //逐出扫描的时间间隔(毫秒) 如果为负数,则不运行逐出线程, 默认-1jedisPoolConfig.setTimeBetweenEvictionRunsMillis(30000); //连接的最小空闲时间 默认1800000毫秒(30分钟)jedisPoolConfig.setMinEvictableIdleTimeMillis(-1); jedisPoolConfig.setSoftMinEvictableIdleTimeMillis(10000); //最大建立连接等待时间。如果超过此时间将接到异常。设为-1表示无限制。jedisPoolConfig.setMaxWaitMillis(1500); jedisPoolConfig.setTestOnBorrow(true); jedisPoolConfig.setTestWhileIdle(true); jedisPoolConfig.setTestOnReturn(false); jedisPoolConfig.setJmxEnabled(true); jedisPoolConfig.setBlockWhenExhausted(false); return jedisPoolConfig; }@Bean("connectionFactory")public JedisConnectionFactory connectionFactory() {RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration(); redisStandaloneConfiguration.setHostName("127.0.0.1"); redisStandaloneConfiguration.setDatabase(0); //redisStandaloneConfiguration.setPassword(RedisPassword.of("123456")); redisStandaloneConfiguration.setPort(6379); //获得默认的连接池构造器JedisClientConfiguration.JedisPoolingClientConfigurationBuilder jpcb =(JedisClientConfiguration.JedisPoolingClientConfigurationBuilder) JedisClientConfiguration.builder(); //指定jedisPoolConifig来修改默认的连接池构造器(真麻烦,滥用设计模式!)jpcb.poolConfig(jedisPoolConfig()); //通过构造器来构造jedis客户端配置JedisClientConfiguration jedisClientConfiguration = jpcb.build(); //单机配置 + 客户端配置 = jedis连接工厂return new JedisConnectionFactory(redisStandaloneConfiguration, jedisClientConfiguration); }@Bean("jedisPool")public JedisPool jedisPool(){return new JedisPool(jedisPoolConfig(),"127.0.0.1",6379); }@Beanpublic RedisTemplate
6.redis工具类
package com.hl.springbootrepetitionsubmit.config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.BoundSetOperations; import org.springframework.data.redis.core.HashOperations; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.ValueOperations; import org.springframework.stereotype.Component; import java.util.*; import java.util.concurrent.TimeUnit; @Componentpublic class RedisUtil {@Autowiredprivate RedisTemplate redisTemplate; /*** 缓存基本的对象,Integer、String、实体类等** @param key缓存的键值* @param value 缓存的值*/publicvoid setCacheObject(final String key, final T value) {redisTemplate.opsForValue().set(key, value); } /*** 缓存基本的对象,Integer、String、实体类等** @param key缓存的键值* @param value缓存的值* @param timeout时间* @param timeUnit 时间颗粒度*/public void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit) {redisTemplate.opsForValue().set(key, value, timeout, timeUnit); } /*** 设置有效时间** @param keyRedis键* @param timeout 超时时间* @return true=设置成功;false=设置失败*/public boolean expire(final String key, final long timeout) {return expire(key, timeout, TimeUnit.SECONDS); } /*** 设置有效时间** @param keyRedis键* @param timeout 超时时间* @param unit时间单位* @return true=设置成功;false=设置失败*/public boolean expire(final String key, final long timeout, final TimeUnit unit) {return redisTemplate.expire(key, timeout, unit); } /*** 获得缓存的基本对象。** @param key 缓存键值* @return 缓存键值对应的数据*/public T getCacheObject(final String key) {ValueOperations operation = redisTemplate.opsForValue(); return operation.get(key); } /*** 删除单个对象** @param key*/public boolean deleteObject(final String key) {return redisTemplate.delete(key); } /*** 删除集合对象** @param collection 多个对象* @return*/public long deleteObject(final Collection collection) {return redisTemplate.delete(collection); } /*** 缓存List数据** @param key缓存的键值* @param dataList 待缓存的List数据* @return 缓存的对象*/public long setCacheList(final String key, final List dataList) {Long count = redisTemplate.opsForList().rightPushAll(key, dataList); return count == null ? 0 : count; } /*** 获得缓存的list对象** @param key 缓存的键值* @return 缓存键值对应的数据*/public List getCacheList(final String key) {return redisTemplate.opsForList().range(key, 0, -1); } /*** 缓存Set** @param key缓存键值* @param dataSet 缓存的数据* @return 缓存数据的对象*/public BoundSetOperations setCacheSet(final String key, final Set dataSet) {BoundSetOperations setOperation = redisTemplate.boundSetOps(key); Iterator it = dataSet.iterator(); while (it.hasNext()) {setOperation.add(it.next()); }return setOperation; } /*** 获得缓存的set** @param key* @return*/public Set getCacheSet(final String key) {return redisTemplate.opsForSet().members(key); } /*** 缓存Map** @param key* @param dataMap*/public void setCacheMap(final String key, final Map dataMap) {if (dataMap != null) {redisTemplate.opsForHash().putAll(key, dataMap); }} /*** 获得缓存的Map** @param key* @return*/public Map getCacheMap(final String key) {return redisTemplate.opsForHash().entries(key); } /*** 往Hash中存入数据** @param keyRedis键* @param hKeyHash键* @param value 值*/public void setCacheMapValue(final String key, final String hKey, final T value) {redisTemplate.opsForHash().put(key, hKey, value); } /*** 获取Hash中的数据** @param keyRedis键* @param hKey Hash键* @return Hash中的对象*/public T getCacheMapValue(final String key, final String hKey) {HashOperations opsForHash = redisTemplate.opsForHash(); return opsForHash.get(key, hKey); } /*** 获取多个Hash中的数据** @param keyRedis键* @param hKeys Hash键集合* @return Hash对象集合*/public List getMultiCacheMapValue(final String key, final Collection hKeys) {return redisTemplate.opsForHash().multiGet(key, hKeys); } /*** 获得缓存的基本对象列表** @param pattern 字符串前缀* @return 对象列表*/public Collection keys(final String pattern) {return redisTemplate.keys(pattern); }}
7.配置拦截器
/** * 防重复提交配置 */@Configurationpublic class RepeatSubmitConfig implements WebMvcConfigurer {@Autowiredprivate RepeatSubmitInterceptor repeatSubmitInterceptor; /*** 添加防重复提交拦截*/@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(repeatSubmitInterceptor).addPathPatterns("/**"); } /*** 生成防重复提交过滤器(重写request)* @return FilterRegistrationBean*/@SuppressWarnings({ "rawtypes", "unchecked" })@Beanpublic FilterRegistrationBean> createRepeatableFilter() {FilterRegistrationBean registration = new FilterRegistrationBean(); registration.setFilter(new RepeatableFilter()); registration.addUrlPatterns("/*"); registration.setName("repeatableFilter"); registration.setOrder(FilterRegistrationBean.LOWEST_PRECEDENCE); return registration; }}
8.controller
@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成功"; }}
三、测试 param传参:
【SpringBoot基于redis自定义注解实现后端接口防重复提交校验】
文章图片
文章图片
body传参:
文章图片
文章图片
项目源码到此这篇关于SpringBoot基于redis自定义注解实现后端接口防重复提交校验的文章就介绍到这了,更多相关SpringBoot 接口防重复提交校验内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
推荐阅读
- springboot中Excel文件下载踩坑大全
- Angular|Angular 基于自定义指令的内容投影 content projection 问题的单步调试
- 数据服务|第四章(Django绑定数据库并发布数据-[基于Vue、Django、supermap iserver和gerapy的生态旅游web系统开发实例])
- arduino|【毕业设计】基于arduino的蓝牙扫地机器人
- 单片机设计|毕业设计 - 题目(基于stm32的智能扫地机器人设计与实现)
- 硬件|硬件篇(教你做STM32蓝牙小车(基于STM32F103ZET6))
- Note|ROS--基于机器人操作系统设计与实现
- 面由心生,由脸观心(基于AI的面部微表情分析技术解读)
- SpringBoot2.1.4中的错误处理机制
- SpringBoot|SpringBoot feign动态设置数据源(https请求)