充值核销卡密恶意并发请求防止重复利用卡密充值成功解决方案
生活随笔
收集整理的這篇文章主要介紹了
充值核销卡密恶意并发请求防止重复利用卡密充值成功解决方案
小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.
項目場景:
springboot項目 核銷卡密充值 在并發(fā)測試中一個卡密重復(fù)充值成功問題描述:
第一次寫沒考慮那么多 就在業(yè)務(wù)層加了幾個校驗卡密狀態(tài)邏輯 只要卡密的狀態(tài)沒問題就直接修改用戶的余額
if(ObjectUtil.isNull(cards)){return failure("該卡密不存在");}if(cards.getIsEnable() == false){return failure("該卡密已被禁用,請聯(lián)系管理員");}if(cards.getStatus() == 3){return failure("該卡密已被使用,如余額未增加,請聯(lián)系管理員");} <update id="updateUserLeftAmount">UPDATE sys_user SETleft_amount = #{add}where id = #{id} </update>結(jié)果:
20個并發(fā)請求的情況下 沖100快 直接沖進去了900快 因為接口被大量的重復(fù)數(shù)據(jù)請求
解決方案:
既然是接口被重復(fù)請求, 那我就直接加個aop攔截接口,防止重復(fù)請求總行了把
切面邏輯 校驗相同ip對這個接口的請求次數(shù)
@Aspect @Component public class LimitRequestAspect {private static ConcurrentHashMap<String, ExpiringMap<String, Integer>> book = new ConcurrentHashMap<>();// 定義切點// 讓所有有@LimitRequest注解的方法都執(zhí)行切面方法@Pointcut("@annotation(limitRequest)")public void excudeService(LimitRequest limitRequest) {}@Around("excudeService(limitRequest)")public Object doAround(ProceedingJoinPoint pjp, LimitRequest limitRequest) throws Throwable {// 獲得request對象RequestAttributes ra = RequestContextHolder.getRequestAttributes();ServletRequestAttributes sra = (ServletRequestAttributes) ra;HttpServletRequest request = sra.getRequest();// 獲取Map對象, 如果沒有則返回默認(rèn)值// 第一個參數(shù)是key, 第二個參數(shù)是默認(rèn)值ExpiringMap<String, Integer> uc = book.getOrDefault(request.getRequestURI(), ExpiringMap.builder().variableExpiration().build());Integer uCount = uc.getOrDefault(request.getRemoteAddr(), 0);if (uCount >= limitRequest.count()) { // 超過次數(shù),不執(zhí)行目標(biāo)方法throw new BaseException("請勿頻繁請求"); // return "接口請求超過次數(shù)";} else if (uCount == 0){ // 第一次請求時,設(shè)置有效時間 // /** Expires entries based on when they were last accessed */ // ACCESSED, // /** Expires entries based on when they were created */ // CREATED;uc.put(request.getRemoteAddr(), uCount + 1, ExpirationPolicy.CREATED, limitRequest.time(), TimeUnit.MILLISECONDS);} else { // 未超過次數(shù), 記錄加一uc.put(request.getRemoteAddr(), uCount + 1);}book.put(request.getRequestURI(), uc);// result的值就是被攔截方法的返回值Object result = pjp.proceed();return result;}注解類
@Documented @Target(ElementType.METHOD) // 說明該注解只能放在方法上面 @Retention(RetentionPolicy.RUNTIME) public @interface LimitRequest {long time() default 6000; // 限制時間 單位:毫秒int count() default 1; // 允許請求的次數(shù) }結(jié)果:
雖然加了接口請求次數(shù)攔截,但并不能防止并發(fā)使用這個卡密充值重復(fù)的問題,然后大佬告訴我 加個鎖吧 然后發(fā)了張圖片給我
然后我根據(jù)圖片的描述加了個version字段 修改了一下我的sql
<update id="updateUserLeftAmount">UPDATE sys_user SETleft_amount = #{add},version = version+1where left_amount = #{leftAmount} and id = #{id} and version=#{version}</update>
再次測試:
還是會出現(xiàn)重復(fù)充值的情況解決方案:
請教大佬 大佬又發(fā)了兩張圖給我看 是關(guān)于redis的 讓我利用redis的單線程機制來校驗然后我集成了jedis又修改了億點點校驗邏輯 代碼如下
在修改用戶余額的操作前使用redis的setnx操作, 存入用戶的唯一id 再設(shè)置一個過期時間 然后再expire查詢一下是否存在 如果存在就return
JedisUtil jedisUtil = JedisUtil.getInstance();Long verifyCarmi = jedisUtil.setnxWithTimeOut("verifyCarmi", user.getId(), 10);if(verifyCarmi == 0){map.put("msg","請勿重復(fù)充值");map.put("bool",false);return map;}jedisUtil.setnxWithTimeOut的代碼
/*** 添加一個鍵值對,如果鍵存在不在添加,如果不存在,添加完成以后設(shè)置鍵的有效期* @param key* @param value* @param timeOut*/public Long setnxWithTimeOut(String key,String value,int timeOut){Jedis jedis = getJedis();long expire = 2;if(0!=jedis.setnx(key, value)){expire = jedis.expire(key, timeOut);}returnJedis(jedis);return expire;}再次測試:
沒問題了 ,并發(fā)請求下只能充值一次全給redis的校驗return了
redis真好用😊
感謝大佬 學(xué)會了學(xué)會了
總結(jié)
以上是生活随笔為你收集整理的充值核销卡密恶意并发请求防止重复利用卡密充值成功解决方案的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: “睡服”面试官系列第十三篇之函数的扩展(
- 下一篇: 前端学习(1539):hello wor