SpringBoot?+?Redis如何解決重復(fù)提交問題(冪等)
在開發(fā)中,一個(gè)對(duì)外暴露的接口可能會(huì)面臨瞬間的大量重復(fù)請(qǐng)求,如果想過濾掉重復(fù)請(qǐng)求造成對(duì)業(yè)務(wù)的傷害,那就需要實(shí)現(xiàn)冪等
冪等:
任意多次執(zhí)行所產(chǎn)生的影響均與一次執(zhí)行的影響相同。最終的含義就是 對(duì)數(shù)據(jù)庫的影響只能是一次性的,不能重復(fù)處理。
解決方案:
- 數(shù)據(jù)庫建立唯一性索引,可以保證最終插入數(shù)據(jù)庫的只有一條數(shù)據(jù)
- token機(jī)制,每次接口請(qǐng)求前先獲取一個(gè)token,然后再下次請(qǐng)求的時(shí)候在請(qǐng)求的header體中加上這個(gè)token,后臺(tái)進(jìn)行驗(yàn)證,如果驗(yàn)證通過刪除token,下次請(qǐng)求再次判斷token(本次案例使用)
- 悲觀鎖或者樂觀鎖,悲觀鎖可以保證每次for update的時(shí)候其他sql無法update數(shù)據(jù)(在數(shù)據(jù)庫引擎是innodb的時(shí)候,select的條件必須是唯一索引,防止鎖全表)
- 先查詢后判斷,首先通過查詢數(shù)據(jù)庫是否存在數(shù)據(jù),如果存在證明已經(jīng)請(qǐng)求過了,直接拒絕該請(qǐng)求,如果沒有存在,就證明是第一次進(jìn)來,直接放行

一、搭建Redis服務(wù)
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;
}
/**
* 寫入緩存設(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方法是否有自定義注解,有的話說明該接口方法進(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 {
}
三、Token創(chuàng)建和校驗(yàn)
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: 自定義異常類
*/
@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、攔截器配置類
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: 攔截重復(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說明該方法要進(jìn)行冪等
if(null != annotation){
return tokenService.checkToken(request);
}
return true;
}
}
五、正常Sevice類
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ù)邏輯";
}
}
六、Controller類
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ù)接口(測試時(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)擊:

到此這篇關(guān)于SpringBoot + Redis如何解決重復(fù)提交問題(冪等)的文章就介紹到這了,更多相關(guān)SpringBoot Redis 重復(fù)提交 內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- SpringBoot+Redis大量重復(fù)提交問題的解決方案
- SpringBoot利用Redis解決海量重復(fù)提交問題
- SpringBoot+Redisson自定義注解一次解決重復(fù)提交問題
- SpringBoot+Redis海量重復(fù)提交問題解決
- 基于SpringBoot接口+Redis解決用戶重復(fù)提交問題
- SpringBoot整合redis+Aop防止重復(fù)提交的實(shí)現(xiàn)
- SpringBoot+Redis使用AOP防止重復(fù)提交的實(shí)現(xiàn)
- SpringBoot?使用AOP?+?Redis?防止表單重復(fù)提交的方法
- SpringBoot基于redis自定義注解實(shí)現(xiàn)后端接口防重復(fù)提交校驗(yàn)
- SpringBoot+Redis實(shí)現(xiàn)后端接口防重復(fù)提交校驗(yàn)的示例
- Spring Boot通過Redis實(shí)現(xiàn)防止重復(fù)提交
相關(guān)文章
Spring MVC 攔截器 interceptor 用法詳解
這篇文章主要介紹了Spring MVC 攔截器 interceptor 用法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-07-07
java.sql.Date和java.util.Date的區(qū)別詳解
Java中有兩個(gè)Date類,一個(gè)是java.util.Date通常情況下用它獲取當(dāng)前時(shí)間或構(gòu)造時(shí)間,另一個(gè)是java.sql.Date是針對(duì)SQL語句使用的,它只包含日期而沒有時(shí)間部分,這篇文章主要給大家介紹了關(guān)于java.sql.Date和java.util.Date區(qū)別的相關(guān)資料,需要的朋友可以參考下2023-03-03
Java最簡單的DES加密算法實(shí)現(xiàn)案例
下面小編就為大家?guī)硪黄狫ava最簡單的DES加密算法實(shí)現(xiàn)案例。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-06-06

