1.什么是接口防重?
在一定的時間內請求同一接口,同一參數。由於請求是健康請求,會執行正常的業務邏輯,從而產生大量的廢數據。
2.處理方法
第一種:前台在請求接口的時候,傳遞一個唯一值,然后在對應接口判斷該唯一值,在一定的時間內是否被消費過
第二種:采用Spring AOP理念,實現請求的切割,在請求執行到某個方法或某層時候,開始攔截進行,獲取該請求的參數,用戶信息,請求地址,存入redis中並放置過期時間,進行防重(推薦使用)
3.談談以上兩種處理方法的利弊
第一種:局限性太高,前台必須傳遞一個唯一值,就算請求到達指定后台服務,寫一個攔截器,需要配置太多不需要攔截的方法,也許你會說,可以攔截有規則的請求地址,這樣真的好嗎?
第二種:作為一名JAVA后台開發,Spring應該是熟悉的不能再熟悉了,Spring核心AOP又用了多少,針對以上請求,只需要寫一個注解類,然后切面到該注解上,在需要防重的方法上只需添加注解即可
4.具體代碼(采用第二種)
注解類
import java.lang.annotation.*;
/**
* 防重
* @author haodongdong
* @date 2020/8/12
* @return
*/
//標識該注解用於方法上
@Target({ElementType.METHOD})
//申明該注解為運行時注解,編譯后改注解不會被遺棄
@Retention(RetentionPolicy.RUNTIME)
//javadoc工具記錄
@Documented
public @interface PreventSubmit
{
}
切面類
import com.qianxian.common.exception.AppException; import com.qianxian.common.util.TokenUtil; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.redisson.api.RLock; import org.redisson.api.RedissonClient; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestContextHolder; import javax.servlet.http.HttpServletRequest; import java.util.Arrays; import java.util.concurrent.TimeUnit; /** * 防重復提交 * @author haodongdong * @date 2020/8/12 * @return */ @Component @Aspect @Slf4j public class PreventSubmitAspect { /** * 放重redis前綴 */ private static String API_PREVENT_SUBMIT = "api:preventSubmit:"; /** * 放重分布式鎖前綴 */ private static String API_LOCK_PREVENT_SUBMIT = "api:preventSubmit:lock:"; /** * 失效時間 */ private static Integer INVALID_NUMBER = 3; /** * redis */ @Autowired private StringRedisTemplate stringRedisTemplate; /** * 分布式鎖 */ @Autowired private RedissonClient redissonClient; /** * 防重 * @author haodongdong * @date 2020/8/12 * @return */ @Around("@annotation(com.qianxian.user.annotation.PreventSubmit)") public Object preventSubmitAspect(ProceedingJoinPoint joinPoint) throws Throwable { RLock lock = null; try { //獲取目標方法的參數 Object[] args = joinPoint.getArgs(); //獲取當前request請求 RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST); //獲取請求地址 String requestUri = request.getRequestURI(); //獲取用戶ID Long userId = null; try { userId = TokenUtil.getUserId(request); }catch (Exception e){} //拼接鎖前綴,采用同一方法,同一用戶,同一接口 String temp = requestUri.concat(Arrays.asList(args).toString()) + (userId != null ? userId : ""); temp = temp.replaceAll("/",""); //拼接rediskey String lockPrefix = API_LOCK_PREVENT_SUBMIT.concat(temp); String redisPrefix = API_PREVENT_SUBMIT.concat(temp); /** * 對同一方法同一用戶同一參數加鎖,即使獲取不到用戶ID,每個用戶請求數據也會不一致,不會造成接口堵塞 */ lock = this.redissonClient.getLock(lockPrefix); lock.lock(); String flag = this.stringRedisTemplate.opsForValue().get(redisPrefix); if(StringUtils.isNotEmpty(flag)){ throw new AppException("您當前的操作太頻繁了,請稍后再試!"); } //存入redis,設置失效時間 this.stringRedisTemplate.opsForValue().set(redisPrefix,redisPrefix,INVALID_NUMBER, TimeUnit.SECONDS); //執行目標方法 Object result = joinPoint.proceed(args); return result; }finally { if(lock != null){ lock.unlock(); } } } }
鏈接:https://www.jianshu.com/p/609fedde1234
來源:簡書
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。