From 9c4a9cf981f6eaef92ab4a4697697ac5eaac6922 Mon Sep 17 00:00:00 2001 From: GUO <1923636941@qq.com> Date: Tue, 7 May 2024 19:45:36 +0800 Subject: [PATCH] =?UTF-8?q?redisson=E5=88=86=E5=B8=83=E5=BC=8F=E9=94=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 5 + src/main/java/com/WisdomSiteApplication.java | 2 + .../redis/lock/redisson/DistributedLock.java | 47 +++++ .../lock/redisson/DistributedLockAspect.java | 174 ++++++++++++++++++ .../lock/redisson/EnableDistributedLock.java | 12 ++ .../redis/lock/redisson/IDistributedLock.java | 70 +++++++ .../com/zhgd/redis/lock/redisson/ILock.java | 33 ++++ .../redisson/RedissonDistributedLock.java | 99 ++++++++++ .../QualityRectifyRecordController.java | 3 +- .../service/IQualityRectifyRecordService.java | 2 +- .../impl/QualityRectifyRecordServiceImpl.java | 8 +- src/main/resources/application.properties | 10 +- 12 files changed, 460 insertions(+), 5 deletions(-) create mode 100644 src/main/java/com/zhgd/redis/lock/redisson/DistributedLock.java create mode 100644 src/main/java/com/zhgd/redis/lock/redisson/DistributedLockAspect.java create mode 100644 src/main/java/com/zhgd/redis/lock/redisson/EnableDistributedLock.java create mode 100644 src/main/java/com/zhgd/redis/lock/redisson/IDistributedLock.java create mode 100644 src/main/java/com/zhgd/redis/lock/redisson/ILock.java create mode 100644 src/main/java/com/zhgd/redis/lock/redisson/RedissonDistributedLock.java diff --git a/pom.xml b/pom.xml index 159f3d635..6586203ff 100644 --- a/pom.xml +++ b/pom.xml @@ -701,6 +701,11 @@ shedlock-provider-redis-spring 2.5.0 + + org.redisson + redisson-spring-boot-starter + 3.16.1 + org.apache.commons diff --git a/src/main/java/com/WisdomSiteApplication.java b/src/main/java/com/WisdomSiteApplication.java index 6f9810001..0d013108a 100644 --- a/src/main/java/com/WisdomSiteApplication.java +++ b/src/main/java/com/WisdomSiteApplication.java @@ -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) { diff --git a/src/main/java/com/zhgd/redis/lock/redisson/DistributedLock.java b/src/main/java/com/zhgd/redis/lock/redisson/DistributedLock.java new file mode 100644 index 000000000..bc90250a0 --- /dev/null +++ b/src/main/java/com/zhgd/redis/lock/redisson/DistributedLock.java @@ -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; +} diff --git a/src/main/java/com/zhgd/redis/lock/redisson/DistributedLockAspect.java b/src/main/java/com/zhgd/redis/lock/redisson/DistributedLockAspect.java new file mode 100644 index 000000000..014fcbdda --- /dev/null +++ b/src/main/java/com/zhgd/redis/lock/redisson/DistributedLockAspect.java @@ -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 + "]"); + } + } +} diff --git a/src/main/java/com/zhgd/redis/lock/redisson/EnableDistributedLock.java b/src/main/java/com/zhgd/redis/lock/redisson/EnableDistributedLock.java new file mode 100644 index 000000000..d982d014f --- /dev/null +++ b/src/main/java/com/zhgd/redis/lock/redisson/EnableDistributedLock.java @@ -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 { +} diff --git a/src/main/java/com/zhgd/redis/lock/redisson/IDistributedLock.java b/src/main/java/com/zhgd/redis/lock/redisson/IDistributedLock.java new file mode 100644 index 000000000..8e64a1348 --- /dev/null +++ b/src/main/java/com/zhgd/redis/lock/redisson/IDistributedLock.java @@ -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()); + } + } + + +} diff --git a/src/main/java/com/zhgd/redis/lock/redisson/ILock.java b/src/main/java/com/zhgd/redis/lock/redisson/ILock.java new file mode 100644 index 000000000..5d7937996 --- /dev/null +++ b/src/main/java/com/zhgd/redis/lock/redisson/ILock.java @@ -0,0 +1,33 @@ +package com.zhgd.redis.lock.redisson; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Objects; + +/** + *

+ * RedissonLock 包装的锁对象 实现AutoCloseable接口,在java7的try(with resource)语法,不用显示调用close方法 + *

+ * @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); + } + } +} diff --git a/src/main/java/com/zhgd/redis/lock/redisson/RedissonDistributedLock.java b/src/main/java/com/zhgd/redis/lock/redisson/RedissonDistributedLock.java new file mode 100644 index 000000000..cdf13f20e --- /dev/null +++ b/src/main/java/com/zhgd/redis/lock/redisson/RedissonDistributedLock.java @@ -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); + } + } + } +} diff --git a/src/main/java/com/zhgd/xmgl/modules/quality/controller/QualityRectifyRecordController.java b/src/main/java/com/zhgd/xmgl/modules/quality/controller/QualityRectifyRecordController.java index 509adf909..a049cce3a 100644 --- a/src/main/java/com/zhgd/xmgl/modules/quality/controller/QualityRectifyRecordController.java +++ b/src/main/java/com/zhgd/xmgl/modules/quality/controller/QualityRectifyRecordController.java @@ -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 add(@RequestBody QualityRectifyRecord qualityRectifyRecord) { + public Result add(@RequestBody QualityRectifyRecord qualityRectifyRecord) throws Exception { qualityRectifyRecordService.saveQualityRectifyRecord(qualityRectifyRecord); return Result.ok(); } diff --git a/src/main/java/com/zhgd/xmgl/modules/quality/service/IQualityRectifyRecordService.java b/src/main/java/com/zhgd/xmgl/modules/quality/service/IQualityRectifyRecordService.java index 41d47fdf9..a1cfe6712 100644 --- a/src/main/java/com/zhgd/xmgl/modules/quality/service/IQualityRectifyRecordService.java +++ b/src/main/java/com/zhgd/xmgl/modules/quality/service/IQualityRectifyRecordService.java @@ -19,7 +19,7 @@ public interface IQualityRectifyRecordService extends IService selectRectifyRecordList(Map map); - void saveQualityRectifyRecord(QualityRectifyRecord qualityRectifyRecord); + void saveQualityRectifyRecord(QualityRectifyRecord qualityRectifyRecord) throws Exception; void qualityInspectionRecordRectifyExportExcel(Map map, HttpServletResponse response) throws IOException; } diff --git a/src/main/java/com/zhgd/xmgl/modules/quality/service/impl/QualityRectifyRecordServiceImpl.java b/src/main/java/com/zhgd/xmgl/modules/quality/service/impl/QualityRectifyRecordServiceImpl.java index 6009e66df..158389470 100644 --- a/src/main/java/com/zhgd/xmgl/modules/quality/service/impl/QualityRectifyRecordServiceImpl.java +++ b/src/main/java/com/zhgd/xmgl/modules/quality/service/impl/QualityRectifyRecordServiceImpl.java @@ -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