Hallo zusammen, ich bin Bruder Tian
Als ich gestern ein Scheininterview für einen Freund führte, wie implementiert man Schnittstellen-Idempotenz? Aus dem Tonfall seiner Antwort ist ersichtlich, dass er den achtbeinigen Aufsatz auswendig lernt.
Damit jeder die idempotente Implementierung der Schnittstelle leicht erleben kann, hat Bruder Tiandiesen Artikel heute zusammengestellt .
Dieser Artikel hat insgesamt neun Hauptinhalte:
Erstellen Sie eine eindeutige Kennung für jede Anfrage, die Idempotenz gewährleisten musstoken
, 先获取token
, 并将此token
存入redis, 请求接口时, 将此token
放到header或者作为请求参数请求接口, 后端接口判断redis中是否存在此token
:
token
, dann, wenn es sich um eine wiederholte Anfrage handelt, aufgrund von Token
wurde gelöscht, es kann die Überprüfung nicht bestehen und gibt Bitte wiederholen Sie den Vorgang nicht
Prompttoken
, 那么, 如果是重复请求, 由于token
已被删除, 则不能通过校验, 返回请勿重复操作
提示Spring Boot
Redis
@ApiIdempotent
注解 + 拦截器对请求进行拦截@ControllerAdvice
全局异常处理Jmeter
5. Projekteinführung
🎜Spring Boot
🎜🎜🎜🎜Redis
🎜🎜🎜🎜@ApiIdempotent
Annotation + Interceptor fängt Anfragen ab🎜🎜🎜🎜@ControllerAdvice
Globale Ausnahmebehandlung🎜🎜🎜🎜Stresstest-Tool:Jmeter
🎜🎜🎜🎜Erklärung:🎜🎜🎜Dieser Artikel konzentriert sich auf Die Kernimplementierung von Idempotence, wie Spring Boot funktioniert. Die Details zur Integration von Redis, ServerResponse, ResponseCode und anderen Details gehen über den Rahmen dieses Artikels hinaus.🎜
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🎜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);font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(239, 112, 96);">TestApplication
🎜rrreee🎜好了,以上便是代码的实现部分,下面我们就来验证一下.🎜🎜🎜🎜🎜七、测试验证🎜🎜 🎜🎜🎜获取token
的控制器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
。
jedisUtil.del(token)
Obwohl nur einer der Token tatsächlich gelöscht wird, da das Löschergebnis nicht überprüft wird, gibt es immer noch einen Daher müssen Parallelitätsprobleme überprüft werden
Tatsächlich ist die Idee sehr einfach, das heißt, jede Anfrage ist garantiert einzigartig und wird somit 保证幂等性
, 通过拦截器+注解
, 就不用每次请求都写重复代码, 其实也可以利用Spring AOP
erreicht.
Okay, ich werde es heute hier teilen.
Das obige ist der detaillierte Inhalt vonInterviewer: In der Zahlungsschnittstelle kann bei wiederholten Zahlungen für dieselbe Bestellung nur einmal Geld abgebucht werden.. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!