日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

使用拦截器和redis+token实现防重复提交完整代码

發(fā)布時間:2024/9/30 编程问答 45 豆豆
生活随笔 收集整理的這篇文章主要介紹了 使用拦截器和redis+token实现防重复提交完整代码 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

文章目錄

    • redis配置:
    • 自定義一個注解:
    • 自定義類繼承HandlerInterceptor
    • mvc添加剛剛自定義的攔截器使之生效
    • tokenservice
    • controller

redis配置:

# redis spring.redis.host=47.101.210.219 spring.redis.port=6379 spring.redis.timeout=0 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency>

其實也可以用jedis連接redis服務(wù)器,與使用redistemplate可以起到相同效果。
redistemplate需要配置序列化,以防出現(xiàn)亂碼:

import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer;/*** @Author h* @Description redis配置類* @Date 11.17**/ @Configuration public class redisConfig {@Bean//1.項目啟動時此方法先被注冊成bean被spring管理public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {RedisTemplate<String, Object> template = new RedisTemplate<>();// 配置連接工廠template.setConnectionFactory(factory);//使用Jackson2JsonRedisSerializer來序列化和反序列化redis的value值(默認(rèn)使用JDK的序列化方式)Jackson2JsonRedisSerializer jacksonSeial = new Jackson2JsonRedisSerializer(Object.class);ObjectMapper om = new ObjectMapper();// 指定要序列化的域,field,get和set,以及修飾符范圍,ANY是都有包括private和publicom.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);// 指定序列化輸入的類型,類必須是非final修飾的,final修飾的類,比如String,Integer等會跑出異常//om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);jacksonSeial.setObjectMapper(om);// 值采用json序列化template.setValueSerializer(jacksonSeial);//使用StringRedisSerializer來序列化和反序列化redis的key值template.setKeySerializer(new StringRedisSerializer());// 設(shè)置hash key 和value序列化模式template.setHashKeySerializer(new StringRedisSerializer());template.setHashValueSerializer(jacksonSeial);template.afterPropertiesSet();return template;} }

用于操作鍵值的工具:

/*** @Author hzy* @Description redis工具類* @Date 11.17**/ @Component public class RedisTemplateUtil {@Qualifier("redisTemplate")@Autowiredprivate RedisTemplate redisTemplate;/*** 寫入緩存* @param key* @param value* @return*/public boolean set(final String key, Object value) {boolean result = false;try{ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();operations.set(key, value);result = true;} catch(Exception e) {e.printStackTrace();}return result;}/*** 寫入緩存設(shè)置失效時間* @param key* @param value* @param expireTime* @return*/public boolean setEx(final String key, Object value, Long expireTime) {boolean result = false;try{ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();operations.set(key, value);redisTemplate.expire(key, expireTime, TimeUnit.SECONDS);result = true;} catch(Exception e) {e.printStackTrace();}return result;}/*** 判斷緩存中是否有對應(yīng)的value* @param key* @return*/public boolean exists(final String key) {return redisTemplate.hasKey(key);}/*** 讀取緩存* @param key* @return*/public Object get(final String key) {Object result = null;ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();result = operations.get(key);return result;}/*** 刪除對應(yīng)的value* @param key*/public boolean remove(final String key) {if(exists(key)) {Boolean delete= redisTemplate.delete(key);return delete;}return false;} }

自定義一個注解:

@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface ForbidSubmit {}

自定義類繼承HandlerInterceptor

關(guān)于HandlerInterceptor:
允許自定義處理程序執(zhí)行鏈的工作流接口。 應(yīng)用程序可以為某些處理程序組注冊任意數(shù)量的現(xiàn)有或自定義攔截器,以添加常見的預(yù)處理行為,而無需修改每個處理程序?qū)崿F(xiàn)。
HandlerInterceptor 在適當(dāng)?shù)?HandlerAdapter 觸發(fā)處理程序本身的執(zhí)行之前被調(diào)用。 這種機(jī)制可用于預(yù)處理方面的大量領(lǐng)域,例如授權(quán)檢查或常見的處理程序行為,如區(qū)域設(shè)置或主題更改。 它的主要目的是允許分解出重復(fù)的處理程序代碼。
在異步處理場景中,處理程序可能在單獨的線程中執(zhí)行,而主線程退出而不呈現(xiàn)或調(diào)用postHandle和afterCompletion回調(diào)。 當(dāng)并發(fā)處理程序執(zhí)行完成時,請求被分派回來以繼續(xù)渲染模型,并再次調(diào)用此合約的所有方法。 有關(guān)更多選項和詳細(xì)信息,請參閱org.springframework.web.servlet.AsyncHandlerInterceptor
通常每個 HandlerMapping bean 定義一個攔截器鏈,共享它的粒度。 為了能夠?qū)⒛硞€攔截器鏈應(yīng)用于一組處理程序,需要通過一個 HandlerMapping bean 映射所需的處理程序。 攔截器本身被定義為應(yīng)用程序上下文中的 bean,映射 bean 定義通過其“攔截器”屬性(在 XML 中: 的 )引用。
HandlerInterceptor 基本上類似于 Servlet 過濾器,但與后者相反,它只允許自定義預(yù)處理和禁止執(zhí)行處理程序本身的選項,以及自定義后處理。 過濾器更強(qiáng)大,例如它們允許交換傳遞給鏈的請求和響應(yīng)對象。 請注意,過濾器在 web.xml 中配置,即應(yīng)用程序上下文中的 HandlerInterceptor。
作為基本準(zhǔn)則,與細(xì)粒度處理程序相關(guān)的預(yù)處理任務(wù)是 HandlerInterceptor 實現(xiàn)的候選對象,尤其是分解出的公共處理程序代碼和授權(quán)檢查。 另一方面,過濾器非常適合請求內(nèi)容和視圖內(nèi)容處理,例如多部分表單和 GZIP 壓縮。 這通常顯示何時需要將過濾器映射到某些內(nèi)容類型(例如圖像)或所有請求。

定義攔截器:

/*** @Author h* @Description 防止重復(fù)提交攔截器* @Date 11.17**/public class RepeatInterceptor implements HandlerInterceptor { @Autowired private TokenService tokenService;@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception {//HandlerMethod:封裝有關(guān)由方法和bean組成的處理程序方法的信息。// 提供對方法參數(shù)、方法返回值、方法注解等的便捷訪問。//類可以使用 bean 實例或 bean 名稱(例如,lazy-init bean、prototype bean)創(chuàng)建。if(handler instanceof HandlerMethod) { HandlerMethod handlerMethod = (HandlerMethod) handler;Method method = handlerMethod.getMethod();//被ForbidSubmit注釋的方法ForbidSubmit annotation = method.getAnnotation(ForbidSubmit.class); if(annotation != null) {try {tokenService.checkToken(request,method.getName());tokenService.createToken(response,method.getName());}catch (Exception e){Result result = Result.fail("不允許重復(fù)提交,請稍后重試");ServletUtils.renderString(response, JSONUtils.marshal(result));throw e;}}//renderstring方法: response.setContentType("application/json");// response.setCharacterEncoding("utf-8");// response.getWriter().print(string);return true; }else {return true;//return preHandle(request,response,handler);}}}

mvc添加剛剛自定義的攔截器使之生效

@Configuration @EnableTransactionManagement public class MyConfig extends WebMvcConfigurationSupport { @Autowired private RepeatInterceptor repeatInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(repeatInterceptor);super.addInterceptors(registry);//添加重復(fù)提交的攔截器 } }

tokenservice

/*** @Author h* @Description 用于創(chuàng)建token, 在防止重復(fù)提交時使用到* @Date 11.17**/ @Service public class TokenService {String TOKEN_NAME = "token";String TOKEN_PREFIX = "token:";// 過期時間, 10s,Integer EXPIRE_TIME_MINUTE = 10;// 過期時間, 一小時Integer EXPIRE_TIME_HOUR = 60 * 60;// 過期時間, 一天Integer EXPIRE_TIME_DAY = 60 * 60 * 24;//token引用了redis服務(wù),創(chuàng)建token采用隨機(jī)算法工具類生成隨機(jī)uuid字符串,然后放入到redis中// (為了防止數(shù)據(jù)的冗余保留,這里設(shè)置過期時間為xx秒,具體可視業(yè)務(wù)而定),如果放入成功,最后返回這個token值。// checkToken方法就是從redis中獲取token到值(如果拿不到就添加一個鍵值對),// 如若不存在,直接拋出異常。這個異常信息可以被攔截器捕捉到,然后返回給前端。@Autowiredprivate RedisTemplateUtil redisTemplateUtil;public String createToken(HttpServletResponse response,String name){String str = UUID.randomUUID().toString();StringBuilder token = new StringBuilder();response.setHeader("Access-Control-Expose-Headers",TOKEN_NAME);try {token.append(TOKEN_PREFIX).append(str);String key = TOKEN_PREFIX+ IpUtils.getHostIp()+name;response.setHeader(TOKEN_NAME,token.toString());if(redisTemplateUtil.exists(key)){return redisTemplateUtil.get(key).toString();}redisTemplateUtil.setEx(key, token.toString(), EXPIRE_TIME_MINUTE.longValue());boolean notEmpty = Objects.nonNull(token.toString());if(notEmpty){return token.toString();}}catch (Exception e){e.printStackTrace();}return new String();}public boolean checkToken(HttpServletRequest request,String name) throws Exception{String key = TOKEN_PREFIX+IpUtils.getHostIp()+name;boolean exists = redisTemplateUtil.exists(key);if(exists){System.out.println("已經(jīng)存在");throw new CustomException(400L, "請勿重復(fù)提交");}return true;} }

自己在攔截器中先檢查redis庫是否有相應(yīng)的鍵,如果有則直接返回禁止提交的信息給前端

controller

/*** 新增保存角色信息*/@ForbidSubmit@ApiOperation("新增一個角色")@PostMapping("/add/{RoleName}")@ResponseBodypublic Result addSave(@PathVariable("RoleName") String newRoleName,HttpServletResponse response){InowRole inowRole = new InowRole();inowRole.setRoleName(newRoleName);inowRole.setCreateBy("ShiroUtils.getLoginName()");//獲得登陸者的姓名inowRole.setCreateTime(new Date());inowRole.setDelFlag(0);if(inowRoleService.save(inowRole)){return Result.success(inowRole);}else{return Result.fail("新增失敗");}}

在上面的controller方法中加了 @ForbidSubmit注解,這樣訪問時可以起到攔截作用。

測試:
第一次訪問接口

第二次訪問時已經(jīng)彈出了異常信息:

后記:
aop也可以起到攔截器相同的功能,且細(xì)粒度更大,只需要定義一個防止重復(fù)提交的aspect即可,以后有時間再試試。

總結(jié)

以上是生活随笔為你收集整理的使用拦截器和redis+token实现防重复提交完整代码的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。