自定义注解解决接口幂等性问题

tech2023-07-24  122

文章目录

一. 什么是幂等性二. 基于拦截器实现2.1 实现思路2.2 引入依赖2.3 配置redis2.4 封装redis工具类2.5 检验token服务层2.6 自定义注解2.7 自定义拦截器2.8 配置拦截器2.9测试 三. 基于aop实现3.1 定义切面3.2 自定义异常类3.3 定义全局异常处理类3.4 测试

一. 什么是幂等性

简单的说就是对于同一笔业务操作,不管调用多少次,得到的结果都是一样的。 例如:在一次用户的支付订单的操作中,第一次支付时因一些原因显示网络异常,但是后台实际已经扣款了,那么用户支付第二次时,就会给用户显示扣款成功.

实现接口幂等性的设计方案可以有很多种,今天我们就用自定义注解的方式来解决

二. 基于拦截器实现

2.1 实现思路
自定义注解,在每个需要控制幂等性的接口上加上此注解后台提供生成token的接口供前台调用,并在生成时将之存入redis中前台每次请求接口时,必须带有从后台拿到的token,每个token只能用一次后台在处理前台的一次请求后,将携带的token在redis中删除自定义拦截器,对前台的每次请求进行token校验(校验redis中是否有此token),校验成功则处理逻辑,校验失败则不进行任何处理(返回错误信息提示前台)
2.2 引入依赖
<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>
2.3 配置redis
spring.redis.port=6300 spring.redis.database=4 spring.redis.host=10.16.64.30 spring.redis.password=123456
2.4 封装redis工具类
@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; } }
2.5 检验token服务层
@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; } }
2.6 自定义注解
@Target(ElementType.METHOD) //注解作用于方法上 @Retention(RetentionPolicy.RUNTIME) //保留到运行时 public @interface Idempotence { }
2.7 自定义拦截器
/* * @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 { } }
2.8 配置拦截器
@Configuration public class WebMvcConfing implements WebMvcConfigurer { @Autowired IdempotenceInterceptor interceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(interceptor).addPathPatterns("/**"); } }
2.9测试
@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(); } }

三. 基于aop实现

这里就可以直接删除拦截器相关的类了IdempotenceInterceptor和WebMvcConfing

3.1 定义切面
@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("请求重复处理"); //没有检验通过直接抛出异常 } } }
3.2 自定义异常类
public class IdempotenceException extends RuntimeException{ public IdempotenceException(String message) { super(message); } }
3.3 定义全局异常处理类
@RestControllerAdvice public class GlobalException { @ExceptionHandler(IdempotenceException.class) public String e(IdempotenceException e) { return e.getMessage(); } }
3.4 测试
最新回复(0)