init
This commit is contained in:
40
common/common-lock/pom.xml
Normal file
40
common/common-lock/pom.xml
Normal file
@@ -0,0 +1,40 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>common</artifactId>
|
||||
<groupId>cn.fateverse</groupId>
|
||||
<version>1.0.0</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>common-lock</artifactId>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>cn.fateverse</groupId>
|
||||
<artifactId>common-redis</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.fateverse</groupId>
|
||||
<artifactId>common-core</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>javax.servlet-api</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-aop</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,54 @@
|
||||
package cn.fateverse.common.lock;
|
||||
|
||||
import cn.fateverse.common.lock.aspect.ResubmitLockAspect;
|
||||
import cn.fateverse.common.lock.service.DistributedLockService;
|
||||
import cn.fateverse.common.lock.service.LockKeyGenerator;
|
||||
import cn.fateverse.common.lock.service.impl.AbstractDistributeLockSupport;
|
||||
import cn.fateverse.common.lock.service.impl.DistributedLockKeyGenerator;
|
||||
import cn.fateverse.common.lock.service.impl.DistributedLockServiceImpl;
|
||||
import cn.fateverse.common.lock.aspect.DistributedLockAspect;
|
||||
import cn.fateverse.common.redis.aspect.RedisCacheAspect;
|
||||
import cn.fateverse.common.lock.service.impl.RedisDistributeLockSupport;
|
||||
import cn.fateverse.common.redis.configure.RedisConfig;
|
||||
import org.redisson.api.RLock;
|
||||
import org.redisson.api.RedissonClient;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
/**
|
||||
* @author Clay
|
||||
* @date 2023-05-21
|
||||
*/
|
||||
@ConditionalOnClass(RedisConfig.class)
|
||||
public class RedisAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
public DistributedLockAspect distributedLockAspect(RedissonClient redissonClient, LockKeyGenerator lockKeyGenerator){
|
||||
return new DistributedLockAspect(redissonClient, lockKeyGenerator);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ResubmitLockAspect resubmitLockAspect(DistributedLockService distributedLockService){
|
||||
return new ResubmitLockAspect(distributedLockService);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RedisCacheAspect redisCacheAspect(){
|
||||
return new RedisCacheAspect();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public LockKeyGenerator lockKeyGenerator(){
|
||||
return new DistributedLockKeyGenerator();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public DistributedLockService distributedLockService(RedissonClient redissonClient){
|
||||
return new DistributedLockServiceImpl(redissonClient);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public AbstractDistributeLockSupport<RLock> distributeLockSupport(RedissonClient redissonClient){
|
||||
return new RedisDistributeLockSupport(redissonClient);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package cn.fateverse.common.lock.annotation;
|
||||
|
||||
import cn.fateverse.common.lock.enums.BlockLockType;
|
||||
import cn.fateverse.common.redis.constant.RedisConstant;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* @author Clay
|
||||
* @date 2023-05-10
|
||||
*/
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@Inherited
|
||||
public @interface DistributedLock {
|
||||
|
||||
/**
|
||||
* 前缀
|
||||
*/
|
||||
String prefix() default "";
|
||||
|
||||
/**
|
||||
* 锁过期时间
|
||||
*/
|
||||
int expireTime() default 30;
|
||||
|
||||
/**
|
||||
* 获取锁等待时间
|
||||
*/
|
||||
int waitTime() default 10;
|
||||
|
||||
/**
|
||||
* 时间单位
|
||||
*/
|
||||
TimeUnit timeUnit() default TimeUnit.SECONDS;
|
||||
|
||||
/**
|
||||
* 分隔符
|
||||
*/
|
||||
String delimiter() default RedisConstant.REDIS_SEPARATOR;
|
||||
|
||||
/**
|
||||
* 锁类型
|
||||
*/
|
||||
BlockLockType lock() default BlockLockType.COMMON;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package cn.fateverse.common.lock.annotation;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 参数注解
|
||||
*
|
||||
* @author Clay
|
||||
* @date 2023-05-10
|
||||
*/
|
||||
@Target({ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@Inherited
|
||||
public @interface DistributedLockParam {
|
||||
|
||||
String name() default "";
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package cn.fateverse.common.lock.annotation;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
*
|
||||
* 防止重复提交注解
|
||||
* 1、当设置了keys时,通过表达式确定取哪几个参数作为防重key
|
||||
* 2、当未设置keys时,可以设置argsIndex设置取哪几个参数作为防重key
|
||||
* 3、argsIndex和ignoreKeys是未设置keys时生效,排除不需要防重的参数
|
||||
*
|
||||
* @author Clay
|
||||
* @date 2023-05-10
|
||||
*/
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface ResubmitLock {
|
||||
|
||||
/**
|
||||
* 防重复提交校验的时间间隔
|
||||
*/
|
||||
long interval() default 5;
|
||||
|
||||
/**
|
||||
* 防重复提交校验的时间间隔的单位
|
||||
*/
|
||||
TimeUnit timeUnit() default TimeUnit.SECONDS;
|
||||
|
||||
/**
|
||||
* 是否选用当前操作用户的信息作为防重复提交校验key的一部分
|
||||
*/
|
||||
boolean currentUser() default true;
|
||||
|
||||
/**
|
||||
* keys和ignoreKeys不能同时使用
|
||||
* 参数Spring EL表达式例如 #{param.name},表达式的值作为防重复校验key的一部分
|
||||
*/
|
||||
String[] keys() default {};
|
||||
|
||||
/**
|
||||
* keys和ignoreKeys不能同时使用
|
||||
* ignoreKeys不区分入参,所有入参拥有相同的字段时,都将过滤掉
|
||||
*/
|
||||
String[] ignoreKeys() default {};
|
||||
|
||||
/**
|
||||
* Spring EL表达式,决定是否进行重复提交校验,多个条件之间为且的关系,默认是进行校验
|
||||
*/
|
||||
String[] conditions() default {"true"};
|
||||
|
||||
/**
|
||||
* 当未配置key时,设置哪几个参数作为防重对象,默认取所有参数
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
int[] argsIndex() default {};
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package cn.fateverse.common.lock.aspect;
|
||||
|
||||
import cn.fateverse.common.lock.annotation.DistributedLock;
|
||||
import cn.fateverse.common.lock.enums.BlockLockType;
|
||||
import cn.fateverse.common.lock.service.LockKeyGenerator;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.fateverse.common.core.exception.CustomException;
|
||||
import cn.fateverse.common.redis.constant.RedisConstant;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
import org.aspectj.lang.annotation.Around;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.redisson.api.RLock;
|
||||
import org.redisson.api.RedissonClient;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.core.annotation.Order;
|
||||
|
||||
/**
|
||||
* 分布式锁切面类
|
||||
* redis 中缓存key说明:
|
||||
*
|
||||
* @author Clay
|
||||
* @date 2023-05-10
|
||||
*/
|
||||
@Aspect
|
||||
@Order(4)
|
||||
@Slf4j
|
||||
@ConditionalOnProperty(name = "enabled", prefix = "redis.distributed-lock", havingValue = "true", matchIfMissing = true)
|
||||
public class DistributedLockAspect {
|
||||
|
||||
//redis使用分隔符
|
||||
private static final String REDIS_SEPARATOR = RedisConstant.REDIS_SEPARATOR;
|
||||
//redis缓存全局前缀
|
||||
private static final String DISTRIBUTED_CHECK_KEY_PREFIX = "distributed_lock" + REDIS_SEPARATOR;
|
||||
|
||||
private final RedissonClient redissonClient;
|
||||
|
||||
private final LockKeyGenerator lockKeyGenerator;
|
||||
|
||||
public DistributedLockAspect(RedissonClient redissonClient,
|
||||
LockKeyGenerator lockKeyGenerator) {
|
||||
this.redissonClient = redissonClient;
|
||||
this.lockKeyGenerator = lockKeyGenerator;
|
||||
}
|
||||
|
||||
@Around("@within(distributedLock) || @annotation(distributedLock)")
|
||||
public Object distributedLockCheck(ProceedingJoinPoint point, DistributedLock distributedLock) throws Throwable {
|
||||
if (StrUtil.isEmpty(distributedLock.prefix())) {
|
||||
throw new CustomException("lock prefix can't be null...");
|
||||
}
|
||||
//获取到lock的关键词
|
||||
String lockKey = DISTRIBUTED_CHECK_KEY_PREFIX + lockKeyGenerator.getLockKey(point, distributedLock);
|
||||
//获取到锁
|
||||
RLock lock = chooseLock(distributedLock, lockKey);
|
||||
|
||||
boolean lockSuccess = false;
|
||||
Object proceed = null;
|
||||
try {
|
||||
lockSuccess = lock.tryLock(distributedLock.waitTime(), distributedLock.expireTime(), distributedLock.timeUnit());
|
||||
if (lockSuccess) {
|
||||
proceed = point.proceed();
|
||||
} else {
|
||||
log.debug("tryLock success key [{}]", lockKey);
|
||||
throw new CustomException("系统繁忙,请稍后再试");
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
log.error("key is : {" + lockKey + "} tryLock error ", e);
|
||||
throw new CustomException("系统繁忙,请稍后再试");
|
||||
} finally {
|
||||
if (lockSuccess) {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
return proceed;
|
||||
}
|
||||
|
||||
|
||||
private RLock chooseLock(DistributedLock distributedLock, String lockKey) {
|
||||
BlockLockType category = distributedLock.lock();
|
||||
switch (category) {
|
||||
case FAIR:
|
||||
return redissonClient.getFairLock(lockKey);
|
||||
case SPIN:
|
||||
return redissonClient.getSpinLock(lockKey);
|
||||
case COMMON:
|
||||
return redissonClient.getLock(lockKey);
|
||||
default:
|
||||
throw new CustomException("锁类型错误");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
package cn.fateverse.common.lock.aspect;
|
||||
|
||||
import cn.fateverse.common.core.constant.UserConstants;
|
||||
import cn.fateverse.common.core.enums.ResultEnum;
|
||||
import cn.fateverse.common.core.exception.CustomException;
|
||||
import cn.fateverse.common.core.utils.HttpServletUtils;
|
||||
import cn.fateverse.common.core.utils.IpUtils;
|
||||
import cn.fateverse.common.core.utils.ReflectUserUtils;
|
||||
import cn.fateverse.common.lock.annotation.ResubmitLock;
|
||||
import cn.fateverse.common.lock.service.DistributedLockService;
|
||||
import cn.fateverse.common.redis.constant.RedisConstant;
|
||||
import cn.fateverse.common.redis.utils.ExpressionUtils;
|
||||
import cn.fateverse.common.redis.utils.KeyUtils;
|
||||
import lombok.NonNull;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
import org.aspectj.lang.JoinPoint;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.Before;
|
||||
import org.aspectj.lang.reflect.MethodSignature;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
/**
|
||||
* 防重复提交切面类
|
||||
* redis 中缓存key说明:
|
||||
* resubmit_lock(全局缓存前缀) + 用户信息(开启用户配置才会添加) + ip地址 + uri + 参数形成的md5唯一秘钥
|
||||
*
|
||||
* @author Clay
|
||||
* @date 2023-05-10
|
||||
*/
|
||||
@Slf4j
|
||||
@Aspect
|
||||
@ConditionalOnProperty(name = "enabled", prefix = "redis.resubmit-lock", havingValue = "true", matchIfMissing = true)
|
||||
public class ResubmitLockAspect {
|
||||
//redis使用分隔符
|
||||
private static final String REDIS_SEPARATOR = RedisConstant.REDIS_SEPARATOR;
|
||||
//redis缓存全局前缀
|
||||
private static final String RESUBMIT_CHECK_KEY_PREFIX = "resubmit_lock" + REDIS_SEPARATOR;
|
||||
|
||||
private final DistributedLockService distributedLockService;
|
||||
|
||||
|
||||
public ResubmitLockAspect(DistributedLockService distributedLockService) {
|
||||
this.distributedLockService = distributedLockService;
|
||||
}
|
||||
|
||||
|
||||
@SneakyThrows
|
||||
@Before("@within(resubmitLock) || @annotation(resubmitLock)")
|
||||
public void resubmitCheck(JoinPoint joinPoint, ResubmitLock resubmitLock) {
|
||||
// 获取方法签名
|
||||
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
|
||||
// 获取参数名称
|
||||
String[] parameterNames = methodSignature.getParameterNames();
|
||||
final Object[] args = joinPoint.getArgs();
|
||||
final String[] conditions = resubmitLock.conditions();
|
||||
EvaluationContext context = ExpressionUtils.getEvaluationContext(args, parameterNames);
|
||||
//根据条件判断是否需要进行防重复提交检查
|
||||
if (!ExpressionUtils.getConditionValue(context, conditions) || ArrayUtils.isEmpty(args)) {
|
||||
return;
|
||||
}
|
||||
doCheck(resubmitLock, args, context);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* key的组成为: resubmit_lock:userId:sessionId:uri:method:(根据spring EL表达式对参数进行拼接)
|
||||
*
|
||||
* @param resubmitLock 注解
|
||||
* @param args 方法入参
|
||||
*/
|
||||
private void doCheck(@NonNull ResubmitLock resubmitLock, Object[] args, EvaluationContext context) {
|
||||
//配置的key
|
||||
final String[] keys = resubmitLock.keys();
|
||||
//获取到请求
|
||||
HttpServletRequest request = HttpServletUtils.getRequest();
|
||||
//获取到方法
|
||||
String method = request.getMethod();
|
||||
//获取到uri
|
||||
String uri = request.getRequestURI();
|
||||
//获取到锁的关键词
|
||||
StringBuilder lockKeyBuffer = new StringBuilder(RESUBMIT_CHECK_KEY_PREFIX);
|
||||
//获取用户信息
|
||||
Object loginUser = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
|
||||
//锁类型使用用户信息,并且用户信息存在,则将用户信息加到key中,若为匿名用户则将用户客户端的ip作为key
|
||||
if (resubmitLock.currentUser() && null != loginUser && !UserConstants.ANONYMOUS_USER.equals(loginUser)) {
|
||||
lockKeyBuffer.append(ReflectUserUtils.getToken(loginUser)).append(REDIS_SEPARATOR);
|
||||
} else {
|
||||
String ipAdder = IpUtils.getIpAdder(request);
|
||||
lockKeyBuffer.append(ipAdder).append(REDIS_SEPARATOR);
|
||||
}
|
||||
//将uri和请求类型放入
|
||||
lockKeyBuffer.append(uri).append(REDIS_SEPARATOR).append(method);
|
||||
// 将请求参数取md5值作为key的一部分,MD5理论上会重复,但是key中还包含session或者用户id,所以同用户在极端时间内请参数不同生成的相同md5值的概率极低
|
||||
String parametersKey = KeyUtils.getParametersKey(args, keys, resubmitLock.ignoreKeys(), resubmitLock.argsIndex(), REDIS_SEPARATOR, context);
|
||||
lockKeyBuffer.append(parametersKey);
|
||||
//进行锁的判断
|
||||
try {
|
||||
//尝试获取锁,等待时间为0,立即返回
|
||||
boolean isLock = distributedLockService.tryLock(lockKeyBuffer.toString(), 0, resubmitLock.interval(), resubmitLock.timeUnit());
|
||||
if (!isLock) {
|
||||
//没有获取到则视为重复提交
|
||||
throw new CustomException(ResultEnum.RESUBMIT_LOCK.msg, ResultEnum.RESUBMIT_LOCK.code);
|
||||
}
|
||||
//发生异常也视为重复提交
|
||||
} catch (InterruptedException e) {
|
||||
throw new CustomException(ResultEnum.RESUBMIT_LOCK.msg, ResultEnum.RESUBMIT_LOCK.code);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
package cn.fateverse.common.lock.base;
|
||||
|
||||
import cn.fateverse.common.lock.enums.DistributeLockType;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* @author Clay
|
||||
* @date 2023-05-10
|
||||
*/
|
||||
public class DistributeLockParam {
|
||||
|
||||
/**
|
||||
* 锁id
|
||||
*/
|
||||
private String lockUuid;
|
||||
|
||||
/**
|
||||
* 锁前缀
|
||||
*/
|
||||
private String lockNamePrefix;
|
||||
|
||||
/**
|
||||
* 过期时间
|
||||
*/
|
||||
private Long expireTime;
|
||||
|
||||
/**
|
||||
* 等待时间
|
||||
*/
|
||||
private Long waitTime;
|
||||
|
||||
/**
|
||||
* 时间单位
|
||||
*/
|
||||
private TimeUnit timeUnit;
|
||||
|
||||
/**
|
||||
* 间隔符
|
||||
*/
|
||||
private String delimiter;
|
||||
|
||||
/**
|
||||
* 锁类型
|
||||
*/
|
||||
private DistributeLockType lockType;
|
||||
|
||||
|
||||
public String getLockUuid() {
|
||||
return lockUuid;
|
||||
}
|
||||
|
||||
public void setLockUuid(String lockUuid) {
|
||||
this.lockUuid = lockUuid;
|
||||
}
|
||||
|
||||
public String getLockNamePrefix() {
|
||||
return lockNamePrefix;
|
||||
}
|
||||
|
||||
public void setLockNamePrefix(String lockNamePrefix) {
|
||||
this.lockNamePrefix = lockNamePrefix;
|
||||
}
|
||||
|
||||
public Long getExpireTime() {
|
||||
return expireTime;
|
||||
}
|
||||
|
||||
public void setExpireTime(Long expireTime) {
|
||||
this.expireTime = expireTime;
|
||||
}
|
||||
|
||||
public Long getWaitTime() {
|
||||
return waitTime;
|
||||
}
|
||||
|
||||
public void setWaitTime(Long waitTime) {
|
||||
this.waitTime = waitTime;
|
||||
}
|
||||
|
||||
public TimeUnit getTimeUnit() {
|
||||
return timeUnit;
|
||||
}
|
||||
|
||||
public void setTimeUnit(TimeUnit timeUnit) {
|
||||
this.timeUnit = timeUnit;
|
||||
}
|
||||
|
||||
public String getDelimiter() {
|
||||
return delimiter;
|
||||
}
|
||||
|
||||
public void setDelimiter(String delimiter) {
|
||||
this.delimiter = delimiter;
|
||||
}
|
||||
|
||||
public DistributeLockType getLockType() {
|
||||
return lockType;
|
||||
}
|
||||
|
||||
public void setLockType(DistributeLockType lockType) {
|
||||
this.lockType = lockType;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package cn.fateverse.common.lock.enums;
|
||||
|
||||
/**
|
||||
* 锁类型
|
||||
*
|
||||
* @author Clay
|
||||
* @date 2023-05-10
|
||||
*/
|
||||
public enum BlockLockType {
|
||||
|
||||
/**
|
||||
* 统一锁
|
||||
*/
|
||||
COMMON,
|
||||
/**
|
||||
* 平凡锁
|
||||
*/
|
||||
SPIN,
|
||||
/**
|
||||
* 可重入失败锁
|
||||
*/
|
||||
FAIR;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package cn.fateverse.common.lock.enums;
|
||||
|
||||
/**
|
||||
* @author Clay
|
||||
* @date 2023-05-10
|
||||
*/
|
||||
public enum DistributeLockType {
|
||||
|
||||
/**
|
||||
* 重入锁
|
||||
*/
|
||||
REENTRANT_LOCK,
|
||||
|
||||
/**
|
||||
* 非公平锁
|
||||
*/
|
||||
FAIR_LOCK,
|
||||
|
||||
/**
|
||||
* 联和锁
|
||||
*/
|
||||
MULTI_LOCK,
|
||||
|
||||
/**
|
||||
* 红锁
|
||||
*/
|
||||
RED_LOCK,
|
||||
|
||||
/**
|
||||
* 读写锁
|
||||
*/
|
||||
READ_WRITE_LOCK,
|
||||
;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package cn.fateverse.common.lock.service;
|
||||
|
||||
import cn.fateverse.common.lock.base.DistributeLockParam;
|
||||
import cn.fateverse.common.redis.constant.RedisConstant;
|
||||
import com.google.common.base.Joiner;
|
||||
|
||||
/**
|
||||
* 分布式锁的接口
|
||||
*
|
||||
* @author Clay
|
||||
* @date 2023-05-10
|
||||
*/
|
||||
public interface DistributeLockSupport<T> {
|
||||
/**
|
||||
* 默认的分隔符
|
||||
*/
|
||||
String DEFAULT_DELIMITER = RedisConstant.REDIS_SEPARATOR;
|
||||
|
||||
|
||||
String DEFAULT_KEY_PREFIX = "LOCK";
|
||||
|
||||
|
||||
Long DEFAULT_EXPIRE_TIME = 10L;
|
||||
|
||||
|
||||
Long DEFAULT_WAIT_TIME = 10L;
|
||||
|
||||
|
||||
Joiner DEFAULT_JOINER = Joiner.on(DistributeLockSupport.DEFAULT_DELIMITER).
|
||||
skipNulls();
|
||||
|
||||
/**
|
||||
* 加锁
|
||||
*
|
||||
* @param distributeLockParam 锁参数
|
||||
* @return 返回锁
|
||||
*/
|
||||
T lock(DistributeLockParam distributeLockParam);
|
||||
|
||||
/**
|
||||
* 解锁
|
||||
*
|
||||
* @param lock 锁
|
||||
*/
|
||||
void unlock(T lock);
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package cn.fateverse.common.lock.service;
|
||||
|
||||
import org.redisson.api.RLock;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 分布式锁接口
|
||||
* @author Clay
|
||||
* @date 2023-05-10
|
||||
*/
|
||||
public interface DistributedLockService {
|
||||
|
||||
/**
|
||||
* 加锁
|
||||
* @param lockKey key
|
||||
*/
|
||||
void lock(String lockKey);
|
||||
|
||||
/**
|
||||
* 释放锁
|
||||
*
|
||||
* @param lockKey key
|
||||
*/
|
||||
void unlock(String lockKey);
|
||||
|
||||
/**
|
||||
* 加锁并设置有效期
|
||||
*
|
||||
* @param lockKey key
|
||||
* @param timeout 有效时间,默认时间单位在实现类传入
|
||||
*/
|
||||
void lock(String lockKey, int timeout);
|
||||
|
||||
/**
|
||||
* 加锁并设置有效期指定时间单位
|
||||
* @param lockKey key
|
||||
* @param timeout 有效时间
|
||||
* @param unit 时间单位
|
||||
*/
|
||||
void lock(String lockKey, int timeout, TimeUnit unit);
|
||||
|
||||
/**
|
||||
* 尝试获取锁,获取到则持有该锁返回true,未获取到立即返回false
|
||||
* @param lockKey
|
||||
* @return true-获取锁成功 false-获取锁失败
|
||||
*/
|
||||
boolean tryLock(String lockKey);
|
||||
|
||||
/**
|
||||
* 尝试获取锁,获取到则持有该锁leaseTime时间.
|
||||
* 若未获取到,在waitTime时间内一直尝试获取,超过watiTime还未获取到则返回false
|
||||
* @param lockKey key
|
||||
* @param waitTime 尝试获取时间
|
||||
* @param leaseTime 锁持有时间
|
||||
* @param unit 时间单位
|
||||
* @return true-获取锁成功 false-获取锁失败
|
||||
* @throws InterruptedException
|
||||
*/
|
||||
boolean tryLock(String lockKey, long waitTime, long leaseTime, TimeUnit unit)
|
||||
throws InterruptedException;
|
||||
|
||||
/**
|
||||
* 锁是否被任意一个线程锁持有
|
||||
* @param lockKey
|
||||
* @return true-被锁 false-未被锁
|
||||
*/
|
||||
boolean isLocked(String lockKey);
|
||||
|
||||
|
||||
RLock getLock(String lockKey);
|
||||
|
||||
RLock getFairLock(String lockKey);
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package cn.fateverse.common.lock.service;
|
||||
|
||||
import cn.fateverse.common.lock.annotation.DistributedLock;
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
|
||||
/**
|
||||
* 锁的关键词生成接口
|
||||
*
|
||||
* @author Clay
|
||||
* @date 2023-05-10
|
||||
*/
|
||||
public interface LockKeyGenerator {
|
||||
|
||||
/**
|
||||
* 获取自定义锁
|
||||
*
|
||||
* @param point aop切点
|
||||
* @param lock 锁注解
|
||||
* @return 锁关键词
|
||||
*/
|
||||
String getLockKey(ProceedingJoinPoint point, DistributedLock lock);
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package cn.fateverse.common.lock.service.impl;
|
||||
|
||||
import cn.fateverse.common.lock.base.DistributeLockParam;
|
||||
import cn.fateverse.common.lock.enums.DistributeLockType;
|
||||
import cn.fateverse.common.lock.service.DistributeLockSupport;
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.base.Preconditions;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* @author Clay
|
||||
* @date 2023-05-10
|
||||
*/
|
||||
public abstract class AbstractDistributeLockSupport<T> implements DistributeLockSupport<T> {
|
||||
|
||||
|
||||
/**
|
||||
* 检验参数
|
||||
*
|
||||
* @param distributeLockParam 锁参数
|
||||
* @return 锁参数
|
||||
*/
|
||||
protected DistributeLockParam fullDistributeDefaultValue(DistributeLockParam distributeLockParam) {
|
||||
Preconditions.checkNotNull(distributeLockParam, "检测到了参数不允许为空!");
|
||||
//对默认值进行填充
|
||||
distributeLockParam.setLockType(Optional.ofNullable(distributeLockParam.getLockType()).orElse(DistributeLockType.FAIR_LOCK));
|
||||
distributeLockParam.setExpireTime(Optional.ofNullable(distributeLockParam.getExpireTime()).orElse(DEFAULT_EXPIRE_TIME));
|
||||
distributeLockParam.setWaitTime(Optional.ofNullable(distributeLockParam.getExpireTime()).orElse(DEFAULT_WAIT_TIME));
|
||||
distributeLockParam.setTimeUnit(Optional.ofNullable(distributeLockParam.getTimeUnit()).orElse(TimeUnit.SECONDS));
|
||||
return distributeLockParam;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 构建相关的锁key值
|
||||
*
|
||||
* @param distributeLockParam 锁参数
|
||||
* @return 锁key
|
||||
*/
|
||||
protected String buildLockKey(DistributeLockParam distributeLockParam) {
|
||||
String lockId = StringUtils.defaultIfEmpty(distributeLockParam.getLockUuid(),
|
||||
java.util.UUID.randomUUID().toString());
|
||||
distributeLockParam.setLockUuid(lockId);
|
||||
String delmiter = StringUtils.defaultIfEmpty(distributeLockParam.getDelimiter(),
|
||||
DEFAULT_DELIMITER);
|
||||
distributeLockParam.setDelimiter(delmiter);
|
||||
String prefix = StringUtils.defaultIfEmpty(distributeLockParam
|
||||
.getLockNamePrefix(), DEFAULT_KEY_PREFIX);
|
||||
distributeLockParam.setLockNamePrefix(prefix);
|
||||
String lockFullName = "";
|
||||
if (!delmiter.equals(DEFAULT_DELIMITER)) {
|
||||
//todo 待优化
|
||||
Joiner joiner = Joiner.on(delmiter).skipNulls();
|
||||
lockFullName = joiner.join(prefix, lockId);
|
||||
} else {
|
||||
lockFullName = DEFAULT_JOINER.join(prefix, lockId);
|
||||
}
|
||||
return lockFullName;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package cn.fateverse.common.lock.service.impl;
|
||||
|
||||
import cn.fateverse.common.lock.annotation.DistributedLockParam;
|
||||
import cn.fateverse.common.lock.service.LockKeyGenerator;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.fateverse.common.lock.annotation.DistributedLock;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
import org.aspectj.lang.reflect.MethodSignature;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Parameter;
|
||||
|
||||
/**
|
||||
* @author Clay
|
||||
* @date 2023-05-10
|
||||
*/
|
||||
@Slf4j
|
||||
public class DistributedLockKeyGenerator implements LockKeyGenerator {
|
||||
|
||||
@Override
|
||||
public String getLockKey(ProceedingJoinPoint point, DistributedLock lock) {
|
||||
MethodSignature signature = (MethodSignature)point.getSignature();
|
||||
//获取请求参数
|
||||
Object[] args = point.getArgs();
|
||||
//获取方法
|
||||
Method method = signature.getMethod();
|
||||
//获取方法参数
|
||||
Parameter[] parameters = method.getParameters();
|
||||
//锁
|
||||
StringBuilder lockBuilder = new StringBuilder();
|
||||
//默认解析方法里面带 CacheParam 注解的属性,如果没有尝试着解析实体对象中的
|
||||
for (int i = 0; i < parameters.length; i++) {
|
||||
Parameter parameter = parameters[i];
|
||||
DistributedLockParam annotation = parameter.getAnnotation(DistributedLockParam.class);
|
||||
if (null == annotation){
|
||||
continue;
|
||||
}
|
||||
lockBuilder.append(lock.delimiter()).append(args[i]);
|
||||
}
|
||||
//如果获取到参数为空,则检查对象中是否存在当前注解
|
||||
if (StrUtil.isEmpty(lockBuilder.toString())){
|
||||
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
|
||||
for (int i = 0; i < parameterAnnotations.length; i++) {
|
||||
Object arg = args[i];
|
||||
Field[] fields = arg.getClass().getDeclaredFields();
|
||||
for (Field field : fields) {
|
||||
DistributedLockParam annotation = field.getAnnotation(DistributedLockParam.class);
|
||||
if (null == annotation){
|
||||
continue;
|
||||
}
|
||||
field.setAccessible(true);
|
||||
lockBuilder.append(lock.delimiter()).append(ReflectionUtils.getField(field,arg));
|
||||
}
|
||||
}
|
||||
}
|
||||
return lock.prefix() + lock;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package cn.fateverse.common.lock.service.impl;
|
||||
|
||||
import cn.fateverse.common.lock.service.DistributedLockService;
|
||||
import org.redisson.api.RLock;
|
||||
import org.redisson.api.RedissonClient;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 分布式锁的 Redisson 接口实现
|
||||
* @author GitEgg
|
||||
* @date 2022-4-10
|
||||
*/
|
||||
public class DistributedLockServiceImpl implements DistributedLockService {
|
||||
|
||||
private final RedissonClient redissonClient;
|
||||
|
||||
public DistributedLockServiceImpl(RedissonClient redissonClient) {
|
||||
this.redissonClient = redissonClient;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void lock(String lockKey) {
|
||||
RLock lock = redissonClient.getLock(lockKey);
|
||||
lock.lock();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unlock(String lockKey) {
|
||||
RLock lock = redissonClient.getLock(lockKey);
|
||||
lock.unlock();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void lock(String lockKey, int timeout) {
|
||||
RLock lock = redissonClient.getLock(lockKey);
|
||||
lock.lock(timeout, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void lock(String lockKey, int timeout, TimeUnit unit) {
|
||||
RLock lock = redissonClient.getLock(lockKey);
|
||||
lock.lock(timeout, unit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean tryLock(String lockKey) {
|
||||
RLock lock = redissonClient.getLock(lockKey);
|
||||
return lock.tryLock();
|
||||
}
|
||||
@Override
|
||||
public boolean tryLock(String lockKey, long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException {
|
||||
RLock lock = redissonClient.getLock(lockKey);
|
||||
return lock.tryLock(waitTime, leaseTime, unit);
|
||||
}
|
||||
@Override
|
||||
public boolean isLocked(String lockKey) {
|
||||
RLock lock = redissonClient.getLock(lockKey);
|
||||
return lock.isLocked();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RLock getLock(String lockKey) {
|
||||
return redissonClient.getLock(lockKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RLock getFairLock(String lockKey) {
|
||||
return redissonClient.getFairLock(lockKey);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package cn.fateverse.common.lock.service.impl;
|
||||
|
||||
import cn.fateverse.common.core.exception.CustomException;
|
||||
import cn.fateverse.common.lock.base.DistributeLockParam;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.redisson.api.RLock;
|
||||
import org.redisson.api.RedissonClient;
|
||||
|
||||
/**
|
||||
* 非阻塞分布式锁的具体实现
|
||||
*
|
||||
* @author Clay
|
||||
* @date 2023-05-10
|
||||
*/
|
||||
@Slf4j
|
||||
public class RedisDistributeLockSupport extends AbstractDistributeLockSupport<RLock> {
|
||||
|
||||
private final RedissonClient redissonClient;
|
||||
|
||||
public RedisDistributeLockSupport(RedissonClient redissonClient) {
|
||||
this.redissonClient = redissonClient;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RLock lock(DistributeLockParam distributeLockParam) {
|
||||
distributeLockParam = fullDistributeDefaultValue(distributeLockParam);
|
||||
String lockKey = buildLockKey(distributeLockParam);
|
||||
RLock lock;
|
||||
try {
|
||||
switch (distributeLockParam.getLockType()) {
|
||||
// 可重入锁
|
||||
case REENTRANT_LOCK:
|
||||
lock = redissonClient.getLock(lockKey);
|
||||
break;
|
||||
// 非公平锁
|
||||
case FAIR_LOCK:
|
||||
lock = redissonClient.getFairLock(lockKey);
|
||||
break;
|
||||
default: {
|
||||
throw new UnsupportedOperationException("暂时不支持此种方式的锁!");
|
||||
}
|
||||
}
|
||||
boolean locked = lock.tryLock(distributeLockParam.getWaitTime(), distributeLockParam.getExpireTime(), distributeLockParam.getTimeUnit());
|
||||
if (locked){
|
||||
return lock;
|
||||
}else {
|
||||
throw new CustomException("获取锁失败");
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
log.error("加锁为阻塞模式下的锁进行失败!", e);
|
||||
throw new CustomException("获取锁失败");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unlock(RLock lock) {
|
||||
try {
|
||||
lock.unlock();
|
||||
} catch (Exception e) {
|
||||
log.error("解锁为阻塞模式下的锁进行失败!", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
cn.fateverse.common.lock.RedisAutoConfiguration
|
||||
Reference in New Issue
Block a user