SpringBoot学习之使用拦截器和Redis实现接口幂等性

接口幂等性

什么是接口幂等性:接口幂等性就是用户对于同一操作发起的一次请求或者多次请求的结果是一致的,不会因为多次点击而产生了副作用。例如:用户购买商品后支付,支付扣款成功,但是返回结果的时候网络异常,此时钱已经扣了,用户再次点击按钮,此时会进行第二次扣款,返回结果成功,用户查询余额返发现多扣钱了,流水记录也变成了两条,这就是没有保证接口的幂等性。

导入依赖

<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";
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值