文章目录
一. 什么是幂等性二. 基于拦截器实现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
;
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;
}
public boolean exists(String key
) {
return redisTemplate
.hasKey(key
);
}
public boolean remove(String key
) {
if (exists(key
)) {
return redisTemplate
.delete(key
);
}
return false;
}
}
2.5 检验token服务层
@Component
public class TokenService {
@Autowired
RedisService redisService
;
public String
createToken() {
String token
= UUID
.randomUUID().toString().replaceAll("-", "");
if (redisService
.setExp(token
, token
, 1000L
)) {
return token
;
}
return null
;
}
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 自定义拦截器
@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
) {
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 测试