/** * 防止重复提交的注解 */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) public @interface AvoidRepeatSubmit { long lockTime() default 1000; }
import com.mushi.anno.AvoidRepeatSubmit; import com.mushi.config.ResultGenerator; import com.mushi.redis.service.RedisDistributedLock; import lombok.extern.slf4j.Slf4j; 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.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.util.Assert; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest; import java.util.UUID; /** * 防止重复提交的切面 */ @Aspect @Component @Slf4j public class RepeatSubmitAspect { @Autowired private RedisDistributedLock redisDistributedLock; /** * 切点 * * @param avoidRepeatSubmit */ @Pointcut("@annotation(avoidRepeatSubmit)") public void pointCut(AvoidRepeatSubmit avoidRepeatSubmit) { } /** * 利用环绕通知进行处理重复提交问题 * * @param pjp * @param avoidRepeatSubmit * @return * @throws Throwable */ @Around("pointCut(avoidRepeatSubmit)") public Object around(ProceedingJoinPoint pjp, AvoidRepeatSubmit avoidRepeatSubmit) throws Throwable { /** * 获取锁的时间 */ long lockSeconds = avoidRepeatSubmit.lockTime(); //获得request对象 HttpServletRequest request = httpServletRequest(); Assert.notNull(request, "request can not null"); // 此处可以用token或者JSessionId String token = request.getHeader("token"); String path = request.getServletPath(); String key = getKey(token, path); String clientId = getClientId(); //锁定多少秒 boolean isSuccess = redisDistributedLock.setLock(key, clientId, lockSeconds); if (isSuccess) { log.info("tryLock success, key = [{}], clientId = [{}]", key, clientId); // 获取锁成功, 执行进程 Object result; try { result = pjp.proceed(); } finally { //解锁 redisDistributedLock.releaseLock(key, clientId); log.info("releaseLock success, key = [{}], clientId = [{}]", key, clientId); } return result; } else { // 获取锁失败,认为是重复提交的请求 log.info("tryLock fail, key = [{}]", key); return ResultGenerator.genRepeatSubmitResult("重复请求,请稍后再试"); } } /** * 获得request对象 * * @return */ private HttpServletRequest httpServletRequest() { ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); return requestAttributes.getRequest(); } /** * 获得请求key * * @param token * @param path * @return */ private String getKey(String token, String path) { return token + path; } /** * 获得uuid * * @return */ private String getClientId() { return UUID.randomUUID().toString(); } }
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.data.redis.core.RedisCallback; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisCluster; import redis.clients.jedis.JedisCommands; import javax.annotation.Resource; import java.util.ArrayList; import java.util.List; @Component public class RedisDistributedLock { @Resource private RedisTemplate<String, Object> redisTemplate; public static final String UNLOCK_LUA; static { StringBuilder sb = new StringBuilder(); sb.append("if redis.call("get",KEYS[1]) == ARGV[1] "); sb.append("then "); sb.append(" return redis.call("del",KEYS[1]) "); sb.append("else "); sb.append(" return 0 "); sb.append("end "); UNLOCK_LUA = sb.toString(); } private final Logger logger = LoggerFactory.getLogger(RedisDistributedLock.class); public boolean setLock(String key, String clientId, long expire) { try { RedisCallback<String> callback = (connection) -> { JedisCommands commands = (JedisCommands) connection.getNativeConnection(); return commands.set(key, clientId, "NX", "PX", expire); }; String result = redisTemplate.execute(callback); return !StringUtils.isEmpty(result); } catch (Exception e) { logger.error("set redis occured an exception", e); } return false; } public String get(String key) { try { RedisCallback<String> callback = (connection) -> { JedisCommands commands = (JedisCommands) connection.getNativeConnection(); return commands.get(key); }; String result = redisTemplate.execute(callback); return result; } catch (Exception e) { logger.error("get redis occured an exception", e); } return ""; } public boolean releaseLock(String key, String requestId) { // 释放锁的时候,有可能因为持锁之后方法执行时间大于锁的有效期,此时有可能已经被另外一个线程持有锁,所以不能直接删除 try { List<String> keys = new ArrayList<>(); keys.add(key); List<String> args = new ArrayList<>(); args.add(requestId); // 使用lua脚本删除redis中匹配value的key,可以避免由于方法执行时间过长而redis锁自动过期失效的时候误删其他线程的锁 // spring自带的执行脚本方法中,集群模式直接抛出不支持执行脚本的异常,所以只能拿到原redis的connection来执行脚本 RedisCallback<Long> callback = (connection) -> { Object nativeConnection = connection.getNativeConnection(); // 集群模式和单机模式虽然执行脚本的方法一样,但是没有共同的接口,所以只能分开执行 // 集群模式 if (nativeConnection instanceof JedisCluster) { return (Long) ((JedisCluster) nativeConnection).eval(UNLOCK_LUA, keys, args); } // 单机模式 else if (nativeConnection instanceof Jedis) { return (Long) ((Jedis) nativeConnection).eval(UNLOCK_LUA, keys, args); } return 0L; }; Long result = redisTemplate.execute(callback); return result != null && result > 0; } catch (Exception e) { logger.error("release lock occured an exception", e); } finally { // 清除掉ThreadLocal中的数据,避免内存溢出 //lockFlag.remove(); } return false; } }
然后再需要接口防重的接口上加上AvoidRepeatSubmit注解