请求防重处理
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