自学内容网 自学内容网

使用SpringBoot自定义注解+拦截器+token机制,实现接口的幂等性

1 整合springboot和redis环境的集成

2 配置请求的方法体和枚举类

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Response {
    private int status;
    private String msg;
    private Object data;
    // 省略get、set、toString、无参有参构造方法
}

///

import lombok.Getter;

@Getter
public enum ResponseCode {
    // 通用模块 1xxxx
    ILLEGAL_ARGUMENT(10000, "参数不合法"),
    REPETITIVE_OPERATION(10001, "请勿重复操作"),
    ;
    ResponseCode(Integer code, String msg) {
        this.code = code;
        this.msg = msg;
    }
    private Integer code;
    private String msg;
}

3 自定义异常以及配置全局异常类

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class ServiceException extends RuntimeException {
    private String code;
    private String msg;
    // 省略get、set、toString以及构造方法
}



import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

@ControllerAdvice
public class MyControllerAdvice {
    @ResponseBody
    @ExceptionHandler(ServiceException.class)
    public Response serviceExceptionHandler(ServiceException exception) {
        Response response = new Response(Integer.valueOf(exception.getCode()), exception.getMsg(), null);
        return response;
    }
}

4 编写创建Token和验证Token的接口以及实现类【核心类】

import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletRequest;

@Service
public interface TokenService {
    public Response createToken();
    
    public Response checkToken(HttpServletRequest request);
}

//

import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletRequest;
import java.util.UUID;

@Service
public class TokenServiceImpl implements TokenService {
    
    @Autowired
    private RedisTemplate redisTemplate;
    
    @Override
    public Response createToken() {
        // 生成uuid当作token
        String token = UUID.randomUUID().toString().replaceAll("-", "");
        // 将生成的token存入redis中
        redisTemplate.opsForValue().set(token, token);
        // 返回正确的结果信息
        Response response = new Response(0, token.toString(), null);
        return response;
    }
    
    @Override
    public Response checkToken(HttpServletRequest request) {
        // 从请求头中获取token
        String token = request.getHeader("token");
        if (StringUtils.isBlank(token)) {
            // 如果请求头token为空就从参数中获取
            token = request.getParameter("token");
            // 如果都为空抛出参数异常的错误
            if (StringUtils.isBlank(token)) {
                throw new ServiceException(ResponseCode.ILLEGAL_ARGUMENT.getCode().toString(), ResponseCode.ILLEGAL_ARGUMENT.getMsg());
            }
        }
        // 如果redis中不包含该token,说明token已经被删除了,抛出请求重复异常
        if (!redisTemplate.hasKey(token)) {
            throw new ServiceException(ResponseCode.REPETITIVE_OPERATION.getCode().toString(), ResponseCode.REPETITIVE_OPERATION.getMsg());
        }
        // 删除token
        Boolean del = redisTemplate.delete(token);
        // 如果删除不成功(已经被其他请求删除),抛出请求重复异常
        if (!del) {
            throw new ServiceException(ResponseCode.REPETITIVE_OPERATION.getCode().toString(), ResponseCode.REPETITIVE_OPERATION.getMsg());
        }
        return new Response(0, "校验成功", null);
    }
}

5 配置需要幂等的自定义注解

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 ApiIdempotent {
}

6 接口拦截器

public class ApiIdempotentInterceptor implements HandlerInterceptor {
    @Autowired
    private TokenService tokenService;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (!(handler instanceof HandlerMethod)) {
            return true;
        }
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        Method method = handlerMethod.getMethod();
        ApiIdempotent methodAnnotation = method.getAnnotation(ApiIdempotent.class);
        if (methodAnnotation != null) {
            // 校验通过放行,校验不通过全局异常捕获后输出返回结果
            tokenService.checkToken(request);
        }
        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 {
    }
}

7 配置拦截器

import org.springframework.context.annotation.Bean;
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 WebConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(apiIdempotentInterceptor());
    }

    @Bean
    public ApiIdempotentInterceptor apiIdempotentInterceptor() {
        return new ApiIdempotentInterceptor();
    }
}

8 写个controller测试

import com.example.demo_26.mindeng_3.domain.Food;
import com.example.demo_26.mindeng_3.mapper.FoodMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;

@RestController
@RequestMapping("/token")
public class TokenController {

    @Autowired
    private TokenService tokenService;

    @Autowired
    private FoodMapper tBookOrderMapper;

    @GetMapping
    public Response token() {
        return tokenService.createToken();
    }

    @GetMapping("/checktoken")
    @ApiIdempotent()
    public String demo(@RequestBody Food tBookOrder) {
        // 处理请求
        int insert = tBookOrderMapper.insert(tBookOrder);
        return insert==1?"ok":"wrong";
    }
}

9 测试

首先,通过调用/token接口生成一个token。
curl -X GET http://localhost:8080/token

然后,使用生成的token调用/token/checktoken接口进行验证。
curl -X POST http://localhost:8080/token/checktoken -H "token: generated-token"

如果再次使用相同的token调用,将会得到重复操作的错误:

这样,我们就通过token机制实现了接口的幂等性


原文地址:https://blog.csdn.net/qq_41712271/article/details/142719107

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!