🎈博客主页:🌈我的主页🌈
🎈欢迎点赞 👍 收藏 🌟留言 📝 欢迎讨论!👏
🎈本文由 【泠青沼~】 原创,首发于 优快云🚩🚩🚩
🎈由于博主是在学小白一枚,难免会有错误,有任何问题欢迎评论区留言指出,感激不尽!🌠个人主页
目录
🌟 一、接口调用出现的问题
现如今我们的系统大多拆分为分布式SOA,或者微服务,一套系统中包含了多个子系统服务,而一个子系统服务往往会去调用另一个服务,而服务调用服务无非就是使用RPC通信或者restful,既然是通信,那么就有可能在服务器处理完毕后返回结果的时候挂掉,这个时候用户端发现很久没有反应,那么就会多次点击按钮,这样请求有多次,那么处理数据的结果必须要统一
🌟 二、接口幂等性
接口幂等性就是用户对于同一操作发起的一次请求或者多次请求的结果是一致的,不会因为多次点击而产生了副作用。举个最简单的例子,支付操作,用户购买商品后支付,支付扣款成功,但是返回结果的时候网络异常,此时钱已经扣了,用户再次点击按钮,此时会进行第二次扣款,返回结果成功,用户查询余额返发现多扣钱了,流水记录也变成了两条,这就是没能保证接口的幂等性
🌟 三、实现token机制
-
生成全局唯一的token,token放到redis内存,token会在页面跳转时获取,请求提交先获取token,请求携带token才会执行
-
提交后后台校验token,执行提交逻辑,提交成功同时删除token,生成新的token更新redis ,这样当第一次提交后token更新了,页面再次提交携带的token是已删除的token后台验证会失败不让提交
🌟 四、通过拦截器实现token机制
🌟🌟 4.1、配置文件
🌟🌟🌟 4.1.1、POM.xml
<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>
🌟🌟🌟 4.1.1、application.properties
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.password=
🌟🌟 4.2、事务处理
🌟🌟🌟 4.2.1、Redis事务处理
- Redis键值对添加操作
//将Redis操作注入
@Autowired
StringRedisTemplate template;
public Boolean setEx(String key,String value,Long expireTime){
boolean result = false;
try {
ValueOperations<String, String> ops = template.opsForValue();
//存入键值对
ops.set(key,value);
//给此键值对一个过期时间,级别为秒级
ops.getAndExpire(key,expireTime, TimeUnit.SECONDS);
result = true;
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
当写入token的键值对时,长时间不使用,就会失效
- Redis判断键值对是否存在
public Boolean exist(String key){
Boolean flag = template.hasKey(key);
return flag;
}
- Redis删除键值对
public Boolean remove(String key){
//如果键值对存在,便删除键值对
if(exist(key)){
return template.delete(key);
}
return false;
}
🌟🌟🌟 4.2.1、token事务处理
- 创建token
@Autowired
RedisService redisService;
public String createtoken(){
String uuid = UUID.randomUUID().toString();
//加入到redis中
redisService.setEx(uuid,uuid,10000L);
return uuid;
}
- 检查token
public boolean checkToken(HttpServletRequest request) throws IdempotentException {
//从请求头中拿到token
String token = request.getHeader("token");
//如果请求头中没有拿到token
if(StringUtils.isEmpty(token)){
//再从请求体中拿
token = request.getParameter("token");
//如果请求体中不存在token,token就不存在
if(StringUtils.isEmpty(token)){
throw new IdempotentException("token不存在");
}
}
//token与redis中的相同就说明重复操作,抛出异常
if(!redisService.exist(token)){
throw new IdempotentException("重复操作");
}
//拿到token后,直接删除token
Boolean bo = redisService.remove(token);
//如果删除失败,就说明redis中没有token,已经被使用过了
if(!bo){
throw new IdempotentException("重复操作");
}
return true;
}
🌟🌟 4.3、自定义注解
@Target(ElementType.METHOD)//加载方法上
@Retention(RetentionPolicy.RUNTIME)//作用在运行时
public @interface AutoIdempotent {
}
//注解解析(拦截器方法)
拦截器拦截有@AutoIdempotent注解的方法,进行幂等性处理,其他方法正常放行
🌟🌟 4.4、自定义拦截器
@Component
public class IdempotentInterceptor implements HandlerInterceptor {
@Autowired
tokenService tokenService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//如果他是被拦截的实例就不会执行此方法,如果不是就直接return
if(!(handler instanceof HandlerMethod)){
return true;
}
Method method = ((HandlerMethod) handler).getMethod();
AutoIdempotent annotation = method.getAnnotation(AutoIdempotent.class);
if(annotation != null){
//如果存在此接口就需要进行幂等性的处理
try {
boolean b = tokenService.checkToken(request);
return b;
} catch (IdempotentException e) {
throw e;
}
}
return true;
}
}
🌟🌟 4.5、异常处理
🌟🌟🌟 4.5.1自定义异常
public class IdempotentException extends Exception{
public IdempotentException(String message) {
super(message);
}
}
🌟🌟🌟 4.5.2、定义全局异常
@RestControllerAdvice
public class GlobalException {
@ExceptionHandler(IdempotentException.class)
public String IdempotentException(IdempotentException e){
return e.getMessage();
}
}
🌟🌟 4.6、WebMvcConfig配置
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Autowired
IdempotentInterceptor idempotentInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(idempotentInterceptor).addPathPatterns("/**");
}
}
🌟🌟 4.7、Controller测试
@RestController
public class HelloController {
@Autowired
tokenService tokenService;
@GetMapping("/gettoken")
public String gettoken(){
return tokenService.createtoken();
}
//进行幂等性处理
@AutoIdempotent
@PostMapping("/hello")
public String gethello(){
return "hello";
}
//不进行幂等性处理
@PostMapping("/hello2")
public String gethello2(){
return "hello2";
}
}
🌟🌟 4.8、验证
-
拿到token
-
携带token请求
-
再次携带token请求
-
不进行幂等性处理请求