天天看点

spring 接口幂等性和解决方法_Spring Boot接口幂等性封装

Spring Boot接口幂等性封装

封装思路

接口幂等性后端的处理方式,就是通过redis来验证表单提交时申请的token有效性。因此,我们可以利用Spring Boot的自动装配特性,针对此功能封装一个可用的starter。本文仅提供实现的思路和核心代码供大家参考。

配置文件

我们针对前段能够配置一些属性进行参数封装,如:可以配置Token有效时间、可以配置请求头中的key,以及是否启用等信息。

在本文中,我针对模块的启用信息、请求头Token的key以及Token过期时间封装成IdempotentProperties类

@Data

@ConfigurationProperties(prefix = "boot.idempotent")

public class IdempotentProperties {

private boolean enable;

private String storeTokenKey = "IDEMPOTENT-TOKEN";

private int expireTime = 5;

}

核心实现思路

针对幂等性的思路,我们可以自定义一个注解,利用Spring的AOP特性,对标注注解的方法进行增强,然后判断改请求头中的Token是否有效,来确保接口的幂等性。同时,自动装配置只需要做两件事情,

1、把AOP的切点配置进去。

2、暴露外部申请接口请求Token的接口

自定义注解

@Idempotent

@Target({ElementType.METHOD})

@Retention(RetentionPolicy.RUNTIME)

public @interface Idempotent {

boolean enable() default true;

}

幂等性核心service

IdempotentService

@AllArgsConstructor

public class IdempotentService {

private final IdempotentProperties properties;

private final RedisTemplate redisTemplate;

public String createToken() {

String token = UUID.fastUUID().toString();

redisTemplate.opsForValue().set(token, token, properties.getExpireTime(), TimeUnit.MINUTES);

return token;

}

public void checkToken(HttpServletRequest request) {

String token = request.getHeader(properties.getStoreTokenKey());

if (StrUtil.isBlank(token)) {

token = request.getParameter(properties.getStoreTokenKey());

if (StrUtil.isBlank(token)) {

throw new IdempotentException("非法提交");

}

}

Object tokenVal = redisTemplate.opsForValue().get(token);

if (Objects.isNull(tokenVal)) {

throw new IdempotentException("禁止重复提交!");

}

Boolean del = redisTemplate.delete(token);

if (!del) {

throw new IdempotentException("禁止重复提交!");

}

}

}

幂等性方法拦截器

IdempotentMethodInterceptor

public class IdempotentMethodInterceptor implements MethodInterceptor {

private IdempotentService idempotentService;

public IdempotentMethodInterceptor(IdempotentService idempotentService) {

this.idempotentService = idempotentService;

}

@Override

public Object invoke(MethodInvocation invocation) throws Throwable {

//获取幂等性注解对象

Idempotent idempotent = invocation.getMethod().getAnnotation(Idempotent.class);

//幂等性未启用

if (!idempotent.enable()) {

return invocation.proceed();

}

ServletRequestAttributes attributes = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes());

// 非Web环境

if (Objects.isNull(attributes)) {

return invocation.proceed();

}

HttpServletRequest request = attributes.getRequest();

idempotentService.checkToken(request);

return invocation.proceed();

}

}

自定义幂等性异常

IdempotentException

public class IdempotentException extends RuntimeException {

public IdempotentException() {

}

public IdempotentException(String message) {

super(message);

}

public IdempotentException(Throwable cause) {

super(cause);

}

public IdempotentException(String message, Throwable cause) {

super(message, cause);

}

public IdempotentException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {

super(message, cause, enableSuppression, writableStackTrace);

}

}

自动装配类

自动装配类IdempotentAutoConfiguration如下:

@Configuration

@EnableConfigurationProperties(IdempotentProperties.class)

@ConditionalOnProperty(prefix = "boot.idempotent", name = "enable", havingValue = "true", matchIfMissing = true)

@RestController

public class IdempotentAutoConfiguration {

private static final String REPEAT_SUBMIT_POINT_CUT = "@annotation(com.xx.common.idempotent.annotation.Idempotent)";

@Autowired

private IdempotentProperties idempotentProperties;

@Autowired

private RedisTemplate redisTemplate;

@Autowired

private IdempotentService idempotentService;

@Bean

public IdempotentService idempotentService() {

return new IdempotentService(idempotentProperties, redisTemplate);

}

@Bean

public DefaultPointcutAdvisor repeatSubmitPointCutAdvice() {

//声明一个AspectJ切点

AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();

//设置切点表达式

pointcut.setExpression(REPEAT_SUBMIT_POINT_CUT);

// 配置增强类advisor, 切面=切点+增强

DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor();

//设置切点

advisor.setPointcut(pointcut);

//设置增强(Advice)

advisor.setAdvice(new IdempotentMethodInterceptor(idempotentService));

//设置增强拦截器执行顺序

// FIXME 所有的AOP顺序需要统一管理,否则会顺序错乱会导致功能异常

advisor.setOrder(600);

return advisor;

}

@GetMapping("api/idempotent/token")

public Result generationToken() {

return Res.ok().data(idempotentService.createToken());

}

}