1.redis工具类
import lombok.extern.slf4j.Slf4j;
import net.logstash.logback.encoder.org.apache.commons.lang.exception.ExceptionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.support.atomic.RedisAtomicLong;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.util.*;
import java.util.concurrent.TimeUnit;
@Slf4j
@Component
public class RedisUtil {
//根据项目名端口号获取项目级别的key前缀
@Value("${spring.redis.projectKeyPrefix}")
public String projectKeyPrefix;
@Autowired
private RedisTemplate<Object, Object> redisTemplate;
/**
* 成功标志
*/
private static final long SUCCESS = 1L;
/**
* 默认过期时间(60s)
*/
private static final long DEFAULT_EXPIRE = 60L;
/**
* 释放锁脚本,原子操作,lua脚本
*/
private static final String UNLOCK_LUA = "if redis.call('get',KEYS[1]) == ARGV[1] then" +
" return redis.call('del',KEYS[1]) " +
"else" +
" return 0 " +
"end";
/** -------------------分布式锁相关操作------------------------- */
/**
* 获取分布式锁
* @param lockKey 锁
* @param requestId 唯一ID
* @return
*/
public boolean tryLock(String lockKey, String requestId){
return tryLock(lockKey, requestId, DEFAULT_EXPIRE);
}
/**
* 获取分布式锁
* @param lockKey 锁
* @param requestId 唯一ID
* @param expireTime 过期时间(默认单位秒)
* @return
*/
public boolean tryLock(String lockKey, String requestId, long expireTime){
Boolean result = Boolean.FALSE;
try {
result = redisTemplate.opsForValue().setIfAbsent(lockKey, requestId, expireTime, TimeUnit.SECONDS);
}catch (Exception e){
log.error("try lock lockKey [{}], requestId [{}], expireTime [{}] error {}", lockKey, requestId,
expireTime, ExceptionUtils.getStackTrace(e));
}
log.info("try lock lockKey [{}], requestId [{}], result is [{}]", lockKey, requestId, result);
return Optional.ofNullable(result).orElse(Boolean.FALSE);
}
/**
* 释放锁
* @param lockKey 锁
* @param requestId 唯一ID
*/
public void unlock(String lockKey, String requestId) {
Boolean result = Boolean.FALSE;
DefaultRedisScript<Long> unlockScript = new DefaultRedisScript<>(UNLOCK_LUA, Long.class);
Long rs = redisTemplate.execute(unlockScript, Collections.singletonList(lockKey), requestId);
if(SUCCESS == rs){
result = Boolean.TRUE;
}
log.info("unlock lockKey [{}], requestId [{}] result is [{}]", lockKey, requestId, result);
}
/**
* 设置Hash类型缓存值
* @param key
* @param hashKey
* @param value
* @return
*/
public boolean hPut(String key, Object hashKey, Object value) {
try {
redisTemplate.opsForHash().put(key, hashKey, value);
return true;
}catch (Exception e){
log.error("redis set hash value [key={}] error, {}", key, ExceptionUtils.getStackTrace(e));
return false;
}
}
/**
* 查看哈希表 key 中,指定的字段是否存在
*
* @param key
* @param hashKey
* @return
*/
public boolean hExists(String key, Object hashKey) {
return redisTemplate.opsForHash().hasKey(key, hashKey);
}
/**
* 删除一个或多个哈希表字段
*
* @param key
* @param hashKeys
* @return
*/
public Long hDelete(String key, Object... hashKeys) {
return redisTemplate.opsForHash().delete(key, hashKeys);
}
/**
* 获取所有给定字段的值
*
* @param key
* @return
*/
public Map<?, ?> hGetAll(String key) {
return redisTemplate.opsForHash().entries(key);
}
/**
* 设置Hash类型多个缓存值
* @param key
* @param maps
* @return
*/
public boolean hPutAll(String key, Map<?, ?> maps) {
try {
redisTemplate.opsForHash().putAll(key, maps);
return true;
}catch (Exception e){
log.error("redis set hash values [key={}] error, {}", key, ExceptionUtils.getStackTrace(e));
return false;
}
}
/**
* 设置Hash类型多个缓存值及设置过期时间
* @param key
* @param maps
* @return
*/
public boolean hPutAllAndExpire(String key, Map<?, ?> maps, Duration duration) {
try {
redisTemplate.opsForHash().putAll(key, maps);
redisTemplate.expire(key, duration);
return true;
}catch (Exception e){
log.error("redis set hash values [key={}] error, {}", key, ExceptionUtils.getStackTrace(e));
return false;
}
}
/**
* 获取递增id(步长=1)
* @param key
* @return
*/
public long generateId(String key){
return generateId(key,1L);
}
/**
* 获取指定步长递增id
* @param key
* @param step
* @return
*/
public long generateId(String key, long step){
RedisAtomicLong atomicLong = new RedisAtomicLong(key, redisTemplate.getConnectionFactory());
return atomicLong.addAndGet(step);
}
/**
* 获取递增id,并设置过期时间(步长=1)
* @param key
* @param expireTime
* @return
*/
public long generateId(String key, Date expireTime){
return generateId(key, expireTime, 1L);
}
/**
* 获取指定步长递增id,并设置过期时间
* @param key
* @param step
* @param expireTime
* @return
*/
public long generateId(String key, Date expireTime, long step){
RedisAtomicLong atomicLong = new RedisAtomicLong(key, redisTemplate.getConnectionFactory());
atomicLong.expireAt(expireTime);
return atomicLong.addAndGet(step);
}
public boolean delete(String key) {
return redisTemplate.delete(key);
}
/**
* 设置指定 key 的值
* @param key
* @param value
*/
public boolean set(String key, String value) {
try {
redisTemplate.opsForValue().set(key, value);
return true;
}catch (Exception e){
log.error("redis set [key={}] failed ==> {}",key, ExceptionUtils.getStackTrace(e));
return false;
}
}
/**
* 将值 value 关联到 key ,并将 key 的过期时间设为 timeout
*
* @param key
* @param value
* @param timeout 过期时间
* @param unit 时间单位
*/
public boolean setExpire(String key, String value, long timeout, TimeUnit unit) {
try {
redisTemplate.opsForValue().set(key, value, timeout, unit);
return true;
}catch (Exception e){
log.error("redis set expire [key={}] failed ==> {}",key, ExceptionUtils.getStackTrace(e));
return false;
}
}
/**
* 获取指定 key 的值
* @param key
* @return
*/
public String get(String key) {
Object value = redisTemplate.opsForValue().get(key);
return value == null ? null : String.valueOf(redisTemplate.opsForValue().get(key));
}
/**
* 添加元素,有序集合是按照元素的score值由小到大排列
*
* @param key
* @param value
* @param score
* @return
*/
public Boolean zAdd(String key, String value, double score) {
return redisTemplate.opsForZSet().add(key, value, score);
}
/**
* 移除指定索引位置的成员
*
* @param key
* @param start
* @param end
* @return
*/
public Long zRemoveRange(String key, long start, long end) {
return redisTemplate.opsForZSet().removeRange(key, start, end);
}
/**
* 获取集合大小
*
* @param key
* @return
*/
public Long zSize(String key) {
return redisTemplate.opsForZSet().size(key);
}
/**
* 获取集合的元素, 从大到小排序, 并返回score值
*
* @param key
* @param start
* @param end
* @return
*/
public Set<ZSetOperations.TypedTuple<Object>> zReverseRangeWithScores(String key, long start, long end) {
return redisTemplate.opsForZSet().reverseRangeWithScores(key, start, end);
}
/**
* set集合添加值
*
* @param key
* @param values
* @return
*/
public Long sAdd(String key, String... values) {
return redisTemplate.opsForSet().add(key, values);
}
/**
* set集合删除值
*
* @param key
* @param values
* @return
*/
public boolean sRemove(String key, String... values) {
return redisTemplate.opsForSet().remove(key,values)>0;
}
/**
* set集合获取所有值
*
* @param key
* @return
*/
public Set<Object> sMembers(String key) {
return redisTemplate.opsForSet().members(key);
}
/**
* zset有序集合添加值及计数+1
*
* @param key
* @param value
* @return
*/
public double zIncrementScore(String key, String value) {
return redisTemplate.opsForZSet().incrementScore(key, value, 1d);
}
/**
* zset有序集合查询前几
*
* @param key
* @param top 前几
* @return
*/
public Set<Object> zReverseRange(String key, long top) {
return redisTemplate.opsForZSet().reverseRange(key, 0L, top);
}
}
2.定义注解
/**
* 不允许并发执行注解
*/
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DisallowConcurrentExecution {
}
3.创建切面类,指定切点实现环绕通知
@Slf4j
@Aspect
@Component
public class DisallowConcurrentExecutionAspect {
@Autowired
private RedisUtil redisUtil;
@Pointcut("@annotation(disallowConcurrentExecution)||@within(disallowConcurrentExecution)")
private void cutMethod(DisallowConcurrentExecution disallowConcurrentExecution){
}
@Around("cutMethod(disallowConcurrentExecution)")
private Object around(ProceedingJoinPoint point,DisallowConcurrentExecution disallowConcurrentExecution) throws Throwable {
//获取方法名
String methodName = point.getSignature().getName();
//获取类名
String className = point.getTarget().getClass().getSimpleName();
//获取分布式锁
String key = StringUtils.join(redisUtil.projectKeyPrefix, className,"_", methodName);
boolean success = redisUtil.tryLock(key, "当前用户唯一标识", 3L);
if (!success) {
throw new BizException("当前用户正在执行该操作,请勿重复提交");
}
//执行目标方法
Object result = point.proceed();
redisUtil.unlock(key, "当前用户唯一标识");
log.info("[{}] unlock redis lock success <---", key);
return result;
}
}
4.使用
在需要解决幂等性问题的方法上面添加注解即可