您好,登錄后才能下訂單哦!
這篇文章主要為大家展示了“SpringBoot + Redis怎么解決重復提交問題”,內容簡而易懂,條理清晰,希望能夠幫助大家解決疑惑,下面讓小編帶領大家一起研究并學習一下“SpringBoot + Redis怎么解決重復提交問題”這篇文章吧。
在開發中,一個對外暴露的接口可能會面臨瞬間的大量重復請求,如果想過濾掉重復請求造成對業務的傷害,那就需要實現冪等
任意多次執行所產生的影響均與一次執行的影響相同。最終的含義就是 對數據庫的影響只能是一次性的,不能重復處理。
數據庫建立唯一性索引,可以保證最終插入數據庫的只有一條數據
token機制,每次接口請求前先獲取一個token,然后再下次請求的時候在請求的header體中加上這個token,后臺進行驗證,如果驗證通過刪除token,下次請求再次判斷token(本次案例使用)
悲觀鎖或者樂觀鎖,悲觀鎖可以保證每次for update的時候其他sql無法update數據(在數據庫引擎是innodb的時候,select的條件必須是唯一索引,防止鎖全表)
先查詢后判斷,首先通過查詢數據庫是否存在數據,如果存在證明已經請求過了,直接拒絕該請求,如果沒有存在,就證明是第一次進來,直接放行
package com.ckw.idempotence.service; /** * @author ckw * @version 1.0 * @date 2020/6/11 9:42 * @description: redis工具類 */ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.ValueOperations; import org.springframework.data.redis.serializer.RedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; import org.springframework.stereotype.Component; import java.io.Serializable; import java.util.concurrent.TimeUnit; /** * redis工具類 */ @Component public class RedisService { private RedisTemplate redisTemplate; @Autowired(required = false) public void setRedisTemplate(RedisTemplate redisTemplate) { RedisSerializer stringSerializer = new StringRedisSerializer(); redisTemplate.setKeySerializer(stringSerializer); redisTemplate.setValueSerializer(stringSerializer); redisTemplate.setHashKeySerializer(stringSerializer); redisTemplate.setHashValueSerializer(stringSerializer); this.redisTemplate = redisTemplate; } /** * 寫入緩存 * * @param key * @param value * @return */ public boolean set(final String key, Object value) { boolean result = false; try { ValueOperations operations = redisTemplate.opsForValue(); operations.set(key, value); result = true; } catch (Exception e) { e.printStackTrace(); } return result; } /** * 寫入緩存設置時效時間 * * @param key * @param value * @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 o = null; ValueOperations valueOperations = redisTemplate.opsForValue(); return valueOperations.get(key); } /** * 刪除對應的value * @param key */ public Boolean remove(final String key) { if(exists(key)){ return redisTemplate.delete(key); } return false; } }
作用:攔截器攔截請求時,判斷調用的地址對應的Controller方法是否有自定義注解,有的話說明該接口方法進行 冪等
package com.ckw.idempotence.annotion; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * @author ckw * @version 1.0 * @date 2020/6/11 9:55 * @description: */ @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface AutoIdempotent { }
package com.ckw.idempotence.service; import com.ckw.idempotence.exectionhandler.BaseException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; import javax.servlet.http.HttpServletRequest; import java.util.UUID; /** * @author ckw * @version 1.0 * @date 2020/6/11 9:56 * @description: token服務 */ @Service public class TokenService { @Autowired RedisService redisService; //創建token public String createToken() { //使用UUID代表token UUID uuid = UUID.randomUUID(); String token = uuid.toString(); //存入redis boolean b = redisService.setEx(token, token, 10000L); return token; } //檢驗請求頭或者請求參數中是否有token public boolean checkToken(HttpServletRequest request) { String token = request.getHeader("token"); //如果header中是空的 if(StringUtils.isEmpty(token)){ //從request中拿 token = request.getParameter("token"); if(StringUtils.isEmpty(token)){ throw new BaseException(20001, "缺少參數token"); } } //如果從header中拿到的token不正確 if(!redisService.exists(token)){ throw new BaseException(20001, "不能重復提交-------token不正確、空"); } //token正確 移除token if(!redisService.remove(token)){ throw new BaseException(20001, "token移除失敗"); } return true; } }
這里用到了自定義異常和自定義響應體如下
自定義異常:
package com.ckw.idempotence.exectionhandler; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; /** * @author ckw * @version 1.0 * @date 2020/5/16 20:58 * @description: 自定義異常類 */ @Data @AllArgsConstructor @NoArgsConstructor public class BaseException extends RuntimeException { private Integer code; private String msg; }
設置統一異常處理:
package com.ckw.idempotence.exectionhandler; import com.ckw.idempotence.utils.R; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; /** * @author ckw * @version 1.0 * @date 2020/5/16 20:45 * @description: 統一異常處理器 */ @ControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(Exception.class) @ResponseBody public R error(Exception e){ e.printStackTrace(); return R.error(); } @ExceptionHandler(BaseException.class) @ResponseBody public R error(BaseException e){ e.printStackTrace(); return R.error().message(e.getMsg()).code(e.getCode()); } }
自定義響應體:
package com.ckw.idempotence.utils; import lombok.Data; import java.util.HashMap; import java.util.Map; /** * @author ckw * @version 1.0 * @date 2020/5/16 18:35 * @description: 返回結果 */ @Data public class R { private Boolean success; private Integer code; private String message; private Map<String, Object> data = new HashMap<String, Object>(); private R() { } //封裝返回成功 public static R ok(){ R r = new R(); r.setSuccess(true); r.setCode(ResultCode.SUCCESS); r.setMessage("成功"); return r; } //封裝返回失敗 public static R error(){ R r = new R(); r.setSuccess(false); r.setCode(ResultCode.ERROR); r.setMessage("失敗"); return r; } public R success(Boolean success){ this.setSuccess(success); return this; } public R message(String message){ this.setMessage(message); return this; } public R code(Integer code){ this.setCode(code); return this; } public R data(String key, Object value){ this.data.put(key, value); return this; } public R data(Map<String, Object> map){ this.setData(map); return this; } }
自定義響應碼:
package com.ckw.idempotence.utils; import lombok.Data; import java.util.HashMap; import java.util.Map; /** * @author ckw * @version 1.0 * @date 2020/5/16 18:35 * @description: 返回結果 */ @Data public class R { private Boolean success; private Integer code; private String message; private Map<String, Object> data = new HashMap<String, Object>(); private R() { } //封裝返回成功 public static R ok(){ R r = new R(); r.setSuccess(true); r.setCode(ResultCode.SUCCESS); r.setMessage("成功"); return r; } //封裝返回失敗 public static R error(){ R r = new R(); r.setSuccess(false); r.setCode(ResultCode.ERROR); r.setMessage("失敗"); return r; } public R success(Boolean success){ this.setSuccess(success); return this; } public R message(String message){ this.setMessage(message); return this; } public R code(Integer code){ this.setCode(code); return this; } public R data(String key, Object value){ this.data.put(key, value); return this; } public R data(Map<String, Object> map){ this.setData(map); return this; } }
1、攔截器配置類
package com.ckw.idempotence.config; import com.ckw.idempotence.interceptor.AutoIdempotentInterceptor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; /** * @author ckw * @version 1.0 * @date 2020/6/11 10:07 * @description: 攔截器配置類 */ @Configuration public class WebConfiguration implements WebMvcConfigurer { @Autowired private AutoIdempotentInterceptor autoIdempotentInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(autoIdempotentInterceptor); } }
2、攔截器類
package com.ckw.idempotence.interceptor; import com.ckw.idempotence.annotion.AutoIdempotent; import com.ckw.idempotence.service.TokenService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.lang.reflect.Method; /** * @author ckw * @version 1.0 * @date 2020/6/11 10:11 * @description: 攔截重復提交數據 */ @Component public class AutoIdempotentInterceptor implements HandlerInterceptor { @Autowired private TokenService tokenService; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if(!(handler instanceof HandlerMethod)) return true; HandlerMethod handlerMethod = (HandlerMethod) handler; Method method = handlerMethod.getMethod(); //拿到方法上面的自定義注解 AutoIdempotent annotation = method.getAnnotation(AutoIdempotent.class); //如果不等于null說明該方法要進行冪等 if(null != annotation){ return tokenService.checkToken(request); } return true; } }
package com.ckw.idempotence.service; import org.springframework.stereotype.Service; /** * @author ckw * @version 1.0 * @date 2020/6/11 10:04 * @description: */ @Service public class TestService { public String testMethod(){ return "正常業務邏輯"; } }
package com.ckw.idempotence.controller; import com.ckw.idempotence.annotion.AutoIdempotent; import com.ckw.idempotence.service.TestService; import com.ckw.idempotence.service.TokenService; import com.ckw.idempotence.utils.R; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; /** * @author ckw * @version 1.0 * @date 2020/6/11 9:58 * @description: */ @RestController @CrossOrigin @RequestMapping("/Idempotence") public class TestController { @Autowired private TokenService tokenService; @Autowired private TestService testService; @GetMapping("/getToken") public R getToken(){ String token = tokenService.createToken(); return R.ok().data("token",token); } //相當于添加數據接口(測試時 連續點擊添加數據按鈕 看結果是否是添加一條數據還是多條數據) @AutoIdempotent @PostMapping("/test/addData") public R addData(){ String s = testService.testMethod(); return R.ok().data("data",s); } }
第一次點擊:
第二次點擊:
以上是“SpringBoot + Redis怎么解決重復提交問題”這篇文章的所有內容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內容對大家有所幫助,如果還想學習更多知識,歡迎關注億速云行業資訊頻道!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。