天天看點

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());

}

}