>  기사  >  데이터 베이스  >  SpringBoot가 Aop+Redis를 결합하여 인터페이스의 반복 제출을 방지하는 방법

SpringBoot가 Aop+Redis를 결합하여 인터페이스의 반복 제출을 방지하는 방법

王林
王林앞으로
2023-05-31 10:40:061237검색

실제 개발 프로젝트에서 외부에 노출된 인터페이스는 종종 많은 요청에 직면합니다. 멱등성의 개념을 설명하겠습니다. 여러 실행의 영향은 한 번의 실행의 영향과 동일합니다. 이 의미에 따르면 최종적인 의미는 데이터베이스에 미치는 영향은 일회성일 뿐 반복적으로 처리할 수 없다는 것입니다. 멱등성을 보장하는 방법에는 일반적으로 다음 방법이 포함됩니다.

1. 최종적으로 하나의 데이터만 데이터베이스에 삽입되도록 데이터베이스에 고유 인덱스를 설정합니다.

2. 토큰 메커니즘. 각 인터페이스 요청 전에 이 토큰을 요청의 헤더 본문에 추가합니다. 확인이 통과되면 토큰이 삭제됩니다. 요청이 다시 판단됩니다.

3. 비관적 잠금 또는 낙관적 잠금은 다른 SQL이 업데이트할 때마다 데이터를 업데이트할 수 없도록 보장할 수 있습니다(데이터베이스 엔진이 innodb인 경우 전체 테이블이 잠기지 않도록 선택 조건이 고유 인덱스여야 함)

4, 먼저 쿼리한 후 판단합니다. 먼저 데이터베이스에 데이터가 존재하는지 쿼리합니다. 존재가 요청이 이루어졌다는 것을 증명하면 해당 요청이 존재하지 않는다는 것을 증명합니다. 처음으로 들어오시면 요청이 직접 공개됩니다.

인터페이스의 반복 제출을 방지해야 하는 이유는 무엇입니까?
새로운 데이터 인터페이스 및 결제 인터페이스와 같은 일부 민감한 작업 인터페이스의 경우 사용자가 제출 버튼을 여러 번 부적절하게 클릭하면 이러한 인터페이스가 여러 번 요청되어 결국 시스템 예외가 발생할 수 있습니다.

프런트 엔드는 어떻게 제어할 수 있나요?
사용자가 제출 버튼을 클릭하면 프런트 엔드를 제어할 수 있습니다.
1. 버튼을 클릭하면 로딩 프롬프트 상자가 나타납니다.
3. 버튼 클릭 후 새 페이지로 이동

그러나 사용자가 어떤 이상한 작업을 수행할지 모르기 때문에 절대 사용자의 행동을 신뢰하지 마십시오. 중요한 것은 백엔드에서 처리하는 것입니다.

차단 처리를 위해 aop+redis 사용

1. 측면 클래스 RepeatSubmitAspect 만들기
구현 프로세스: 인터페이스 요청 후 키를 찾을 수 있는 경우 토큰+요청 경로가 키 값으로 사용됩니다. , 그 반대가 사실이 아닌 것으로 반복적으로 제출되었음을 증명합니다. 반복 제출이 아닌 경우 직접 공개되며, 키는 redis에 기록되고 일정 시간 내에 만료되도록 설정됩니다. (여기서는 만료 시간을 5초로 설정했습니다.)

기존 웹 프로젝트에서는, 반복적인 제출을 방지하기 위한 일반적인 접근 방식은 다음과 같습니다. 백엔드는 고유한 제출 토큰(uuid)을 생성하고 이를 서버에 저장합니다. 페이지가 요청을 시작하면 백엔드는 확인 후 토큰을 삭제합니다. 요청의 고유성을 보장하기 위한 요청입니다.
단, 어필방식은 앞부분과 뒷부분 모두 변경이 필요합니다. 프로젝트 초기라면 달성 가능하지만, 프로젝트 후반에는 많은 기능이 구현되어 있습니다. 대규모 변경은 불가능합니다.

Idea

1. Controller
2에 제출된 모든 요청을 표시하도록 @NoRepeatSubmit 주석을 사용자 정의합니다. AOP
3을 통해 @NoRepeatSubmit으로 표시된 모든 메서드를 차단합니다. 요청 주소는 redis 분산 잠금을 얻기 위한 고유 키로 사용됩니다. 이때 동시에 획득하면 하나의 스레드만 획득할 수 있습니다.
4. 업무 실행 후 잠금 해제

Redis 분산 잠금 정보

Redis를 사용하는 것은 로드 밸런싱 배포를 위한 것입니다. 독립 실행형 프로젝트인 경우 Redis를 대체하기 위해 로컬 스레드 안전 캐시를 사용할 수 있습니다. 코드
맞춤 주석

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @ClassName NoRepeatSubmit
 * @Description 这里描述
 * @Author admin
 * @Date 2021/3/2 16:16
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface NoRepeatSubmit {

    /**
     * 设置请求锁定时间
     *
     * @return
     */
    int lockTime() default 10;

}

AOP
package com.hongkun.aop;

/**
 * @ClassName RepeatSubmitAspect
 * @Description 这里描述
 * @Author admin
 * @Date 2021/3/2 16:15
 */

import com.hongkun.until.ApiResult;
import com.hongkun.until.Result;
import com.hongkun.until.RedisLock;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * @author liucheng
 * @since 2020/01/15
 * 防止接口重复提交
 */
@Aspect
@Component
public class RepeatSubmitAspect {
    private static final Logger LOGGER = LoggerFactory.getLogger(RepeatSubmitAspect.class);

    @Autowired
    private RedisLock redisLock;

    @Pointcut("@annotation(noRepeatSubmit)")
    public void pointCut(NoRepeatSubmit noRepeatSubmit) {
    }

    @Around("pointCut(noRepeatSubmit)")
    public Object around(ProceedingJoinPoint pjp, NoRepeatSubmit noRepeatSubmit) throws Throwable {

        int lockSeconds = noRepeatSubmit.lockTime();

        RequestAttributes ra = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes sra = (ServletRequestAttributes) ra;
        HttpServletRequest request = sra.getRequest();

        Assert.notNull(request, "request can not null");

        // 此处可以用token或者JSessionId
        String token = request.getHeader("token");
        String path = request.getServletPath();
        String key = getKey(token, path);
        String clientId = getClientId();

        boolean isSuccess = redisLock.lock(key, clientId, lockSeconds,TimeUnit.SECONDS);
        LOGGER.info("tryLock key = [{}], clientId = [{}]", key, clientId);

        if (isSuccess) {
            LOGGER.info("tryLock success, key = [{}], clientId = [{}]", key, clientId);
            // 获取锁成功
            Object result;
            try {
                // 执行进程
                result = pjp.proceed();
            } finally {
                // 解锁
                redisLock.unlock(key, clientId);
                LOGGER.info("releaseLock success, key = [{}], clientId = [{}]", key, clientId);
            }

            return result;

        } else {
            // 获取锁失败,认为是重复提交的请求
            LOGGER.info("tryLock fail, key = [{}]", key);
            return ApiResult.success(200, "重复请求,请稍后再试", null);
        }

    }

    private String getKey(String token, String path) {
        return "00000"+":"+token + path;
    }

    private String getClientId() {
        return UUID.randomUUID().toString();
    }


}

위 내용은 SpringBoot가 Aop+Redis를 결합하여 인터페이스의 반복 제출을 방지하는 방법의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 yisu.com에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제