赞
踩
简单的说就是对于同一笔业务操作,不管调用多少次,得到的结果都是一样的。
例如:在一次用户的支付订单的操作中,第一次支付时因一些原因显示网络异常,但是后台实际已经扣款了,那么用户支付第二次时,就会给用户显示扣款成功.
实现接口幂等性的设计方案可以有很多种,今天我们就用自定义注解的方式来解决
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version>2.3.3.RELEASE</version>
</dependency>
spring.redis.port=6300
spring.redis.database=4
spring.redis.host=10.16.64.30
spring.redis.password=123456
@Component public class RedisService { @Autowired StringRedisTemplate redisTemplate; /** * 给key设置过期时间 * * @param key * @param value * @param time * @return */ public boolean setExp(String key, String value, Long time) { ValueOperations<String, String> ops = redisTemplate.opsForValue(); ops.set(key, value); Boolean expire = redisTemplate.expire(key, time, TimeUnit.SECONDS); if (expire) { return true; } return false; } /** * 判读key是否存在 * * @param key * @return */ public boolean exists(String key) { return redisTemplate.hasKey(key); } /** * 删除key * * @param key * @return */ public boolean remove(String key) { if (exists(key)) { return redisTemplate.delete(key); } return false; } }
@Component public class TokenService { @Autowired RedisService redisService; /** * 创建token 并存入redis * @return */ public String createToken() { String token = UUID.randomUUID().toString().replaceAll("-", ""); if (redisService.setExp(token, token, 1000L)) { return token; } return null; } /** * 校验token * @param request * @return */ public boolean checkToken(HttpServletRequest request) { String token = request.getHeader("token"); if (StringUtils.isEmpty(token)) { token = request.getParameter("token"); if (StringUtils.isEmpty(token)) { return false; } } if (!redisService.exists(token)) { return false; } if (!redisService.remove(token)) { return false; } return true; } }
@Target(ElementType.METHOD) //注解作用于方法上
@Retention(RetentionPolicy.RUNTIME) //保留到运行时
public @interface Idempotence {
}
/* * @className: IdempotenceInterceptor * @description 对有此注解的相关接口作拦截处理,没有此注解的接口直接放行 * @since JDK1.8 * @author ljh * @createdAt 2020/9/3 0003 * @version 1.0.0 **/ @Component public class IdempotenceInterceptor implements HandlerInterceptor { @Autowired TokenService tokenService; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (!(handler instanceof HandlerMethod)) { return true; } Method method = ((HandlerMethod) handler).getMethod(); Idempotence annotation = method.getAnnotation(Idempotence.class); if (annotation != null) { if (tokenService.checkToken(request)) { return true; } else { return false; } } return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { } }
@Configuration
public class WebMvcConfing implements WebMvcConfigurer {
@Autowired
IdempotenceInterceptor interceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(interceptor).addPathPatterns("/**");
}
}
@RestController public class TestController { @Autowired TokenService tokenService; @Idempotence @PostMapping("/test1") public String test1(){ return "111"; //被拦截器拦截作校验处理 } @PostMapping("/test2") public String test2(){ return "222"; //直接返回结果 } @GetMapping("/getToken") public String getToken(){ return tokenService.createToken(); } }
这里就可以直接删除拦截器相关的类了IdempotenceInterceptor和WebMvcConfing
@Component @Aspect public class IdempotenceAspect { @Autowired TokenService tokenService; @Pointcut("@annotation(com.cicro.annotation.idempotence.Idempotence)") public void pointCut() { } @Before("pointCut()") public void before(JoinPoint joinPoint) { //拿到当前请求request HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); if (!tokenService.checkToken(request)){ throw new IdempotenceException("请求重复处理"); //没有检验通过直接抛出异常 } } }
public class IdempotenceException extends RuntimeException{
public IdempotenceException(String message) {
super(message);
}
}
@RestControllerAdvice
public class GlobalException {
@ExceptionHandler(IdempotenceException.class)
public String e(IdempotenceException e) {
return e.getMessage();
}
}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。