栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

SpringBoot项目幂等性问题解决方案实现

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

SpringBoot项目幂等性问题解决方案实现

大体思路:

前端请求头存放幂等性ID,自定义注解ApiIdempotent,在需要幂等性controller访问前拦截获取幂等性ID作为Redis的Key,值为N(正在执行),设置一定的过期时间。

controller执行后拦截设置Redis中key幂等性ID的值为Y(已执行完)

代码:

1. 自定义幂等性注解,在需要幂等性校验的controller方法上添加该注解

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

2. 访问前拦截检验幂等性Id

import com.sf.cg.common.Code.ResponseCode;
import com.sf.cg.common.constant.CommonConstant;
import com.sf.cg.common.exception.ServiceException;
import com.sf.cg.model.common.baseRequest;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdviceAdapter;

import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.lang.reflect.Type;



@RestControllerAdvice(annotations = {RestController.class, Controller.class})
public class baseRequestValidationAdvice extends RequestBodyAdviceAdapter {

    @Resource
    private RedisService redisService;

    @Override
    public boolean supports(MethodParameter methodParameter, Type targetType, Class> converterType) {
        return true;
    }

    @Override
    public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType,
                                Class> converterType) {
        baseRequest request = (baseRequest) body;
        // ...参数检验、token检验等等
        // 幂等性校验
        checkIdempotent(inputMessage, parameter);
        return body;
    }

    
    private void checkIdempotent(HttpInputMessage inputMessage, MethodParameter parameter){
        // 幂等性校验, 校验通过则放行, 校验失败则抛出异常, 并通过统一异常处理返回友好提示
        Method method = parameter.getMethod();
        assert method != null;
        ApiIdempotent apiIdempotent = method.getAnnotation(ApiIdempotent.class);
        if (apiIdempotent != null) {
            // 请求头中获得tractionId幂等性Id, 前端保证唯一
            if(inputMessage.getHeaders().containsKey(CommonConstant.TRACTION_ID)){
                // 检验幂等性
                String tractionId = inputMessage.getHeaders().get(CommonConstant.TRACTION_ID).get(0);
                checkTractionId(tractionId);
            }else{
                throw new ServiceException(ResponseCode.ILLEGAL_ARGUMENT.getMsg());
            }
        }
    }

    
    private void checkTractionId(String tractionId){
        if (StringUtils.isBlank(tractionId)) {
            throw new ServiceException(ResponseCode.ILLEGAL_ARGUMENT.getMsg());
        }
        // 判断Redis中是否存在,加锁看情况
        String key = "traction:" + tractionId;
        String value = redisService.getString(key);
        if (StringUtils.isNotBlank(value)) {
            if(CommonConstant.STATUS_Y.equals(value)){
                // 已经执行
                throw new ServiceException(CommonConstant.SUCCESS, "success");
            } else{
                // 请求太频繁, 请稍后再试
                throw new ServiceException(ResponseCode.ACCESS_LIMIT.getMsg());
            }
        } else{
            // 存到redis,N表示正在执行
            redisService.setString(key, CommonConstant.STATUS_N, 10L);
        }
    }
}

3. 访问后修改幂等性Id

import com.sf.boot.base.vo.Result;
import com.sf.cg.common.constant.CommonConstant;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

import javax.annotation.Resource;
import java.lang.reflect.Method;


@RestControllerAdvice(annotations = {RestController.class, Controller.class})
public class WebControllerAdvice implements ResponseBodyAdvice {

    private Logger logger = LoggerFactory.getLogger(getClass());

    @Resource
    private RedisService redisService;

    
    @ResponseBody
    @ExceptionHandler(Exception.class)
    public Result exceptionHandler(Exception e) {
        return Result.DefaultFailure(e.getMessage());
    }

    
    @Override
    public boolean supports(MethodParameter methodParameter, Class> aClass) {
        Method method = methodParameter.getMethod();
        assert method != null;
        return method.isAnnotationPresent(ApiIdempotent.class);
    }

    
    @Override
    public Result beforeBodyWrite(Result restResult, MethodParameter methodParameter, MediaType mediaType, Class> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
        String tractionId = "";
        if(serverHttpRequest.getHeaders().containsKey(CommonConstant.TRACTION_ID)) {
            tractionId = serverHttpRequest.getHeaders().get(CommonConstant.TRACTION_ID).get(0);
        }
        String key = "traction:" + tractionId;
        if(CommonConstant.SUCCESS.equals(restResult.getCode())){
            // 更新key的值为Y,表示已经执行完
            int result = redisService.setString(key, CommonConstant.STATUS_Y);
            logger.info("request success update token, result:{}", result);
        } else {
            // 接口执行错误,删除key
            int result = redisService.del(key);
            logger.info("request error delete token, result:{}", result);
        }
        return restResult;
    }

}

4. 全局返回异常Code和错误消息

public enum ResponseCode {

    ILLEGAL_ARGUMENT(10000, "参数不合法, 缺少tractionId"),
    ACCESS_LIMIT(10002, "请求太频繁, 请稍后再试"),;

    ResponseCode(Integer code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    private Integer code;

    private String msg;

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }
}

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/673019.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号