请求防重处理

1、自定义注解

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public  @interface NoRepeatSubmit {

    /**
     * 默认1s钟以内算重复提交
     * @return
     */
    int lockTime() default 1000;
}

2、定义切面类

/**
 * 接口重复调用切面
 * 仅使用单机时
 */
@Aspect
@Component
@Slf4j
public class RepeatSubmitAspect {

     //定义切入点表达式
    @Pointcut("@annotation(noRepeatSubmit)")
    public void pointCut(NoRepeatSubmit noRepeatSubmit) {
    }

    @Around("pointCut(noRepeatSubmit)")
    public Object around(ProceedingJoinPoint pjp, NoRepeatSubmit noRepeatSubmit) throws Throwable {
        int lockMillisecond = noRepeatSubmit.lockTime();
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
        Assert.notNull(request, "request can not null");
        //此处可以用或者JSessionId
        String token = request.getHeader("Cookie");
        String path = getPath(request);
        Object[] args = pjp.getArgs();
        String key = getKey(token, path, args);
        boolean isSuccess = LocalCache.getInstance().tryLock(key, lockMillisecond);
        log.info("获取锁 key = [{}], clientId = [{}]", key,token);

        if (isSuccess) {
            log.info("获取锁 成功, key = [{}], clientId = [{}]", key,token);
            // 获取锁成功
            Object result;
            try {
                // 执行进程
                result = pjp.proceed();
            } finally {
                // 解锁
                LocalCache.getInstance().releaseLock(key);
                log.info("释放锁 成功, key = [{}], clientId = [{}]", key,token);
            }
            return result;
        } else {
            // 获取锁失败,认为是重复提交的请求
            log.info("获取锁 失败, key = [{}]", key);
            LayUiResult result = new LayUiResult();
            result.setResultStr("操作太快了,请稍后重试");
            result.setResult(false);
            return result;
        }

    }

    private String getKey(String token, String path, Object... args) {
        return token + "_" + path + "_" + getParam(args);
    }

    public <T> String getNotNullFieldStr(T origin) {
        if (origin == null)
            return "";
        StringBuffer stringBuffer = new StringBuffer();
        Field[] fields = origin.getClass().getDeclaredFields();
        for (int i = 0; i < fields.length; i++) {
            try {
                fields[i].setAccessible(true);
                String name = fields[i].getName();
                Object value = fields[i].get(origin);
                if (null != value) {
                    stringBuffer.append(name).append("=").append(value).append("&");
                }
                fields[i].setAccessible(false);
            } catch (Exception e) {
            }
        }
        return stringBuffer.toString();
    }

    public static boolean isWrapClass(Class clz) {
        try {
            return ((Class) clz.getField("TYPE").get(null)).isPrimitive();
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * 生成md5
     *
     * @param base
     * @return
     */
    public static String getMD5(String base) {
        String md5 = DigestUtils.md5DigestAsHex(base.getBytes());
        return md5;
    }

    private String getParam(Object[] args) {
        if (args == null) {
            return "";
        }
        StringBuffer stringBuffer = new StringBuffer();
        for (Object object : args) {
            if (object != null) {
                if (isWrapClass(object.getClass())) {
                    stringBuffer.append(object.toString());
                } else {
                    stringBuffer.append(getMD5(getNotNullFieldStr(object)));
                }
            }
        }
        return getMD5(stringBuffer.toString());
    }

    private String getPath(HttpServletRequest request) {
        String uri = request.getRequestURI();
        String contextPath = request.getContextPath();
        if (StringUtils.length(contextPath) > 0) {
            uri = StringUtils.substring(uri, contextPath.length());
        }
        return uri;
    }
}

3、本地接口调用内存缓存

package com.idea.aop;

import lombok.extern.slf4j.Slf4j;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * 本地接口调用内存缓存
 */
@Slf4j
public class LocalCache {
    private static LocalCache instance = new LocalCache();

    private LocalCache() {

    }

    public static LocalCache getInstance() {
        return instance;
    }

    /**
     * {key:{EXPIRED:时间戳,INSERT:时间戳},...}
     */
    private Map<String, Map<String, Object>> localMap = new HashMap<>();

    /**
     * 获取锁,当key存在且未过期时可以成功获得锁
     *
     * @param key
     * @param lockMillisecond
     * @return
     */
    public synchronized boolean tryLock(String key, long lockMillisecond) {
        //清理缓存
        clearExpired();
        //key已存在
        if (localMap.containsKey(key)) {
            //已过期
            if (expired(key, lockMillisecond)) {
                //已过期,从Map中移除
                localMap.remove(key);
                addCache(key, lockMillisecond);
                return true;
            } else {
                //未过期
                return false;
            }
        } else {
            //未在执行中
            addCache(key, lockMillisecond);
            return true;
        }
    }

    private void addCache(String key, long lockMillisecond) {
        Map map = new HashMap<>();
        map.put(EXPIRED, lockMillisecond);
        map.put(INSERT, new Date().getTime());
        localMap.put(key, map);
    }

    /**
     * 过期时间_KEY
     */
    /**
     * 创建时间_KEY
     */
    private static final String EXPIRED = "EXPIRED";
    private static final String INSERT = "INSERT";

    /**
     * 是否已过期
     *
     * @param key
     * @param lockMillisecond
     * @return
     */
    private synchronized boolean expired(Object key, long lockMillisecond) {
        Map<String, Object> map = localMap.get(key);
        //不存在 默认返回未过期
        if (map == null) {
            return true;
        }
        //数据接口调用时间戳
        long oldTime = (Long) map.get(INSERT);

        long now = new Date().getTime();

        //已过期:当前时间戳-(开始时间+过期时间)大于 0
        return (now - (oldTime + lockMillisecond)) > 0L;
    }

    /**
     * 清理Map中所有已过期的接口请求
     */
    private synchronized void clearExpired() {
        for (Map.Entry entry : localMap.entrySet()) {
            Object key = entry.getKey();
            Map<String, Object> map = localMap.get(key);
            long expired = (Long) map.get(EXPIRED);
            if (expired(key, expired)) {
                releaseLock(key);
            }
        }
    }

    /**
     * 释放锁
     *
     * @param key
     */
    public synchronized void releaseLock(Object key) {
        localMap.remove(key);
        log.info("本地缓存释放,Key:{}", key);
    }

}

 知识点回顾:

request.getRequestURL() 返回全路径
request.getRequestURI() 返回除去host(域名或者ip)部分的路径
举例如下:
request.getRequestURL()  http://localhost:8080/project_name/user/login.do
request.getRequestURI()  /project_name/user/login.do
request.getServletPath()    这个方法获取的是包括servlet之后的路径,不包括项目名的路径  /user/login.do
request.getContextPath()  从字面我们知道contextpath的意思是容器的路径,我们可以把context理解为项目。所以这个方法获取的就是项目名路径  /project_name

 

posted on 2022-06-21 23:29  小破孩楼主  阅读(53)  评论(0编辑  收藏  举报