使用拦截器和redis+token实现防重复提交完整代码
文章目錄
- 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服務器,與使用redistemplate可以起到相同效果。
redistemplate需要配置序列化,以防出現亂碼:
用于操作鍵值的工具:
/*** @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;}/*** 寫入緩存設置失效時間* @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;}/*** 判斷緩存中是否有對應的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;}/*** 刪除對應的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
關于HandlerInterceptor:
允許自定義處理程序執行鏈的工作流接口。 應用程序可以為某些處理程序組注冊任意數量的現有或自定義攔截器,以添加常見的預處理行為,而無需修改每個處理程序實現。
HandlerInterceptor 在適當的 HandlerAdapter 觸發處理程序本身的執行之前被調用。 這種機制可用于預處理方面的大量領域,例如授權檢查或常見的處理程序行為,如區域設置或主題更改。 它的主要目的是允許分解出重復的處理程序代碼。
在異步處理場景中,處理程序可能在單獨的線程中執行,而主線程退出而不呈現或調用postHandle和afterCompletion回調。 當并發處理程序執行完成時,請求被分派回來以繼續渲染模型,并再次調用此合約的所有方法。 有關更多選項和詳細信息,請參閱org.springframework.web.servlet.AsyncHandlerInterceptor
通常每個 HandlerMapping bean 定義一個攔截器鏈,共享它的粒度。 為了能夠將某個攔截器鏈應用于一組處理程序,需要通過一個 HandlerMapping bean 映射所需的處理程序。 攔截器本身被定義為應用程序上下文中的 bean,映射 bean 定義通過其“攔截器”屬性(在 XML 中: 的 )引用。
HandlerInterceptor 基本上類似于 Servlet 過濾器,但與后者相反,它只允許自定義預處理和禁止執行處理程序本身的選項,以及自定義后處理。 過濾器更強大,例如它們允許交換傳遞給鏈的請求和響應對象。 請注意,過濾器在 web.xml 中配置,即應用程序上下文中的 HandlerInterceptor。
作為基本準則,與細粒度處理程序相關的預處理任務是 HandlerInterceptor 實現的候選對象,尤其是分解出的公共處理程序代碼和授權檢查。 另一方面,過濾器非常適合請求內容和視圖內容處理,例如多部分表單和 GZIP 壓縮。 這通常顯示何時需要將過濾器映射到某些內容類型(例如圖像)或所有請求。
定義攔截器:
/*** @Author h* @Description 防止重復提交攔截器* @Date 11.17**/public class RepeatInterceptor implements HandlerInterceptor { @Autowired private TokenService tokenService;@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception {//HandlerMethod:封裝有關由方法和bean組成的處理程序方法的信息。// 提供對方法參數、方法返回值、方法注解等的便捷訪問。//類可以使用 bean 實例或 bean 名稱(例如,lazy-init bean、prototype bean)創建。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("不允許重復提交,請稍后重試");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);//添加重復提交的攔截器 } }tokenservice
/*** @Author h* @Description 用于創建token, 在防止重復提交時使用到* @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服務,創建token采用隨機算法工具類生成隨機uuid字符串,然后放入到redis中// (為了防止數據的冗余保留,這里設置過期時間為xx秒,具體可視業務而定),如果放入成功,最后返回這個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("已經存在");throw new CustomException(400L, "請勿重復提交");}return true;} }自己在攔截器中先檢查redis庫是否有相應的鍵,如果有則直接返回禁止提交的信息給前端
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注解,這樣訪問時可以起到攔截作用。
測試:
第一次訪問接口
第二次訪問時已經彈出了異常信息:
后記:
aop也可以起到攔截器相同的功能,且細粒度更大,只需要定義一個防止重復提交的aspect即可,以后有時間再試試。
總結
以上是生活随笔為你收集整理的使用拦截器和redis+token实现防重复提交完整代码的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: nacos作注册中心+feign接口调用
- 下一篇: 继承 WebMvcConfigurati