随笔 - 832  文章 - 2  评论 - 31  阅读 - 167万

请求防重处理

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   小破孩楼主  阅读(55)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

点击右上角即可分享
微信分享提示