면접관: 결제 인터페이스에서는 동일한 주문에 대한 반복 결제에 대해 한 번만 공제할 수 있나요?
안녕하세요 여러분 저는 Tian 형제입니다
어제 친구를 위해 모의 인터뷰를 하던 중, 인터페이스 멱등성을 어떻게 구현하나요? 대답하는 톤을 보면 팔다리 수필을 외우고 있다는 걸 알 수 있다.
그래서 모든 사람이 인터페이스의 멱등성 구현을 쉽게 경험할 수 있도록 Tian 형제가 오늘 이 글을 준비했습니다.
이 글은 총 9개의 주요 내용으로 구성되어 있습니다:

Imdempotence는 일반인의 용어로 동일한 요청을 여러 번 시작하는 인터페이스입니다. . 작업은 한 번만 실행될 수 있어야 합니다. 예:
주문 인터페이스, 주문은 여러 번 생성할 수 없습니다 결제 인터페이스, 동일한 주문에 대한 반복 결제는 한 번만 차감될 수 있습니다. -
Alipay 콜백 인터페이스, 여러 콜백이 있을 수 있으며 반복 콜백을 처리해야 합니다 -
일반적인 양식 제출 인터페이스는 네트워크 시간 초과 및 기타 이유로 인해 제출을 여러 번만 클릭할 수 있으며 한 번만 성공할 수 있습니다. 고유 인덱스--새로운 더티 데이터 방지
비관적 잠금--데이터 획득 시 잠금(테이블 잠금 또는 행 잠금)
- 낙관적 잠금-구현됨 버전 번호 기준으로 데이터 업데이트 시 데이터를 순간적으로 확인
- 분산 잠금 - redis(jedis, redisson) 또는 Zookeeper 구현
- 상태 머신 - 상태 변경, 데이터 업데이트 시 상태 판단
-
- 이 기사에서는 두 번째 구현 방법, 즉
메커니즘을 통해 인터페이스 멱등성 확인을 구현하는 방법을 사용합니다. 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
2、@RestController @RequestMapping("/token") public class TokenController { @Autowired private TokenService tokenService; @GetMapping public ServerResponse token() { return tokenService.createToken(); } }
로그인 후 복사로그인 후 복사JedisUtil
🎜🎜3、自定义注解@RestController @RequestMapping("/test") @Slf4j public class TestController { @Autowired private TestService testService; @ApiIdempotent @PostMapping("testIdempotence") public ServerResponse testIdempotence() { return testService.testIdempotence(); } }
로그인 후 복사로그인 후 복사@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 중국어 웹사이트의 기타 관련 기사를 참조하세요!

핫 AI 도구

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

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

Undress AI Tool
무료로 이미지를 벗다

Clothoff.io
AI 옷 제거제

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

인기 기사

뜨거운 도구

메모장++7.3.1
사용하기 쉬운 무료 코드 편집기

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

스튜디오 13.0.1 보내기
강력한 PHP 통합 개발 환경

드림위버 CS6
시각적 웹 개발 도구

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

뜨거운 주제











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

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

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

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

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

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

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