redisson分布式锁

This commit is contained in:
GUO 2024-05-07 19:45:36 +08:00
parent dfa1adfd79
commit 9c4a9cf981
12 changed files with 460 additions and 5 deletions

View File

@ -701,6 +701,11 @@
<artifactId>shedlock-provider-redis-spring</artifactId>
<version>2.5.0</version>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.16.1</version>
</dependency>
<!-- lettuce pool 缓存连接池 -->
<dependency>
<groupId>org.apache.commons</groupId>

View File

@ -2,6 +2,7 @@ package com;
import cn.xuyanwu.spring.file.storage.EnableFileStorage;
import com.zhgd.redis.lock.redisson.EnableDistributedLock;
import org.apache.catalina.connector.Connector;
import org.junit.Test;
import org.mybatis.spring.annotation.MapperScan;
@ -35,6 +36,7 @@ import org.springframework.security.web.firewall.StrictHttpFirewall;
//@EnableConfigurationProperties({FileStorageProperties.class})
@PropertySource(value = "classpath:application.properties", encoding = "utf-8")
@SpringBootApplication
@EnableDistributedLock
public class WisdomSiteApplication extends SpringBootServletInitializer {
public static void main(String[] args) {

View File

@ -0,0 +1,47 @@
package com.zhgd.redis.lock.redisson;
import java.lang.annotation.*;
import java.util.concurrent.TimeUnit;
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DistributedLock {
/**
* 保证业务接口的key的唯一性否则失去了分布式锁的意义 锁key
* 支持使用spEl表达式
*/
String key();
/**
* 保证业务接口的key的唯一性否则失去了分布式锁的意义 锁key 前缀
*/
String keyPrefix() default "";
/**
* 是否在等待时间内获取锁如果在等待时间内无法获取到锁则返回失败
*/
boolean tryLok() default false;
/**
* 获取锁的最大尝试时间 会尝试tryTime时间获取锁在该时间内获取成功则返回否则抛出获取锁超时异常tryLok=true时该值必须大于0
*
*/
long tryTime() default 0;
/**
* 加锁的时间超过这个时间后锁便自动解锁
*/
long lockTime() default 30;
/**
* tryTime lockTime的时间单位
*/
TimeUnit unit() default TimeUnit.SECONDS;
/**
* 是否公平锁false:非公平锁true:公平锁
*/
boolean fair() default false;
}

View File

@ -0,0 +1,174 @@
package com.zhgd.redis.lock.redisson;
import com.zhgd.jeecg.common.execption.OpenAlertException;
import com.zhgd.jeecg.common.execption.OpenPromptException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
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.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.common.TemplateParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.util.Objects;
@Aspect
@Slf4j
public class DistributedLockAspect {
@Resource
private IDistributedLock distributedLock;
/**
* SpEL表达式解析
*/
private SpelExpressionParser spelExpressionParser = new SpelExpressionParser();
/**
* 用于获取方法参数名字
*/
private DefaultParameterNameDiscoverer nameDiscoverer = new DefaultParameterNameDiscoverer();
@Pointcut("@annotation(com.zhgd.redis.lock.redisson.DistributedLock)")
public void distributorLock() {
}
@Around("distributorLock()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
// 获取DistributedLock
DistributedLock distributedLock = this.getDistributedLock(pjp);
// 获取 lockKey
String lockKey = this.getLockKey(pjp, distributedLock);
ILock lockObj = null;
try {
// 加锁tryLok = true,并且tryTime > 0时尝试获取锁获取不到超时异常
if (distributedLock.tryLok()) {
if(distributedLock.tryTime() < 0){
throw new OpenAlertException("tryTime must be greater than 0");
}
lockObj = this.distributedLock.tryLock(lockKey, distributedLock.tryTime(), distributedLock.lockTime(), distributedLock.unit(), distributedLock.fair());
} else {
lockObj = this.distributedLock.lock(lockKey, distributedLock.lockTime(), distributedLock.unit(), distributedLock.fair());
}
if (Objects.isNull(lockObj)) {
throw new OpenPromptException("正在处理中,请稍等");
}
return pjp.proceed();
} catch (Exception e) {
throw e;
} finally {
// 解锁
this.unLock(lockObj);
}
}
/**
* @param pjp
* @return
* @throws NoSuchMethodException
*/
private DistributedLock getDistributedLock(ProceedingJoinPoint pjp) throws NoSuchMethodException {
String methodName = pjp.getSignature().getName();
Class clazz = pjp.getTarget().getClass();
Class<?>[] par = ((MethodSignature) pjp.getSignature()).getParameterTypes();
Method lockMethod = clazz.getMethod(methodName, par);
DistributedLock distributedLock = lockMethod.getAnnotation(DistributedLock.class);
return distributedLock;
}
/**
* 解锁
*
* @param lockObj
*/
private void unLock(ILock lockObj) {
if (Objects.isNull(lockObj)) {
return;
}
try {
this.distributedLock.unLock(lockObj);
} catch (Exception e) {
log.error("分布式锁解锁异常", e);
}
}
/**
* 获取 lockKey
*
* @param pjp
* @param distributedLock
* @return
*/
private String getLockKey(ProceedingJoinPoint pjp, DistributedLock distributedLock) {
String lockKey = distributedLock.key();
String keyPrefix = distributedLock.keyPrefix();
if (StringUtils.isBlank(lockKey)) {
throw new OpenAlertException("Lok key cannot be empty");
}
if (lockKey.contains("#")) {
this.checkSpEL(lockKey);
MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
// 获取方法参数值
Object[] args = pjp.getArgs();
lockKey = getValBySpEL(lockKey, methodSignature, args);
}
lockKey = StringUtils.isBlank(keyPrefix) ? lockKey : keyPrefix + lockKey;
return lockKey;
}
/**
* 解析spEL表达式
*
* @param spEL
* @param methodSignature
* @param args
* @return
*/
private String getValBySpEL(String spEL, MethodSignature methodSignature, Object[] args) {
// 获取方法形参名数组
String[] paramNames = nameDiscoverer.getParameterNames(methodSignature.getMethod());
if (paramNames == null || paramNames.length < 1) {
throw new OpenAlertException("Lok key cannot be empty");
}
Expression expression = spelExpressionParser.parseExpression(spEL);
// spring的表达式上下文对象
EvaluationContext context = new StandardEvaluationContext();
// 给上下文赋值
for (int i = 0; i < args.length; i++) {
context.setVariable(paramNames[i], args[i]);
}
Object value = expression.getValue(context);
if (value == null) {
throw new OpenAlertException("The parameter value cannot be null");
}
return value.toString();
}
/**
* SpEL 表达式校验
*
* @param spEL
* @return
*/
private void checkSpEL(String spEL) {
try {
ExpressionParser parser = new SpelExpressionParser();
parser.parseExpression(spEL, new TemplateParserContext());
} catch (Exception e) {
log.error("spEL表达式解析异常", e);
throw new OpenAlertException("Invalid SpEL expression [" + spEL + "]");
}
}
}

View File

@ -0,0 +1,12 @@
package com.zhgd.redis.lock.redisson;
import org.springframework.context.annotation.Import;
import java.lang.annotation.*;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({DistributedLockAspect.class})
public @interface EnableDistributedLock {
}

View File

@ -0,0 +1,70 @@
package com.zhgd.redis.lock.redisson;
import java.util.concurrent.TimeUnit;
public interface IDistributedLock {
/**
* 获取锁默认30秒失效失败一直等待直到获取锁
*
* @param key 锁的key
* @return 锁对象
*/
ILock lock(String key);
/**
* 获取锁,失败一直等待直到获取锁
*
* @param key 锁的key
* @param lockTime 加锁的时间超过这个时间后锁便自动解锁 如果lockTime为-1则保持锁定直到显式解锁
* @param unit {@code lockTime} 参数的时间单位
* @param fair 是否公平锁
* @return 锁对象
*/
ILock lock(String key, long lockTime, TimeUnit unit, boolean fair);
/**
* 尝试获取锁30秒获取不到超时异常锁默认30秒失效
*
* @param key 锁的key
* @param tryTime 获取锁的最大尝试时间
* @return
* @throws Exception
*/
ILock tryLock(String key, long tryTime) throws Exception;
/**
* 尝试获取锁获取不到超时异常
*
* @param key 锁的key
* @param tryTime 获取锁的最大尝试时间
* @param lockTime 加锁的时间
* @param unit {@code tryTime @code lockTime} 参数的时间单位
* @param fair 是否公平锁
* @return
* @throws Exception
*/
ILock tryLock(String key, long tryTime, long lockTime, TimeUnit unit, boolean fair) throws Exception;
/**
* 解锁
*
* @param lock
* @throws Exception
*/
void unLock(Object lock);
/**
* 释放锁
*
* @param lock
* @throws Exception
*/
default void unLock(ILock lock) {
if (lock != null) {
unLock(lock.getLock());
}
}
}

View File

@ -0,0 +1,33 @@
package com.zhgd.redis.lock.redisson;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Objects;
/**
* <p>
* RedissonLock 包装的锁对象 实现AutoCloseable接口在java7的try(with resource)语法不用显示调用close方法
* </p>
* @since 2023-06-08 16:57
*/
@AllArgsConstructor
public class ILock implements AutoCloseable {
/**
* 持有的锁对象
*/
@Getter
private Object lock;
/**
* 分布式锁接口
*/
@Getter
private IDistributedLock distributedLock;
@Override
public void close() throws Exception {
if(Objects.nonNull(lock)){
distributedLock.unLock(lock);
}
}
}

View File

@ -0,0 +1,99 @@
package com.zhgd.redis.lock.redisson;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;
@Slf4j
@Component
public class RedissonDistributedLock implements IDistributedLock {
@Resource
private RedissonClient redissonClient;
/**
* 统一前缀
*/
@Value("${redisson.lock.prefix:bi:distributed:lock}")
private String prefix;
@Override
public ILock lock(String key) {
return this.lock(key, 0L, TimeUnit.SECONDS, false);
}
@Override
public ILock lock(String key, long lockTime, TimeUnit unit, boolean fair) {
RLock lock = getLock(key, fair);
// 获取锁,失败一直等待,直到获取锁,不支持自动续期
if (lockTime > 0L) {
lock.lock(lockTime, unit);
} else {
// 具有Watch Dog 自动延期机制 默认续30s 每隔30/3=10 秒续到30s
lock.lock();
}
return new ILock(lock, this);
}
@Override
public ILock tryLock(String key, long tryTime) throws Exception {
return this.tryLock(key, tryTime, 0L, TimeUnit.SECONDS, false);
}
@Override
public ILock tryLock(String key, long tryTime, long lockTime, TimeUnit unit, boolean fair)
throws Exception {
RLock lock = getLock(key, fair);
boolean lockAcquired;
// 尝试获取锁获取不到超时异常,不支持自动续期
if (lockTime > 0L) {
lockAcquired = lock.tryLock(tryTime, lockTime, unit);
} else {
// 具有Watch Dog 自动延期机制 默认续30s 每隔30/3=10 秒续到30s
lockAcquired = lock.tryLock(tryTime, unit);
}
if (lockAcquired) {
return new ILock(lock, this);
}
return null;
}
/**
* 获取锁
*
* @param key
* @param fair
* @return
*/
private RLock getLock(String key, boolean fair) {
RLock lock;
String lockKey = prefix + ":" + key;
if (fair) {
// 获取公平锁
lock = redissonClient.getFairLock(lockKey);
} else {
// 获取普通锁
lock = redissonClient.getLock(lockKey);
}
return lock;
}
@Override
public void unLock(Object lock) {
if (!(lock instanceof RLock)) {
throw new IllegalArgumentException("Invalid lock object");
}
RLock rLock = (RLock) lock;
if (rLock.isLocked()) {
try {
rLock.unlock();
} catch (IllegalMonitorStateException e) {
log.error("释放分布式锁异常", e);
}
}
}
}

View File

@ -2,7 +2,6 @@ package com.zhgd.xmgl.modules.quality.controller;
import com.zhgd.annotation.OperLog;
import com.zhgd.jeecg.common.api.vo.Result;
import com.zhgd.jeecg.common.mybatis.EntityMap;
import com.zhgd.xmgl.modules.quality.entity.QualityRectifyRecord;
import com.zhgd.xmgl.modules.quality.service.IQualityRectifyRecordService;
import io.swagger.annotations.Api;
@ -54,7 +53,7 @@ public class QualityRectifyRecordController {
@OperLog(operModul = "质量管理", operType = "添加质量检查整改/复查记录信息", operDesc = "添加质量检查整改/复查记录信息")
@ApiOperation(value = "添加质量检查-整改/复查记录信息", notes = "添加质量检查-整改/复查记录信息", httpMethod = "POST")
@PostMapping(value = "/add")
public Result<QualityRectifyRecord> add(@RequestBody QualityRectifyRecord qualityRectifyRecord) {
public Result<QualityRectifyRecord> add(@RequestBody QualityRectifyRecord qualityRectifyRecord) throws Exception {
qualityRectifyRecordService.saveQualityRectifyRecord(qualityRectifyRecord);
return Result.ok();
}

View File

@ -19,7 +19,7 @@ public interface IQualityRectifyRecordService extends IService<QualityRectifyRec
List<QualityRectifyRecord> selectRectifyRecordList(Map<String, Object> map);
void saveQualityRectifyRecord(QualityRectifyRecord qualityRectifyRecord);
void saveQualityRectifyRecord(QualityRectifyRecord qualityRectifyRecord) throws Exception;
void qualityInspectionRecordRectifyExportExcel(Map<String, Object> map, HttpServletResponse response) throws IOException;
}

View File

@ -11,6 +11,8 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.gexin.fastjson.JSON;
import com.gexin.fastjson.JSONArray;
import com.gexin.fastjson.JSONObject;
import com.zhgd.redis.lock.redisson.DistributedLock;
import com.zhgd.redis.lock.redisson.RedissonDistributedLock;
import com.zhgd.xmgl.call.SanjiangDataCall;
import com.zhgd.xmgl.modules.basicdata.service.INoticeService;
import com.zhgd.xmgl.modules.quality.entity.QualityInspectionRecord;
@ -68,8 +70,12 @@ public class QualityRectifyRecordServiceImpl extends ServiceImpl<QualityRectifyR
return qualityRectifyRecordMapper.selectRectifyRecordList(map);
}
@Autowired
RedissonDistributedLock redissonDistributedLock;
@Override
public void saveQualityRectifyRecord(QualityRectifyRecord qualityRectifyRecord) {
@DistributedLock(keyPrefix = "quality_rectify_record:",key = "#qualityRectifyRecord.qualityId",tryLok = true,lockTime = 0)
public void saveQualityRectifyRecord(QualityRectifyRecord qualityRectifyRecord) throws Exception {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
qualityRectifyRecord.setCreateTime(sdf.format(new Date()));
qualityRectifyRecordMapper.insert(qualityRectifyRecord);

View File

@ -122,12 +122,20 @@ spring.redis.database=1
spring.redis.host=127.0.0.1
spring.redis.port=6379
#spring.redis.password=JXJ@admin
spring.redis.password=
#分布式锁密码注释的时候,这个也要注释
#spring.redis.password=
spring.redis.timeout=2000s
spring.redis.lettuce.pool.max-active=8
spring.redis.lettuce.pool.max-wait=60s
spring.redis.lettuce.pool.max-idle=10
spring.redis.lettuce.pool.min-idle=10
#分布式锁
redisson.codec=org.redisson.codec.JsonJacksonCodec
redisson.threads=4
redisson.netty.threads=4
redisson.single-server-config.address=redis://${spring.redis.host}:${spring.redis.port}
#redisson.single-server-config.password=null
#redisson.single-server-config.database=0
# spring boot admin 所在服务器
spring.boot.admin.client.url=http://localhost:9091
# actuator 配置内容