All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.nxyfan.framework.common.idempotent.RepeatSubmitAspect Maven / Gradle / Ivy

package com.nxyfan.framework.common.idempotent;

import cn.dev33.satoken.SaManager;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindingResult;
import org.springframework.web.multipart.MultipartFile;
import com.alibaba.fastjson.JSON;
import com.nxyfan.framework.common.annotation.CommonRepeatSubmit;
import com.nxyfan.framework.common.cache.CommonCacheOperator;
import com.nxyfan.framework.common.constant.CommonConstant;
import com.nxyfan.framework.common.exception.CommonException;
import com.nxyfan.framework.common.pojo.CommonResult;
import com.nxyfan.framework.common.util.CommonIpAddressUtil;
import com.nxyfan.framework.common.util.CommonServletUtil;
import java.util.Collection;
import java.util.Map;
import java.util.StringJoiner;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/** 
 *
 * Describe: 防止重复提交(参考美团GTIS防重系统)
 * Author: Administrator  
 * Create Time: 2024年4月27日 下午3:05:23 
 * Copyright @ 2024 51LIFE  
 */
@Aspect
@Component
public class RepeatSubmitAspect {

    private static final ThreadLocal KEY_CACHE = new ThreadLocal<>();
    @Resource
    private CommonCacheOperator commonCacheOperator;
    
    @Before("@annotation(com.nxyfan.framework.common.annotation.CommonRepeatSubmit)")
    public void doBefore(JoinPoint point, CommonRepeatSubmit repeatSubmit) throws Throwable {
        // 如果注解不为0 则使用注解数值
        long interval = repeatSubmit.timeUnit().toMillis(repeatSubmit.interval());
        if(interval < 1000) {
            throw CommonException.bizException("重复提交间隔时间不能小于'1'秒");
        }
        // 请求对象
        HttpServletRequest request = CommonServletUtil.getRequest();
        // 请求ip
        String ip = CommonIpAddressUtil.getIp(request);
        // 请求参数
        String nowParams = argsArrayToString(point.getArgs());
        // 请求地址(作为存放cache的key值)
        String url = request.getRequestURI();
        // 唯一值(没有消息头则使用请求地址)
        String tokenName = StringUtils.trimToEmpty(request.getHeader(SaManager.getConfig().getTokenName()));
        String submitKey = "";
        if(StrUtil.isNotBlank(tokenName)) {
        	submitKey = tokenName + ":" + ip + ":" + url + ":" + nowParams;
        }else {
        	submitKey = ip + ":" + url + ":" + nowParams;
        }
        // 唯一标识(指定key + url + 消息头)
        String cacheRepeatKey = CommonConstant.REPEAT_SUBMIT_KEY_PREFIX + SecureUtil.md5(submitKey);
        // 缓存判断
        Object cacheRepeatValue = this.commonCacheOperator.get(cacheRepeatKey);
        if(ObjectUtil.isEmpty(cacheRepeatValue)) {
        	this.commonCacheOperator.put(cacheRepeatKey, submitKey, interval / 1000);
        	KEY_CACHE.set(cacheRepeatKey);
        }else {
        	String message = "不允许重复提交,请稍候再试!";
            Object configMessage = this.commonCacheOperator.get(CommonConstant.REDIS_CONFIG_CACHE_KEY + CommonConstant.CONFIG_KEY_SYS_REPEAT_SUBMIT_MESSAGE);
            if(ObjectUtil.isNotEmpty(configMessage)) {
            	message = Convert.toStr(configMessage);
            }
            throw CommonException.bizException(message);
        }
    }

    /**
     * 处理完请求后执行
     *
     * @param joinPoint 切点
     */
    @AfterReturning(pointcut = "@annotation(com.nxyfan.framework.common.annotation.CommonRepeatSubmit)", returning = "jsonResult")
    public void doAfterReturning(JoinPoint joinPoint, CommonRepeatSubmit repeatSubmit, Object jsonResult) {
        if(jsonResult instanceof CommonResult) {
            try {
                // 成功则不删除redis数据 保证在有效时间内无法重复提交
            	CommonResult r = (CommonResult) jsonResult;
                if(r.getRspCode() == 100) {
                    return;
                }
                this.commonCacheOperator.remove(KEY_CACHE.get());
            }finally {
                KEY_CACHE.remove();
            }
        }
    }

    /**
     * 拦截异常操作
     *
     * @param joinPoint 切点
     * @param e         异常
     */
    @AfterThrowing(value = "@annotation(com.nxyfan.framework.common.annotation.CommonRepeatSubmit)", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, CommonRepeatSubmit repeatSubmit, Exception e) {
    	this.commonCacheOperator.remove(KEY_CACHE.get());
        KEY_CACHE.remove();
    }

    /**
     * 参数拼装
     */
    private String argsArrayToString(Object[] paramsArray) {
        StringJoiner params = new StringJoiner(" ");
        if(ArrayUtil.isEmpty(paramsArray)) {
            return params.toString();
        }
        for(Object o : paramsArray) {
            if(ObjectUtil.isNotNull(o) && !isFilterObject(o)) {
                params.add(JSON.toJSONString(o));
            }
        }
        return params.toString();
    }

    /**
     * 判断是否需要过滤的对象。
     *
     * @param o 对象信息。
     * @return 如果是需要过滤的对象,则返回true;否则返回false。
     */
    @SuppressWarnings("rawtypes")
    public boolean isFilterObject(final Object o) {
        Class clazz = o.getClass();
        if(clazz.isArray()) {
            return clazz.getComponentType().isAssignableFrom(MultipartFile.class);
        }else if (Collection.class.isAssignableFrom(clazz)) {
            Collection collection = (Collection) o;
            for(Object value : collection) {
                return value instanceof MultipartFile;
            }
        }else if (Map.class.isAssignableFrom(clazz)) {
            Map map = (Map) o;
            for(Object value : map.values()) {
                return value instanceof MultipartFile;
            }
        }
        return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse || o instanceof BindingResult;
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy