解决幂等问题的Demo
上篇文章描述了幂等问题的解决方案,接下来,我们通过toke机制来解决接口的幂等问题。
1、Toke支持本地缓存和redis缓存,
首先根据配置决定生成哪个实现类。
package com.sinosoft.idempotence.config;import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.sinosoft.idempotence.service.impl.CacheToken;
import com.sinosoft.idempotence.service.impl.RedisToken;
import lombok.Data;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;@Configuration
@ConfigurationProperties(prefix = "idempotence")
@Data
public class TokenConfig {/*** 处理幂等性缓存方式 local/redis*/private String lockType = "local";@Bean@ConditionalOnProperty(name = "idempotence.type", havingValue = "local", matchIfMissing = true)public CacheToken getCacheToken() {Cache<String, Object> cache = Caffeine.newBuilder()// 初始的缓存空间大小.initialCapacity(100)// 缓存的最大条数.maximumSize(Integer.MAX_VALUE).build();return new CacheToken(cache);}@Bean@ConditionalOnProperty(name = "idempotence.type", havingValue = "redis")public RedisToken getRedisToken(RedisTemplate redisTemplate) {return new RedisToken(redisTemplate);}
}
定义接口
package com.sinosoft.idempotence.service;public interface CheckTokenService {/*** 判断token是否合法* @param token boolean* @return*/boolean checkToken(String token) throws Exception;/*** 获取token* @return token*/String getToken();
}
本地缓存和redis缓存实现定义的接口
package com.sinosoft.idempotence.service.impl;import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.sinosoft.idempotence.service.CheckTokenService;
import lombok.extern.slf4j.Slf4j;
import com.github.benmanes.caffeine.cache.Cache;import java.util.UUID;@Slf4j
public class CacheToken implements CheckTokenService {private Cache cache;public CacheToken(Cache cache) {this.cache = cache;}@Overridepublic boolean checkToken (String token) throws Exception{//判断传入的token是否为空if(StringUtils.isBlank(token)){throw new Exception("token为空,请先获取token");}Object o = cache.getIfPresent(token);//判断本地缓存中是否存传入的token,存在说明是第一次访问接口,不存在说明不是第一次if(o==null){throw new Exception("请不要重复请求接口!");}//判断成功删除tokencache.invalidate(token);return true;}@Overridepublic String getToken() {String token= UUID.randomUUID().toString();cache.put(token, token);log.info("获取token:{},保存本地缓存中。",token);return token;}
}
package com.sinosoft.idempotence.service.impl;import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.sinosoft.idempotence.service.CheckTokenService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;import java.util.UUID;@Slf4j
public class RedisToken implements CheckTokenService {private RedisTemplate redisTemplate;public RedisToken(RedisTemplate redisTemplate){this.redisTemplate = redisTemplate;}@Overridepublic boolean checkToken(String token) throws Exception{//判断传入的token是否为空if(StringUtils.isBlank(token)){throw new Exception("token为空,请先获取token");}ValueOperations valueOperations = redisTemplate.opsForValue();Object o = valueOperations.get(token);//判断redis中是否存传入的token,存在说明是第一次访问接口,不存在说明不是第一次if(o==null){throw new Exception("请不要重复请求接口!");}//判断成功删除tokenvalueOperations.getOperations().delete(token);return true;}@Overridepublic String getToken() {String token= UUID.randomUUID().toString();ValueOperations valueOperations = redisTemplate.opsForValue();valueOperations.set(token,token);log.info("获取token:{},保存redis中。",token);return token;}
}
2、自定义注解
因为我们的controller中有很多接口,并不是每个接口都需要幂等,在需要幂等的接口上增加该注解。
/*** 自定义幂等注解* @author lsh* @date 2022/3/9*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiIdempotent {}
3、定义拦截器
调用方法前,校验方法上是否有定义的注解,如果有注解校验toke是否生效。
package com.sinosoft.idempotence.interceptor;import com.sinosoft.idempotence.config.AutoIdempotent;
import com.sinosoft.idempotence.service.CheckTokenService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;/*** 幂等拦截器* @author lsh* @date 2022/3/24*/
@Component
public class AutoIdempotentInterceptor implements HandlerInterceptor {@Autowiredprivate CheckTokenService tokenService;@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {if (!(handler instanceof HandlerMethod)) {return true;}HandlerMethod handlerMethod = (HandlerMethod) handler;Method method = handlerMethod.getMethod();//被ApiIdempotment标记的扫描AutoIdempotent methodAnnotation = method.getAnnotation(AutoIdempotent.class);if (methodAnnotation != null) {try {// 幂等性校验, 校验通过则放行, 校验失败则抛出异常, 并通过统一异常处理返回友好提示return tokenService.checkToken(request.getHeader("token"));}catch (Exception ex){throw ex;}}//必须返回true,否则会被拦截一切请求return true;}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {}
}
3、配置拦截器生效
package com.sinosoft.idempotence.config;import com.sinosoft.idempotence.interceptor.AutoIdempotentInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;import javax.annotation.Resource;/*** 配置拦截器* @author lsh* @date 2022/3/24*/
@Configuration
public class WebConfiguration implements WebMvcConfigurer {@Resourceprivate AutoIdempotentInterceptor autoIdempotentInterceptor;/*** 添加拦截器* @param registry*/@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(autoIdempotentInterceptor);}
}
4、测试
首先在postMan调用获取Toke接口获取token

然后请求接口,接口上增加了我们自定义的@AutoIdempotent注解

因为是通过请求头获取toke,与前端约定好将toke的key是“token”,所以请求接口时在header增加参数,值是获取的token值。
第一次请求接口

第二次请求接口


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
