SpringBoot基于redis自定義注解實現(xiàn)后端接口防重復提交校驗
一、添加依賴
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-redis</artifactId> <version>1.4.4.RELEASE</version> </dependency> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>3.6.3</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.75</version> </dependency>
二、代碼實現(xiàn)
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); } @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(StandardCharsets.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內(nèi)容 * * @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 { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { ServletRequest requestWrapper = null; //將原生的request變?yōu)榭芍貜妥x取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); } } }
3.自定義注解
/** * 自定義注解防止表單重復提交 */ @Inherited @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface RepeatSubmit { }
4.創(chuàng)建防止重復提交攔截器
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; /** * 防止重復提交攔截器 */ @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 RedisUtil redisUtil; /** * 間隔時間,單位:秒 * <p> * 兩次相同參數(shù)的請求,如果間隔時間大于該參數(shù),系統(tǒng)不會認定為重復提交的數(shù)據(jù) */ @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<String, Object> 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); } } /** * 驗證是否重復提交由子類實現(xiàn)具體的防重復提交的規(guī)則 */ public boolean isRepeatSubmit(HttpServletRequest request) { String nowParams = ""; if (request instanceof RepeatedlyRequestWrapper) { RepeatedlyRequestWrapper repeatedlyRequest = (RepeatedlyRequestWrapper) request; nowParams = repeatedlyRequest.getBody(); } // body參數(shù)為空,獲取Parameter的數(shù)據(jù) if (StrUtil.isBlank(nowParams)) { nowParams = JSONObject.toJSONString(request.getParameterMap()); } Map<String, Object> nowDataMap = new HashMap<String, Object>(); 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<String, Object> sessionMap = (Map<String, Object>) sessionObj; if (sessionMap.containsKey(url)) { Map<String, Object> preDataMap = (Map<String, Object>) sessionMap.get(url); if (compareParams(nowDataMap, preDataMap) && compareTime(nowDataMap, preDataMap)) { return true; } } } Map<String, Object> cacheMap = new HashMap<String, Object>(); cacheMap.put(url, nowDataMap); redisUtil.setCacheObject(cacheRepeatKey, cacheMap, intervalTime, TimeUnit.SECONDS); return false; } /** * 判斷參數(shù)是否相同 */ private boolean compareParams(Map<String, Object> nowMap, Map<String, Object> preMap) { String nowParams = (String) nowMap.get(REPEAT_PARAMS); String preParams = (String) preMap.get(REPEAT_PARAMS); return nowParams.equals(preParams); } /** * 判斷兩次間隔時間 */ private boolean compareTime(Map<String, Object> nowMap, Map<String, Object> 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; @Configuration public 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); //最大空閑數(shù) jedisPoolConfig.setMaxIdle(200); //每次釋放連接的最大數(shù)目,默認是3 jedisPoolConfig.setNumTestsPerEvictionRun(1024); //逐出掃描的時間間隔(毫秒) 如果為負數(shù),則不運行逐出線程, 默認-1 jedisPoolConfig.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); } @Bean public RedisTemplate<Object, Object> redisTemplate(JedisConnectionFactory connectionFactory) { RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>(); redisTemplate.setConnectionFactory(connectionFactory); GenericJackson2JsonRedisSerializer jackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer(); redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setHashKeySerializer(new StringRedisSerializer()); redisTemplate.setValueSerializer(jackson2JsonRedisSerializer); redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer); //初始化參數(shù)和初始化工作 redisTemplate.afterPropertiesSet(); return redisTemplate; } /** * 緩存對象集合中,緩存是以key-value形式保存的, * 當不指定緩存的key時,SpringBoot會使用keyGenerator生成Key。 */ @Override @Bean public KeyGenerator keyGenerator() { return (target, method, params) -> { StringBuilder sb = new StringBuilder(); //類名+方法名 sb.append(target.getClass().getName()); sb.append(method.getName()); for (Object obj : params) { sb.append(obj.toString()); } return sb.toString(); }; } /** * 緩存管理器 * @param connectionFactory 連接工廠 * @return cacheManager */ @Bean public CacheManager cacheManager(JedisConnectionFactory connectionFactory) { //新建一個Jackson2JsonRedis的redis存儲的序列化方式 GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer(); //解決查詢緩存轉換異常的問題 // 配置序列化(解決亂碼的問題) //entryTtl設置過期時間 //serializeValuesWith設置redis存儲的序列化方式 RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig() .entryTtl(TIME_TO_LIVE) .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(genericJackson2JsonRedisSerializer)) .disableCachingNullValues(); //定義要返回的redis緩存管理對象 return RedisCacheManager.builder(connectionFactory).cacheDefaults(config).build(); } }
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; @Component public class RedisUtil { @Autowired private RedisTemplate redisTemplate; /** * 緩存基本的對象,Integer、String、實體類等 * * @param key 緩存的鍵值 * @param value 緩存的值 */ public <T> void setCacheObject(final String key, final T value) { redisTemplate.opsForValue().set(key, value); } /** * 緩存基本的對象,Integer、String、實體類等 * * @param key 緩存的鍵值 * @param value 緩存的值 * @param timeout 時間 * @param timeUnit 時間顆粒度 */ public <T> void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit) { redisTemplate.opsForValue().set(key, value, timeout, timeUnit); } /** * 設置有效時間 * * @param key Redis鍵 * @param timeout 超時時間 * @return true=設置成功;false=設置失敗 */ public boolean expire(final String key, final long timeout) { return expire(key, timeout, TimeUnit.SECONDS); } /** * 設置有效時間 * * @param key Redis鍵 * @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 緩存鍵值對應的數(shù)據(jù) */ public <T> T getCacheObject(final String key) { ValueOperations<String, T> 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數(shù)據(jù) * * @param key 緩存的鍵值 * @param dataList 待緩存的List數(shù)據(jù) * @return 緩存的對象 */ public <T> long setCacheList(final String key, final List<T> dataList) { Long count = redisTemplate.opsForList().rightPushAll(key, dataList); return count == null ? 0 : count; } /** * 獲得緩存的list對象 * * @param key 緩存的鍵值 * @return 緩存鍵值對應的數(shù)據(jù) */ public <T> List<T> getCacheList(final String key) { return redisTemplate.opsForList().range(key, 0, -1); } /** * 緩存Set * * @param key 緩存鍵值 * @param dataSet 緩存的數(shù)據(jù) * @return 緩存數(shù)據(jù)的對象 */ public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet) { BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key); Iterator<T> it = dataSet.iterator(); while (it.hasNext()) { setOperation.add(it.next()); } return setOperation; } /** * 獲得緩存的set * * @param key * @return */ public <T> Set<T> getCacheSet(final String key) { return redisTemplate.opsForSet().members(key); } /** * 緩存Map * * @param key * @param dataMap */ public <T> void setCacheMap(final String key, final Map<String, T> dataMap) { if (dataMap != null) { redisTemplate.opsForHash().putAll(key, dataMap); } } /** * 獲得緩存的Map * * @param key * @return */ public <T> Map<String, T> getCacheMap(final String key) { return redisTemplate.opsForHash().entries(key); } /** * 往Hash中存入數(shù)據(jù) * * @param key Redis鍵 * @param hKey Hash鍵 * @param value 值 */ public <T> void setCacheMapValue(final String key, final String hKey, final T value) { redisTemplate.opsForHash().put(key, hKey, value); } /** * 獲取Hash中的數(shù)據(jù) * * @param key Redis鍵 * @param hKey Hash鍵 * @return Hash中的對象 */ public <T> T getCacheMapValue(final String key, final String hKey) { HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash(); return opsForHash.get(key, hKey); } /** * 獲取多個Hash中的數(shù)據(jù) * * @param key Redis鍵 * @param hKeys Hash鍵集合 * @return Hash對象集合 */ public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys) { return redisTemplate.opsForHash().multiGet(key, hKeys); } /** * 獲得緩存的基本對象列表 * * @param pattern 字符串前綴 * @return 對象列表 */ public Collection<String> keys(final String pattern) { return redisTemplate.keys(pattern); } }
7.配置攔截器
/** * 防重復提交配置 */ @Configuration public class RepeatSubmitConfig implements WebMvcConfigurer { @Autowired private RepeatSubmitInterceptor repeatSubmitInterceptor; /** * 添加防重復提交攔截 */ @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(repeatSubmitInterceptor).addPathPatterns("/**"); } /** * 生成防重復提交過濾器(重寫request) * @return FilterRegistrationBean */ @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; } }
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<String> name){ return "保存Body成功"; } }
三、測試
param傳參:
body傳參:
到此這篇關于SpringBoot基于redis自定義注解實現(xiàn)后端接口防重復提交校驗的文章就介紹到這了,更多相關SpringBoot 接口防重復提交校驗內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Java while(scanner.hasNext())無法跳出的解決方案
這篇文章主要介紹了Java while(scanner.hasNext())無法跳出的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-10-10深入講解spring boot中servlet的啟動過程與原理
這篇文章主要給大家介紹了關于spring boot中servlet啟動過程與原理的相關資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2018-07-07SpringSecurity權限控制實現(xiàn)原理解析
這篇文章主要介紹了SpringSecurity權限控制實現(xiàn)原理解析,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2020-03-03

Spring boot 整合CXF開發(fā)web service示例