목차
七、测试验证 " >七、测试验证
Java Java인터뷰 질문들 면접관: 결제 인터페이스에서는 동일한 주문에 대한 반복 결제에 대해 한 번만 공제할 수 있나요?

면접관: 결제 인터페이스에서는 동일한 주문에 대한 반복 결제에 대해 한 번만 공제할 수 있나요?

Aug 22, 2023 pm 03:57 PM
자바 면접 질문

안녕하세요 여러분 저는 Tian 형제입니다

어제 친구를 위해 모의 인터뷰를 하던 중, 인터페이스 멱등성을 어떻게 구현하나요? 대답하는 톤을 보면 팔다리 수필을 외우고 있다는 걸 알 수 있다.

그래서 모든 사람이 인터페이스의 멱등성 구현을 쉽게 경험할 수 있도록 Tian 형제

가 오늘 이 글을 준비했습니다.

이 글은 총 9개의 주요 내용으로 구성되어 있습니다:

면접관: 결제 인터페이스에서는 동일한 주문에 대한 반복 결제에 대해 한 번만 공제할 수 있나요?

1. 멱등성의 개념 Imdempotence는 일반인의 용어로 동일한 요청을 여러 번 시작하는 인터페이스입니다. . 작업은 한 번만 실행될 수 있어야 합니다. 예:

  • 주문 인터페이스, 주문은 여러 번 생성할 수 없습니다
  • 결제 인터페이스, 동일한 주문에 대한 반복 결제는 한 번만 차감될 수 있습니다.
  • Alipay 콜백 인터페이스, 여러 콜백이 있을 수 있으며 반복 콜백을 처리해야 합니다
  • 일반적인 양식 제출 인터페이스는 네트워크 시간 초과 및 기타 이유로 인해 제출을 여러 번만 클릭할 수 있으며 한 번만 성공할 수 있습니다.
    고유 인덱스--새로운 더티 데이터 방지

토큰 메커니즘--반복적인 페이지 제출 방지비관적 잠금--데이터 획득 시 잠금(테이블 잠금 또는 행 잠금)

  1. 낙관적 잠금-구현됨 버전 번호 기준으로 데이터 업데이트 시 데이터를 순간적으로 확인
  2. 분산 잠금 - redis(jedis, redisson) 또는 Zookeeper 구현
  3. 상태 머신 - 상태 변경, 데이터 업데이트 시 상태 판단
  4. 3. 이 기사의 구현
  5. 이 기사에서는 두 번째 구현 방법, 즉
    메커니즘을 통해 인터페이스 멱등성 확인을 구현하는 방법을 사용합니다.

    4. 구현 아이디어

    멱등성을 보장해야 하는 각 요청에 대한 고유 식별자를 만듭니다token, 先获取token, 并将此token存入redis, 请求接口时, 将此token放到header或者作为请求参数请求接口, 后端接口判断redis中是否存在此token:

    • 존재하는 경우 비즈니스 로직을 정상적으로 처리하고 redis토큰, 토큰이 삭제되어 확인을 통과할 수 없으며 작업을 반복하지 마세요Prompttoken, 那么, 如果是重复请求, 由于token已被删除, 则不能通过校验, 返回请勿重复操作提示
    • 如果不存在, 说明参数不合法或者是重复请求, 返回提示即可

    五、项目简介

    • Spring Boot
    • Redis
    • @ApiIdempotent注解 + 拦截器对请求进行拦截
    • @ControllerAdvice全局异常处理
    • 压测工具: Jmeter
    존재하지 않으면 매개변수가 불법이거나 반복 요청이라는 의미이므로 그냥 반환하세요. 프롬프트

    5. 프로젝트 소개

    🎜스프링 부트🎜🎜🎜🎜Redis🎜🎜🎜🎜@ApiIdempotent 주석 + 인터셉터가 요청을 가로챕니다 🎜🎜🎜🎜@ControllerAdvice전역 예외 처리🎜🎜🎜🎜스트레스 테스트 도구: Jmeter🎜🎜🎜🎜설명:🎜🎜🎜이 문서는 다음에 중점을 둡니다. 멱등성의 핵심 구현, Spring Boot Redis, ServerResponse, ResponseCode 및 기타 세부 사항 통합에 대한 세부 사항은 이 기사의 범위를 벗어납니다.🎜

    六、代码实现

    1、maven依赖maven依赖

    <!-- Redis-Jedis -->
    <dependency>
       <groupId>redis.clients</groupId>
       <artifactId>jedis</artifactId>
       <version>2.9.0</version>
    </dependency>
    
    <!--lombok 本文用到@Slf4j注解, 也可不引用, 自定义log即可-->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.16.10</version>
    </dependency>
    로그인 후 복사

    2、JedisUtil

    @Component
    @Slf4j
    public class JedisUtil {
    
        @Autowired
        private JedisPool jedisPool;
    
        private Jedis getJedis() {
            return jedisPool.getResource();
        }
    
        /**
         * 设值
         *
         * @param key
         * @param value
         * @return
         */
        public String set(String key, String value) {
            Jedis jedis = null;
            try {
                jedis = getJedis();
                return jedis.set(key, value);
            } catch (Exception e) {
                log.error("set key:{} value:{} error", key, value, e);
                return null;
            } finally {
                close(jedis);
            }
        }
    
        /**
         * 设值
         *
         * @param key
         * @param value
         * @param expireTime 过期时间, 单位: s
         * @return
         */
        public String set(String key, String value, int expireTime) {
            Jedis jedis = null;
            try {
                jedis = getJedis();
                return jedis.setex(key, expireTime, value);
            } catch (Exception e) {
                log.error("set key:{} value:{} expireTime:{} error", key, value, expireTime, e);
                return null;
            } finally {
                close(jedis);
            }
        }
    
        /**
         * 取值
         *
         * @param key
         * @return
         */
        public String get(String key) {
            Jedis jedis = null;
            try {
                jedis = getJedis();
                return jedis.get(key);
            } catch (Exception e) {
                log.error("get key:{} error", key, e);
                return null;
            } finally {
                close(jedis);
            }
        }
    
        /**
         * 删除key
         *
         * @param key
         * @return
         */
        public Long del(String key) {
            Jedis jedis = null;
            try {
                jedis = getJedis();
                return jedis.del(key.getBytes());
            } catch (Exception e) {
                log.error("del key:{} error", key, e);
                return null;
            } finally {
                close(jedis);
            }
        }
    
        /**
         * 判断key是否存在
         *
         * @param key
         * @return
         */
        public Boolean exists(String key) {
            Jedis jedis = null;
            try {
                jedis = getJedis();
                return jedis.exists(key.getBytes());
            } catch (Exception e) {
                log.error("exists key:{} error", key, e);
                return null;
            } finally {
                close(jedis);
            }
        }
    
        /**
         * 设值key过期时间
         *
         * @param key
         * @param expireTime 过期时间, 单位: s
         * @return
         */
        public Long expire(String key, int expireTime) {
            Jedis jedis = null;
            try {
                jedis = getJedis();
                return jedis.expire(key.getBytes(), expireTime);
            } catch (Exception e) {
                log.error("expire key:{} error", key, e);
                return null;
            } finally {
                close(jedis);
            }
        }
    
        /**
         * 获取剩余时间
         *
         * @param key
         * @return
         */
        public Long ttl(String key) {
            Jedis jedis = null;
            try {
                jedis = getJedis();
                return jedis.ttl(key);
            } catch (Exception e) {
                log.error("ttl key:{} error", key, e);
                return null;
            } finally {
                close(jedis);
            }
        }
    
        private void close(Jedis jedis) {
            if (null != jedis) {
                jedis.close();
            }
        }
    
    }
    로그인 후 복사

    3、自定义注解@ApiIdempotent

    /**
     * 在需要保证 接口幂等性 的Controller的方法上使用此注解
     */
    @Target({ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface ApiIdempotent {
    }
    로그인 후 복사

    4、ApiIdempotentInterceptor 拦截器

    /**
     * 接口幂等性拦截器
     */
    public class ApiIdempotentInterceptor implements HandlerInterceptor {
    
        @Autowired
        private TokenService tokenService;
    
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
            if (!(handler instanceof HandlerMethod)) {
                return true;
            }
    
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            Method method = handlerMethod.getMethod();
    
            ApiIdempotent methodAnnotation = method.getAnnotation(ApiIdempotent.class);
            if (methodAnnotation != null) {
                check(request);// 幂等性校验, 校验通过则放行, 校验失败则抛出异常, 并通过统一异常处理返回友好提示
            }
    
            return true;
        }
    
        private void check(HttpServletRequest request) {
            tokenService.checkToken(request);
        }
    
        @Override
        public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
        }
    
        @Override
        public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
        }
    }
    로그인 후 복사

    5、TokenServiceImpl

    @Service
    public class TokenServiceImpl implements TokenService {
    
        private static final String TOKEN_NAME = "token";
    
        @Autowired
        private JedisUtil jedisUtil;
    
        @Override
        public ServerResponse createToken() {
            String str = RandomUtil.UUID32();
            StrBuilder token = new StrBuilder();
            token.append(Constant.Redis.TOKEN_PREFIX).append(str);
    
            jedisUtil.set(token.toString(), token.toString(), Constant.Redis.EXPIRE_TIME_MINUTE);
    
            return ServerResponse.success(token.toString());
        }
    
        @Override
        public void checkToken(HttpServletRequest request) {
            String token = request.getHeader(TOKEN_NAME);
            if (StringUtils.isBlank(token)) {// header中不存在token
                token = request.getParameter(TOKEN_NAME);
                if (StringUtils.isBlank(token)) {// parameter中也不存在token
                    throw new ServiceException(ResponseCode.ILLEGAL_ARGUMENT.getMsg());
                }
            }
    
            if (!jedisUtil.exists(token)) {
                throw new ServiceException(ResponseCode.REPETITIVE_OPERATION.getMsg());
            }
    
            Long del = jedisUtil.del(token);
            if (del <= 0) {
                throw new ServiceException(ResponseCode.REPETITIVE_OPERATION.getMsg());
            }
        }
    
    }
    로그인 후 복사

    6、TestApplication

    @SpringBootApplication
    @MapperScan("com.wangzaiplus.test.mapper")
    public class TestApplication  extends WebMvcConfigurerAdapter {
    
        public static void main(String[] args) {
            SpringApplication.run(TestApplication.class, args);
        }
    
        /**
         * 跨域
         * @return
         */
        @Bean
        public CorsFilter corsFilter() {
            final UrlBasedCorsConfigurationSource urlBasedCorsConfigurationSource = new UrlBasedCorsConfigurationSource();
            final CorsConfiguration corsConfiguration = new CorsConfiguration();
            corsConfiguration.setAllowCredentials(true);
            corsConfiguration.addAllowedOrigin("*");
            corsConfiguration.addAllowedHeader("*");
            corsConfiguration.addAllowedMethod("*");
            urlBasedCorsConfigurationSource.registerCorsConfiguration("/**", corsConfiguration);
            return new CorsFilter(urlBasedCorsConfigurationSource);
        }
    
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            // 接口幂等性拦截器
            registry.addInterceptor(apiIdempotentInterceptor());
            super.addInterceptors(registry);
        }
    
        @Bean
        public ApiIdempotentInterceptor apiIdempotentInterceptor() {
            return new ApiIdempotentInterceptor();
        }
    
    }
    로그인 후 복사

    好了,以上便是代码的实现部分,下面我们就来验证一下。

    七、测试验证

    获取token的控制器TokenController

    @RestController
    @RequestMapping("/token")
    public class TokenController {
    
        @Autowired
        private TokenService tokenService;
    
        @GetMapping
        public ServerResponse token() {
            return tokenService.createToken();
        }
    
    }
    로그인 후 복사
    로그인 후 복사

    2、JedisUtil🎜
    @RestController
    @RequestMapping("/test")
    @Slf4j
    public class TestController {
    
        @Autowired
        private TestService testService;
    
        @ApiIdempotent
        @PostMapping("testIdempotence")
        public ServerResponse testIdempotence() {
            return testService.testIdempotence();
        }
    
    }
    로그인 후 복사
    로그인 후 복사
    🎜3、自定义注解@ApiIdempotent🎜rrreee🎜4、ApiIdempotentInterceptor  拦截器🎜rrreee🎜5、TokenServiceImpl< /code>🎜rrreee🎜6、<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px; background-color: rgba(27, 31, 35, 0.05);글꼴 계열: 'Operator Mono', Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(239, 112, 96);">TestApplication 🎜rrreee🎜好了,以上便是代码的实现分,下面我们就来验证一下。🎜🎜🎜🎜🎜七、测试验证🎜🎜 🎜🎜🎜获取토큰의 태그 제조TokenController :🎜
    @RestController
    @RequestMapping("/token")
    public class TokenController {
    
        @Autowired
        private TokenService tokenService;
    
        @GetMapping
        public ServerResponse token() {
            return tokenService.createToken();
        }
    
    }
    로그인 후 복사
    로그인 후 복사

    TestController, 注意@ApiIdempotent注解, 在需要幂等性校验的方法上声明此注解即可, 不需要校验的无影响:

    @RestController
    @RequestMapping("/test")
    @Slf4j
    public class TestController {
    
        @Autowired
        private TestService testService;
    
        @ApiIdempotent
        @PostMapping("testIdempotence")
        public ServerResponse testIdempotence() {
            return testService.testIdempotence();
        }
    
    }
    로그인 후 복사
    로그인 후 복사

    获取token

    면접관: 결제 인터페이스에서는 동일한 주문에 대한 반복 결제에 대해 한 번만 공제할 수 있나요?

    查看Redis

    면접관: 결제 인터페이스에서는 동일한 주문에 대한 반복 결제에 대해 한 번만 공제할 수 있나요?

    测试接口安全性: 利用Jmeter测试工具模拟50个并发请求, 将上一步获取到的token作为参数

    면접관: 결제 인터페이스에서는 동일한 주문에 대한 반복 결제에 대해 한 번만 공제할 수 있나요?
    면접관: 결제 인터페이스에서는 동일한 주문에 대한 반복 결제에 대해 한 번만 공제할 수 있나요?

    header或参数均不传token, 或者token值为空, 或者token值乱填, 均无法通过校验, 如token值为abcd

    면접관: 결제 인터페이스에서는 동일한 주문에 대한 반복 결제에 대해 한 번만 공제할 수 있나요?

    8. 주의사항 (매우 중요)

    면접관: 결제 인터페이스에서는 동일한 주문에 대한 반복 결제에 대해 한 번만 공제할 수 있나요?

    위 그림에서는 동시성 보안 문제가 있기 때문에 삭제 성공 여부를 확인하지 않고 단순히 토큰을 직접 삭제할 수는 없습니다. 왜냐하면, 여러 스레드가 동시에 46행에 도달할 가능성이 있기 때문입니다. 이때 토큰이 삭제되지 않았으므로 실행이 계속됩니다. jedisUtil.del(token)의 삭제 결과를 확인하지 않고 직접 해제하면 반복되는 문제가 있습니다. 실제로 실제 삭제 작업만 있는 경우에도 제출은 계속 발생합니다. 아래에서 재현해 보세요.

    코드 약간 수정:

    면접관: 결제 인터페이스에서는 동일한 주문에 대한 반복 결제에 대해 한 번만 공제할 수 있나요?

    다시 요청

    면접관: 결제 인터페이스에서는 동일한 주문에 대한 반복 결제에 대해 한 번만 공제할 수 있나요?

    콘솔을 다시 살펴보세요

    면접관: 결제 인터페이스에서는 동일한 주문에 대한 반복 결제에 대해 한 번만 공제할 수 있나요?

    토큰 중 하나만 삭제되었지만 삭제 결과가 확인되지 않았기 때문에 여전히 남아 있습니다. 따라서 동시성 문제를 확인해야 합니다

    9. 요약

    사실 아이디어는 매우 간단합니다. 즉, 각 요청이 고유함을 보장하므로 保证幂等性, 通过拦截器+注解, 就不用每次请求都写重复代码, 其实也可以利用Spring AOP 달성됩니다.

    네, 오늘은 여기서 공유하겠습니다.

위 내용은 면접관: 결제 인터페이스에서는 동일한 주문에 대한 반복 결제에 대해 한 번만 공제할 수 있나요?의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.

핫 AI 도구

Undresser.AI Undress

Undresser.AI Undress

사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover

AI Clothes Remover

사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool

Undress AI Tool

무료로 이미지를 벗다

Clothoff.io

Clothoff.io

AI 옷 제거제

AI Hentai Generator

AI Hentai Generator

AI Hentai를 무료로 생성하십시오.

인기 기사

R.E.P.O. 에너지 결정과 그들이하는 일 (노란색 크리스탈)
4 몇 주 전 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. 최고의 그래픽 설정
4 몇 주 전 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. 아무도들을 수없는 경우 오디오를 수정하는 방법
4 몇 주 전 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. 채팅 명령 및 사용 방법
4 몇 주 전 By 尊渡假赌尊渡假赌尊渡假赌

뜨거운 도구

메모장++7.3.1

메모장++7.3.1

사용하기 쉬운 무료 코드 편집기

SublimeText3 중국어 버전

SublimeText3 중국어 버전

중국어 버전, 사용하기 매우 쉽습니다.

스튜디오 13.0.1 보내기

스튜디오 13.0.1 보내기

강력한 PHP 통합 개발 환경

드림위버 CS6

드림위버 CS6

시각적 웹 개발 도구

SublimeText3 Mac 버전

SublimeText3 Mac 버전

신 수준의 코드 편집 소프트웨어(SublimeText3)

인터뷰어: Spring Aop 공통 주석 및 실행 순서 인터뷰어: Spring Aop 공통 주석 및 실행 순서 Aug 15, 2023 pm 04:32 PM

Spring을 알아야 하므로 Aop의 모든 알림 순서에 대해 이야기해 보겠습니다. Spring Boot 또는 Spring Boot 2는 Aop의 실행 순서에 어떤 영향을 줍니까? AOP에서 직면한 함정에 대해 알려주십시오.

특정 그룹과의 인터뷰: 온라인에서 OOM을 발견하면 어떻게 문제를 해결해야 합니까? 어떻게 해결하나요? 어떤 옵션이 있나요? 특정 그룹과의 인터뷰: 온라인에서 OOM을 발견하면 어떻게 문제를 해결해야 합니까? 어떻게 해결하나요? 어떤 옵션이 있나요? Aug 23, 2023 pm 02:34 PM

OOM은 코드나 JVM 매개변수 구성으로 인해 프로그램에 취약점이 있음을 의미합니다. 이 기사에서는 Java 프로세스가 OOM을 트리거할 때 문제를 해결하는 방법에 대해 독자에게 설명합니다.

초보자도 BAT 면접관과 경쟁할 수 있습니다: CAS 초보자도 BAT 면접관과 경쟁할 수 있습니다: CAS Aug 24, 2023 pm 03:09 PM

자바 동시 프로그래밍 시리즈의 추가 챕터인 C A S(비교 및 교환)는 여전히 이해하기 쉬운 스타일로 그림과 텍스트를 포함해 독자들이 면접관과 열띤 대화를 나눌 수 있도록 해준다.

지난주에 XX보험 인터뷰했는데 멋있었어요! ! ! 지난주에 XX보험 인터뷰했는데 멋있었어요! ! ! Aug 25, 2023 pm 03:44 PM

지난 주에 그룹의 한 친구가 Ping An Insurance와 인터뷰를 하러 갔습니다. 결과는 다소 아쉽지만, 말씀하신 것처럼 기본적으로 모든 질문에 낙담하지 않기를 바랍니다. 면접 질문을 외워야 면접이 해결될 수 있으니, 열심히 해주세요!

Ele.me의 필기 시험 문제는 간단해 보이지만 많은 사람들을 당황하게 합니다. Ele.me의 필기 시험 문제는 간단해 보이지만 많은 사람들을 당황하게 합니다. Aug 24, 2023 pm 03:29 PM

많은 회사의 필기 시험 문제를 과소평가하지 마십시오. 함정이 있으며 우연히 함정에 빠질 수 있습니다. 이런 주기에 관한 필기시험 문제를 접하게 된다면 차분하게 생각하고 차근차근 풀어나가시길 권합니다.

5개의 문자열 면접 질문, 10% 미만의 사람들이 모두 올바르게 답할 수 있습니다! (답변 포함) 5개의 문자열 면접 질문, 10% 미만의 사람들이 모두 올바르게 답할 수 있습니다! (답변 포함) Aug 23, 2023 pm 02:49 PM

​이 기사에서는 Java String 클래스에 관한 5가지 면접 질문을 살펴보겠습니다. 저는 인터뷰 과정에서 이 5가지 질문 중 몇 가지를 직접 경험했습니다. 이 기사는 이러한 질문에 대한 답변이 왜 이런지 이해하는 데 도움이 될 것입니다.

100개의 Linux 인터뷰 질문과 답변을 수집하는 것이 좋습니다 100개의 Linux 인터뷰 질문과 답변을 수집하는 것이 좋습니다 Aug 23, 2023 pm 02:37 PM

이 기사에는 Linux 개요, 디스크, 디렉토리, 파일, 보안, 구문 수준, 실제 전투, 파일 관리 명령, 문서 편집 명령, 디스크 관리 명령, 네트워크 통신 명령, 시스템 관리 명령, 백업을 다루는 총 30,000 단어가 넘습니다. 압축 명령 등 Linux 지식 포인트 해체.

메이투안, 대답할 수 있는지 볼까? 메이투안, 대답할 수 있는지 볼까? Aug 24, 2023 pm 03:51 PM

메이투안, 대답할 수 있는지 볼까?

See all articles