您好,登錄后才能下訂單哦!
這篇文章主要為大家展示了“SpringBoot + Redis怎么解決重復(fù)提交問(wèn)題”,內(nèi)容簡(jiǎn)而易懂,條理清晰,希望能夠幫助大家解決疑惑,下面讓小編帶領(lǐng)大家一起研究并學(xué)習(xí)一下“SpringBoot + Redis怎么解決重復(fù)提交問(wèn)題”這篇文章吧。
在開(kāi)發(fā)中,一個(gè)對(duì)外暴露的接口可能會(huì)面臨瞬間的大量重復(fù)請(qǐng)求,如果想過(guò)濾掉重復(fù)請(qǐng)求造成對(duì)業(yè)務(wù)的傷害,那就需要實(shí)現(xiàn)冪等
任意多次執(zhí)行所產(chǎn)生的影響均與一次執(zhí)行的影響相同。最終的含義就是 對(duì)數(shù)據(jù)庫(kù)的影響只能是一次性的,不能重復(fù)處理。
數(shù)據(jù)庫(kù)建立唯一性索引,可以保證最終插入數(shù)據(jù)庫(kù)的只有一條數(shù)據(jù)
token機(jī)制,每次接口請(qǐng)求前先獲取一個(gè)token,然后再下次請(qǐng)求的時(shí)候在請(qǐng)求的header體中加上這個(gè)token,后臺(tái)進(jìn)行驗(yàn)證,如果驗(yàn)證通過(guò)刪除token,下次請(qǐng)求再次判斷token(本次案例使用)
悲觀鎖或者樂(lè)觀鎖,悲觀鎖可以保證每次for update的時(shí)候其他sql無(wú)法update數(shù)據(jù)(在數(shù)據(jù)庫(kù)引擎是innodb的時(shí)候,select的條件必須是唯一索引,防止鎖全表)
先查詢(xún)后判斷,首先通過(guò)查詢(xún)數(shù)據(jù)庫(kù)是否存在數(shù)據(jù),如果存在證明已經(jīng)請(qǐng)求過(guò)了,直接拒絕該請(qǐng)求,如果沒(méi)有存在,就證明是第一次進(jìn)來(lái),直接放行
package com.ckw.idempotence.service; /** * @author ckw * @version 1.0 * @date 2020/6/11 9:42 * @description: redis工具類(lèi) */ 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工具類(lèi) */ @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; } /** * 寫(xiě)入緩存 * * @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; } /** * 寫(xiě)入緩存設(shè)置時(shí)效時(shí)間 * * @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; } /** * 判斷緩存中是否有對(duì)應(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 o = null; ValueOperations valueOperations = redisTemplate.opsForValue(); return valueOperations.get(key); } /** * 刪除對(duì)應(yīng)的value * @param key */ public Boolean remove(final String key) { if(exists(key)){ return redisTemplate.delete(key); } return false; } }
作用:攔截器攔截請(qǐng)求時(shí),判斷調(diào)用的地址對(duì)應(yīng)的Controller方法是否有自定義注解,有的話說(shuō)明該接口方法進(jìn)行 冪等
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服務(wù) */ @Service public class TokenService { @Autowired RedisService redisService; //創(chuàng)建token public String createToken() { //使用UUID代表token UUID uuid = UUID.randomUUID(); String token = uuid.toString(); //存入redis boolean b = redisService.setEx(token, token, 10000L); return token; } //檢驗(yàn)請(qǐng)求頭或者請(qǐng)求參數(shù)中是否有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, "缺少參數(shù)token"); } } //如果從header中拿到的token不正確 if(!redisService.exists(token)){ throw new BaseException(20001, "不能重復(fù)提交-------token不正確、空"); } //token正確 移除token if(!redisService.remove(token)){ throw new BaseException(20001, "token移除失敗"); } return true; } }
這里用到了自定義異常和自定義響應(yīng)體如下
自定義異常:
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: 自定義異常類(lèi) */ @Data @AllArgsConstructor @NoArgsConstructor public class BaseException extends RuntimeException { private Integer code; private String msg; }
設(shè)置統(tǒng)一異常處理:
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: 統(tǒng)一異常處理器 */ @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()); } }
自定義響應(yīng)體:
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: 返回結(jié)果 */ @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; } }
自定義響應(yīng)碼:
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: 返回結(jié)果 */ @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、攔截器配置類(lèi)
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: 攔截器配置類(lèi) */ @Configuration public class WebConfiguration implements WebMvcConfigurer { @Autowired private AutoIdempotentInterceptor autoIdempotentInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(autoIdempotentInterceptor); } }
2、攔截器類(lèi)
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: 攔截重復(fù)提交數(shù)據(jù) */ @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說(shuō)明該方法要進(jìn)行冪等 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 "正常業(yè)務(wù)邏輯"; } }
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); } //相當(dāng)于添加數(shù)據(jù)接口(測(cè)試時(shí) 連續(xù)點(diǎn)擊添加數(shù)據(jù)按鈕 看結(jié)果是否是添加一條數(shù)據(jù)還是多條數(shù)據(jù)) @AutoIdempotent @PostMapping("/test/addData") public R addData(){ String s = testService.testMethod(); return R.ok().data("data",s); } }
第一次點(diǎn)擊:
第二次點(diǎn)擊:
以上是“SpringBoot + Redis怎么解決重復(fù)提交問(wèn)題”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對(duì)大家有所幫助,如果還想學(xué)習(xí)更多知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。