接口幂等性
什么是接口幂等性:接口幂等性就是用户对于同一操作发起的一次请求或者多次请求的结果是一致的,不会因为多次点击而产生了副作用。例如:用户购买商品后支付,支付扣款成功,但是返回结果的时候网络异常,此时钱已经扣了,用户再次点击按钮,此时会进行第二次扣款,返回结果成功,用户查询余额返发现多扣钱了,流水记录也变成了两条,这就是没有保证接口的幂等性。
导入依赖
<dependencies>
<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-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
配置application.properties文件
spring.redis.host=192.168.65.128
spring.redis.port=6379
spring.redis.password=hx
在token包下创建RedisService类
package org.hx.springboot_redismidenxin_demo36.token;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
public class RedisService {
@Autowired
StringRedisTemplate stringRedisTemplate;
public boolean setEx(String key,String value,Long expireTime){
boolean result = false;
try {
ValueOperations<String,String> ops = stringRedisTemplate.opsForValue();
ops.set(key,value);
stringRedisTemplate.expire(key,expireTime, TimeUnit.SECONDS);
result = true;
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
public boolean exists(String key){
return stringRedisTemplate.hasKey(key);
}
public boolean remove(String key){
if(exists(key)){
return stringRedisTemplate.delete(key);
}
return false;
}
}
在token包下创建TokenService类
package org.hx.springboot_redismidenxin_demo36.token;
import org.hx.springboot_redismidenxin_demo36.exception.idempotentException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import javax.servlet.http.HttpServletRequest;
import java.util.UUID;
@Service
public class TokenService {
@Autowired
RedisService redisService;
public String createToken(){
String uuid = UUID.randomUUID().toString();
redisService.setEx(uuid,uuid,10000L);
return uuid;
}
public boolean checkToken(HttpServletRequest request) throws idempotentException {
String token = request.getHeader("token");
if(StringUtils.isEmpty(token)){
token = request.getParameter("token");
if(StringUtils.isEmpty(token)){
throw new idempotentException("token 不存在");
}
}
if(!redisService.exists(token)){
throw new idempotentException("重复操作");
}
boolean remove = redisService.remove(token);
if(!remove){
throw new idempotentException("重复操作");
}
return true;
}
}
在annotation包下创建AutoIdempotent类
package org.hx.springboot_redismidenxin_demo36.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoIdempotent {
}
在exception包下创建idempotentException
package org.hx.springboot_redismidenxin_demo36.exception;
public class idempotentException extends Exception{
public idempotentException(String message) {
super(message);
}
}
在exception包下创建GlobalException
package org.hx.springboot_redismidenxin_demo36.exception;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class GlobalException {
@ExceptionHandler(idempotentException.class)
public String idempotentException(idempotentException e){
return e.getMessage();
}
}
在config包下创建WebMvcConfig类
package org.hx.springboot_redismidenxin_demo36.config;
import org.hx.springboot_redismidenxin_demo36.interceptor.idempotenInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Autowired
idempotenInterceptor interceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(interceptor).addPathPatterns("/**");
}
}
在interceptor包下创建idempotenInterceptor类
package org.hx.springboot_redismidenxin_demo36.interceptor;
import org.hx.springboot_redismidenxin_demo36.annotation.AutoIdempotent;
import org.hx.springboot_redismidenxin_demo36.exception.idempotentException;
import org.hx.springboot_redismidenxin_demo36.token.TokenService;
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 javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
@Component
public class idempotenInterceptor 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();
AutoIdempotent idempotent = method.getAnnotation(AutoIdempotent.class);
if (idempotent != null){
try {
return tokenService.checkToken(request);
} catch (idempotentException e) {
throw e;
}
}
return true;
}
}
在Controller包下创建Controller类
package org.hx.springboot_redismidenxin_demo36.Controller;
import org.hx.springboot_redismidenxin_demo36.annotation.AutoIdempotent;
import org.hx.springboot_redismidenxin_demo36.token.TokenService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class Controller {
@Autowired
TokenService tokenService;
@GetMapping("/gettoken")
public String getToken(){
return tokenService.createToken();
}
@PostMapping("/hello")
@AutoIdempotent
public String hello(){
return "hello";
}
@PostMapping("/hello2")
public String hello2(){
return "hello2";
}
}