SpringBoot自定义注解 + AOP+分布式Redis 防止重复提交

第一步 引入依赖pom.xml:


        
            org.redisson
            redisson
            3.16.3 
        
        
            com.fasterxml.jackson.core
            jackson-databind
            2.11.1
        

        
            com.fasterxml.jackson.core
            jackson-core
            2.11.1
        

        
            com.fasterxml.jackson.core
            jackson-annotations
            2.11.1
        
    

第二步 增加配置:

  redis:
    host: localhost # Redis服务器地址
    database: 4 # Redis数据库索引(默认为0)
    port: 6379 # Redis服务器连接端口
    password: w23456
    timeout: 30000ms # 连接超时时间(毫秒)

第三步 增加自定义注解:

package com.hxnwm.ny.diancan.common.annotaiton;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @ClassName SubmitLimit
 * @Description TODO
 * @Author wdj
 * @Date 2023/7/25 18:01
 * @Version
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
public @interface SubmitLimit {

    /**
     * 指定时间内不可重复提交(仅相对上一次发起请求时间差),单位毫秒
     * @return
     */
    int waitTime() default 1000;

    /**
     * 指定请求头部key,可以组合生成签名
     * @return
     */
    String[] customerHeaders() default {};


    /**
     * 自定义重复提交提示语
     * @return
     */
    String customerTipMsg() default "";

}
package com.hxnwm.ny.diancan.common.aspect;

import com.hxnwm.ny.diancan.common.annotaiton.SubmitLimit;
import com.hxnwm.ny.diancan.common.lock.DistributedLockService;
import com.hxnwm.ny.diancan.common.result.Result;
import com.hxnwm.ny.diancan.common.utils.JacksonUtils;
import org.apache.commons.codec.digest.DigestUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.bind.BindResult;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.ui.Model;
import org.springframework.ui.ModelMap;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.ExtendedServletRequestDataBinder;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.Objects;
/**
 * @ClassName SubmitLimitAspect
 * @Description TODO
 * @Author wdj
 * @Date 2023/7/27 9:07
 * @Version
 */
@Order(1)
@Aspect
@Component
public class SubmitLimitAspect {

    private static final Logger LOGGER = LoggerFactory.getLogger(SubmitLimitAspect.class);
    /**
     * redis分割符
     */
    private static final String REDIS_SEPARATOR = ":";
    /**
     * 默认重复提交提示语
     */
    private static final String DEFAULT_TIP_MSG = "服务正在处理,请勿重复提交!";

    @Autowired
    private DistributedLockService distributedLockService;


    /**
     * 方法调用环绕拦截
     */
    @Around(value = "@annotation(com.hxnwm.ny.diancan.common.annotaiton.SubmitLimit)")
    public Object doAround(ProceedingJoinPoint joinPoint){
        HttpServletRequest request = getHttpServletRequest();
        if(Objects.isNull(request)){
            return Result.error(5001,"请求参数不能为空!");
        }
        //获取注解配置的参数
        SubmitLimit submitLimit = getSubmitLimit(joinPoint);
        //组合生成key,通过key实现加锁和解锁
        String lockKey = buildSubmitLimitKey(joinPoint, request, submitLimit.customerHeaders());
        //尝试在指定的时间内加锁
        boolean lock = distributedLockService.acquireLock(lockKey, submitLimit.waitTime());
        if(!lock){
            String tipMsg = StringUtils.isEmpty(submitLimit.customerTipMsg()) ? DEFAULT_TIP_MSG : submitLimit.customerTipMsg();
            return Result.error(5001,tipMsg);
        }
        try {
            //继续执行后续流程
            return execute(joinPoint);
        } finally {
            //执行完毕之后,手动将锁释放
            distributedLockService.releaseLock();
        }
    }

    /**
     * 执行任务
     * @param joinPoint
     * @return
     */
    private Object execute(ProceedingJoinPoint joinPoint){
        try {
          return joinPoint.proceed();
        } catch (Exception e) {
            return Result.error(5001,e.getMessage());
        } catch (Throwable e) {
            LOGGER.error("业务处理发生异常,错误信息:",e);
            return Result.error(5001,"业务处理发生异常");
        }
    }


    /**
     * 获取请求对象
     * @return
     */
    private HttpServletRequest getHttpServletRequest(){
        RequestAttributes ra = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes sra = (ServletRequestAttributes)ra;
        HttpServletRequest request = sra.getRequest();
        return request;
    }

    /**
     * 获取注解值
     * @param joinPoint
     * @return
     */
    private SubmitLimit getSubmitLimit(JoinPoint joinPoint){
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        SubmitLimit submitLimit = method.getAnnotation(SubmitLimit.class);
        return submitLimit;
    }

    /**
     * 组合生成lockKey
     * 生成规则:接口名+方法名+请求参数签名(对请求头部参数+请求body参数,取SHA1值)
     * @param joinPoint
     * @param request
     * @param customerHeaders
     * @return
     */
    private String buildSubmitLimitKey(JoinPoint joinPoint, HttpServletRequest request, String[] customerHeaders){
        //请求参数=请求头部+请求body
        String requestHeader = getRequestHeader(request, customerHeaders);
        String requestBody = getRequestBody(joinPoint.getArgs());
        String requestParamSign = DigestUtils.sha1Hex(requestHeader + requestBody);
        String submitLimitKey = new StringBuilder()
                .append(joinPoint.getSignature().getDeclaringType().getSimpleName())
                .append(REDIS_SEPARATOR)
                .append(joinPoint.getSignature().getName())
                .append(REDIS_SEPARATOR)
                .append(requestParamSign)
                .toString();
        return submitLimitKey;
    }


    /**
     * 获取指定请求头部参数
     * @param request
     * @param customerHeaders
     * @return
     */
    private String getRequestHeader(HttpServletRequest request, String[] customerHeaders){
        if (Objects.isNull(customerHeaders)) {
            return "";
        }
        StringBuilder sb = new StringBuilder();
        for (String headerKey : customerHeaders) {
            sb.append(request.getHeader(headerKey));
        }
        return sb.toString();
    }


    /**
     * 获取请求body参数
     * @param args
     * @return
     */
    private String getRequestBody(Object[] args){
        if (Objects.isNull(args)) {
            return "";
        }
        StringBuilder sb = new StringBuilder();
        for (Object arg : args) {
            if (arg instanceof HttpServletRequest
                    || arg instanceof HttpServletResponse
                    || arg instanceof MultipartFile
                    || arg instanceof BindResult
                    || arg instanceof MultipartFile[]
                    || arg instanceof ModelMap
                    || arg instanceof Model
                    || arg instanceof ExtendedServletRequestDataBinder
                    || arg instanceof byte[]) {
                continue;
            }
            sb.append(JacksonUtils.toJson(arg));
        }
        return sb.toString();
    }
}

第四步 增加分布式服务:

package com.hxnwm.ny.diancan.common.lock;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

@Component
public class DistributedLockService {
    // 锁的过期时间,单位秒
    private static final int LOCK_EXPIRE = 30;

    @Autowired
    private RedissonClient redissonClient;
    private RLock lock;

    public boolean acquireLock(String lockKey,int lockExpire) {
        if (lockExpire<=0){lockExpire=LOCK_EXPIRE;}
        try {
             lock= redissonClient.getLock(lockKey);
            // 尝试获取锁,设置锁的自动过期时间为 LOCK_EXPIRE 秒
            boolean tryLock = lock.tryLock(lockExpire, TimeUnit.SECONDS);
            return tryLock;
        } catch (Exception e) {
            e.printStackTrace();
            Thread.currentThread().interrupt();
            return false;
        }
    }

    public void releaseLock() {
        lock.unlock();
    }
}


最后一步:使用。在控制层增加@SubmitLimit(customerHeaders = {"token"}, customerTipMsg = "正在加紧为您处理,请勿重复下单!")

 在不改变原有逻辑上增加额外的功能

    /**
     * 下单详情
     *
     * @author knight
     * @time 2022/3/22 13:51
     */
    @SubmitLimit(customerHeaders = {"token"}, customerTipMsg = "正在加紧为您处理,请勿重复下单!")
    @RequestMapping(value = "submit/detail", method = RequestMethod.POST)
    public Result submitDetail(@Validated @RequestBody CartOrderInfo cartOrderInfo) {
        return Result.handlerData(this.orderManage.submitDetail(cartOrderInfo));
    }

你可能感兴趣的:(springboot,spring,boot,分布式,redis)