Spring Boot 는 AOP 를 사용 하여 중복 제출 을 방지 하 는 방법 예시

전통 적 인 웹 프로젝트 에서 중복 제출 을 방지 합 니 다.일반적인 방법 은 백 엔 드 에서 유일한 제출 토 큰(uid)을 만 들 고 서버 에 저장 하 는 것 입 니 다.페이지 제출 요청 은 이 제출 토 큰 을 가지 고 백 엔 드 인증 을 하고 첫 번 째 검증 후 이 토 큰 을 삭제 하여 제출 요청 의 유일 성 을 확보 합 니 다.
상술 한 사고방식 은 사실 문제 가 없 지만 앞 뒤 가 약간 바 뀌 어야 한다.만약 에 업무 개발 이 끝나 고 이것 을 더 하면 개 동량 이 좀 크다.본 절의 실현 방안 은 전단 협조,순 백 엔 드 처리 가 필요없다.
사고의 방향
  • 사용자 정의 주석@NoRepeat Submit 모든 컨트롤 러 의 제출 요청 표시
  • AOP 를 통 해@NoRepeatSubmit 라 고 표 시 된 모든 방법 을 차단
  • 업무 방법 이 실행 되 기 전에 현재 사용자 의 token(또는 JSsionId)+현재 요청 주 소 를 가 져 옵 니 다.유일한 KEY 로 서 Redis 분포 식 자 물 쇠 를 가 져 옵 니 다.(이때 동시에 가 져 오 면 하나의 스 레 드 만 자 물 쇠 를 성공 적 으로 가 져 옵 니 다)
  • 업무 방법 집행 후 잠 금 해제
  • Redis 분산 잠 금 에 대하 여
    모 르 는 친구 가 여기에 도장 을 찍다=>Redis 분산 잠 금 의 정확 한 실현 방식
    Redis 를 사용 하 는 것 은 부하 균형 배 치 를 위 한 것 입 니 다.단일 기기 의 배 치 된 프로젝트 라면 Redis 대신 안전 한 로 컬 Cache 를 사용 할 수 있 습 니 다.
    Code
    여기에 AOP 클래스 와 테스트 클래스 만 붙 여 져 있 습 니 다.전체 코드 는=>Gitee참조.
    
    @Aspect
    @Component
    public class RepeatSubmitAspect {
    
      private static final Logger LOGGER = LoggerFactory.getLogger(RepeatSubmitAspect.class);
    
      @Autowired
      private RedisLock redisLock;
    
      @Pointcut("@annotation(com.gitee.taven.aop.NoRepeatSubmit)")
      public void pointCut() {}
    
      @Around("pointCut()")
      public Object before(ProceedingJoinPoint pjp) {
        try {
          HttpServletRequest request = RequestUtils.getRequest();
          Assert.notNull(request, "request can not null");
    
          //      token  JSessionId
          String token = request.getHeader("Authorization");
          String path = request.getServletPath();
          String key = getKey(token, path);
          String clientId = getClientId();
    
          boolean isSuccess = redisLock.tryLock(key, clientId, 10);
          LOGGER.info("tryLock key = [{}], clientId = [{}]", key, clientId);
    
          if (isSuccess) {
            LOGGER.info("tryLock success, key = [{}], clientId = [{}]", key, clientId);
            //      ,     
            Object result = pjp.proceed();
            //   
            redisLock.releaseLock(key, clientId);
            LOGGER.info("releaseLock success, key = [{}], clientId = [{}]", key, clientId);
            return result;
    
          } else {
            //      ,          
            LOGGER.info("tryLock fail, key = [{}]", key);
            return new ApiResult(200, "    ,     ", null);
          }
    
        } catch (Throwable throwable) {
          throwable.printStackTrace();
        }
    
        return new ApiResult(500, "    ", null);
      }
    
      private String getKey(String token, String path) {
        return token + path;
      }
    
      private String getClientId() {
        return UUID.randomUUID().toString();
      }
    
    }
    
    
    다 중 루틴 테스트
    테스트 코드 는 다음 과 같 습 니 다.10 개의 요청 을 동시에 제출 하 는 것 을 모 의 합 니 다.
    
    @Component
    public class RunTest implements ApplicationRunner {
    
      private static final Logger LOGGER = LoggerFactory.getLogger(RunTest.class);
    
      @Autowired
      private RestTemplate restTemplate;
    
      @Override
      public void run(ApplicationArguments args) throws Exception {
        System.out.println("       ");
        String url="http://localhost:8000/submit";
        CountDownLatch countDownLatch = new CountDownLatch(1);
        ExecutorService executorService = Executors.newFixedThreadPool(10);
    
        for(int i=0; i<10; i++){
          String userId = "userId" + i;
          HttpEntity request = buildRequest(userId);
          executorService.submit(() -> {
            try {
              countDownLatch.await();
              System.out.println("Thread:"+Thread.currentThread().getName()+", time:"+System.currentTimeMillis());
              ResponseEntity<String> response = restTemplate.postForEntity(url, request, String.class);
              System.out.println("Thread:"+Thread.currentThread().getName() + "," + response.getBody());
    
            } catch (InterruptedException e) {
              e.printStackTrace();
            }
          });
        }
    
        countDownLatch.countDown();
      }
    
      private HttpEntity buildRequest(String userId) {
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        headers.set("Authorization", "yourToken");
        Map<String, Object> body = new HashMap<>();
        body.put("userId", userId);
        return new HttpEntity<>(body, headers);
      }
    
    }
    
    
    중복 제출 을 방지 하 는 데 성공 하 였 습 니 다.콘 솔 로 그 는 다음 과 같 습 니 다.10 개의 스 레 드 의 시작 시간 이 거의 동시에 시작 되 었 고 한 개의 요청 만 제출 되 었 습 니 다.

    이 절 데모
    여기에 찔러 라=>Gitee
    build 프로젝트 후 로 컬 redis 를 시작 하고 프로젝트 를 실행 하면 자동 으로 테스트 방법 을 실행 합 니 다.
    이상 이 바로 본 고의 모든 내용 입 니 다.여러분 의 학습 에 도움 이 되 고 저 희 를 많이 응원 해 주 셨 으 면 좋 겠 습 니 다.

    좋은 웹페이지 즐겨찾기