1.自定义重复提交注解
package com.zddts.common.annotation.pre; import java.lang.annotation.*; /** * 说明:防止重复提交 * Created by luojie on 2019/07/16. */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface NoRepeatSubmitAnnotation { }
2.切面处理
package com.zddts.patmarket.aop; import com.zddts.common.annotation.dict.DataDictClass; import com.zddts.common.annotation.pre.NoRepeatSubmitAnnotation; import com.zddts.common.utils.R; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.netflix.zuul.util.RequestUtils; import org.springframework.data.redis.core.RedisCallback; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest; import java.util.Objects; import java.util.UUID; /** * 说明:防止重复提交 * Created by luojie on 2019/07/16. */ @Aspect @Component public class RepeatSubmitAspect { private final static Logger LOGGER = LoggerFactory.getLogger(RepeatSubmitAspect.class); public static final int LOCK_EXPIRE = 3000; // ms @Autowired private RedisTemplate redisTemplate; @Pointcut("@annotation(noRepeatSubmit)") public void noRepeatSubmitAnnotation(NoRepeatSubmitAnnotation noRepeatSubmit) { } @Around("@annotation(noRepeatSubmit)") public Object around(ProceedingJoinPoint pjp, NoRepeatSubmitAnnotation noRepeatSubmit) throws Throwable { HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); // 此处可以用token或者JSessionId String token = request.getHeader("Authorization"); String path = request.getServletPath(); String key = getKey(token, path); String clientId = getClientId(); boolean isSuccess = lock(key); if (isSuccess) { LOGGER.info("tryLock success, key = [{}], clientId = [{}]", key, clientId); // 获取锁成功, 执行进程 Object result; try { result = pjp.proceed(); } finally { // 解锁 delete(key); LOGGER.info("releaseLock success, key = [{}], clientId = [{}]", key, clientId); } return result; } else { // 获取锁失败,认为是重复提交的请求 LOGGER.error("tryLock fail, key = [{}]", key); return R.error("重复请求,请稍后再试"); } } private String getKey(String token, String path) { return token + path; } private String getClientId() { return UUID.randomUUID().toString(); } /** * 最终加强分布式锁 * * @param key key值 * @return 是否获取到 */ public boolean lock(String key) { // 利用lambda表达式 return (Boolean) redisTemplate.execute((RedisCallback) connection -> { long expireAt = System.currentTimeMillis() + LOCK_EXPIRE + 1; Boolean acquire = connection.setNX(key.getBytes(), String.valueOf(expireAt).getBytes()); if (acquire) { return true; } else { byte[] value = connection.get(key.getBytes()); if (Objects.nonNull(value) && value.length > 0) { long expireTime = Long.parseLong(new String(value)); if (expireTime < System.currentTimeMillis()) { // 如果锁已经过期 byte[] oldValue = connection.getSet(key.getBytes(), String.valueOf(System.currentTimeMillis() + LOCK_EXPIRE + 1).getBytes()); // 防止死锁 return Long.parseLong(new String(oldValue)) < System.currentTimeMillis(); } } } return false; }); } /** * 删除锁 * * @param key */ public void delete(String key) { redisTemplate.delete(key); } }
参考来源: https://blog.csdn.net/u011277123/article/details/91492347
https://blog.csdn.net/weixin_38399962/article/details/82753763
扩展:可以通过 NoRepeatSubmitAnnotation 属性自定义超时时间