/** * 防止重複提交的註解 */ @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註解 java