From 0d2ba4e70aca621089e1c227b323426c5b2547d2 Mon Sep 17 00:00:00 2001
From: zhongzb <972627721@qq.com>
Date: Sat, 22 Apr 2023 22:49:11 +0800
Subject: [PATCH] =?UTF-8?q?=E5=88=86=E5=B8=83=E5=BC=8F=E9=94=81=E6=B3=A8?=
=?UTF-8?q?=E8=A7=A3=E6=9C=80=E4=BD=B3=E5=AE=9E=E8=B7=B5?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
mallchat-common/pom.xml | 5 ++
.../common/annotation/RedissonLock.java | 40 +++++++++++++++
.../common/aspect/FrequencyControlAspect.java | 17 ++-----
.../common/aspect/RedissonLockAspect.java | 50 ++++++++++++++++++
.../common/common/config/RedissonConfig.java | 30 +++++++++++
.../common/exception/CommonErrorEnum.java | 1 +
.../common/common/service/LockService.java | 51 +++++++++++++++++++
.../common/common/utils/SpElUtils.java | 35 +++++++++++++
.../service/impl/UserBackpackServiceImpl.java | 2 +
.../custom/common/config/SwaggerConfig.java | 46 +++++++++++------
10 files changed, 248 insertions(+), 29 deletions(-)
create mode 100644 mallchat-common/src/main/java/com/abin/mallchat/common/common/annotation/RedissonLock.java
create mode 100644 mallchat-common/src/main/java/com/abin/mallchat/common/common/aspect/RedissonLockAspect.java
create mode 100644 mallchat-common/src/main/java/com/abin/mallchat/common/common/config/RedissonConfig.java
create mode 100644 mallchat-common/src/main/java/com/abin/mallchat/common/common/service/LockService.java
create mode 100644 mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/SpElUtils.java
diff --git a/mallchat-common/pom.xml b/mallchat-common/pom.xml
index cb7d4ca..9f6b423 100644
--- a/mallchat-common/pom.xml
+++ b/mallchat-common/pom.xml
@@ -109,6 +109,11 @@
com.github.ben-manes.caffeine
caffeine
+
+ org.redisson
+ redisson-spring-boot-starter
+ 3.17.1
+
\ No newline at end of file
diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/common/annotation/RedissonLock.java b/mallchat-common/src/main/java/com/abin/mallchat/common/common/annotation/RedissonLock.java
new file mode 100644
index 0000000..eeb7a93
--- /dev/null
+++ b/mallchat-common/src/main/java/com/abin/mallchat/common/common/annotation/RedissonLock.java
@@ -0,0 +1,40 @@
+package com.abin.mallchat.common.common.annotation;
+
+import java.lang.annotation.*;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 分布式锁注解
+ */
+@Retention(RetentionPolicy.RUNTIME)//运行时生效
+@Target(ElementType.METHOD)//作用在方法上
+public @interface RedissonLock {
+ /**
+ * key的前缀,默认取方法全限定名,除非我们在不同方法上对同一个资源做分布式锁,就自己指定
+ *
+ * @return key的前缀
+ */
+ String prefixKey() default "";
+
+ /**
+ * springEl 表达式
+ *
+ * @return 表达式
+ */
+ String key();
+
+ /**
+ * 等待锁的时间,默认-1,不等待直接失败,redisson默认也是-1
+ *
+ * @return 单位秒
+ */
+ int waitTime() default -1;
+
+ /**
+ * 等待锁的时间单位,默认毫秒
+ *
+ * @return 单位
+ */
+ TimeUnit unit() default TimeUnit.MILLISECONDS;
+
+}
diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/common/aspect/FrequencyControlAspect.java b/mallchat-common/src/main/java/com/abin/mallchat/common/common/aspect/FrequencyControlAspect.java
index 3347dc6..58fc660 100644
--- a/mallchat-common/src/main/java/com/abin/mallchat/common/common/aspect/FrequencyControlAspect.java
+++ b/mallchat-common/src/main/java/com/abin/mallchat/common/common/aspect/FrequencyControlAspect.java
@@ -6,6 +6,7 @@ import com.abin.mallchat.common.common.exception.BusinessException;
import com.abin.mallchat.common.common.exception.CommonErrorEnum;
import com.abin.mallchat.common.common.utils.RedisUtils;
import com.abin.mallchat.common.common.utils.RequestHolder;
+import com.abin.mallchat.common.common.utils.SpElUtils;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
@@ -33,8 +34,6 @@ import java.util.*;
public class FrequencyControlAspect {
@Autowired
private RedisUtils redisUtils;
- private final ExpressionParser parser = new SpelExpressionParser();
- private final DefaultParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
@Around("@annotation(com.abin.mallchat.common.common.annotation.FrequencyControl)||@annotation(com.abin.mallchat.common.common.annotation.FrequencyControlContainer)")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
@@ -43,11 +42,11 @@ public class FrequencyControlAspect {
Map keyMap = new HashMap<>();
for (int i = 0; i < annotationsByType.length; i++) {
FrequencyControl frequencyControl = annotationsByType[i];
- String prefix = StrUtil.isBlank(frequencyControl.prefixKey()) ? method.toGenericString() + ":index:" + i : frequencyControl.prefixKey();//默认方法限定名+注解排名(可能多个)
+ String prefix = StrUtil.isBlank(frequencyControl.prefixKey()) ? SpElUtils.getMethodKey(method) + ":index:" + i : frequencyControl.prefixKey();//默认方法限定名+注解排名(可能多个)
String key = "";
switch (frequencyControl.target()) {
case EL:
- key = parseSpEl(method, joinPoint.getArgs(), frequencyControl.spEl());
+ key = SpElUtils.parseSpEl(method, joinPoint.getArgs(), frequencyControl.spEl());
break;
case IP:
key = RequestHolder.get().getIp();
@@ -79,13 +78,5 @@ public class FrequencyControlAspect {
}
}
- private String parseSpEl(Method method, Object[] args, String spEl) {
- String[] params = parameterNameDiscoverer.getParameterNames(method);//解析参数名
- EvaluationContext context = new StandardEvaluationContext();//el解析需要的上下文对象
- for (int i = 0; i < params.length; i++) {
- context.setVariable(params[i], args[i]);//所有参数都作为原材料扔进去
- }
- Expression expression = parser.parseExpression(spEl);
- return expression.getValue(context, String.class);
- }
+
}
diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/common/aspect/RedissonLockAspect.java b/mallchat-common/src/main/java/com/abin/mallchat/common/common/aspect/RedissonLockAspect.java
new file mode 100644
index 0000000..4c5b9c4
--- /dev/null
+++ b/mallchat-common/src/main/java/com/abin/mallchat/common/common/aspect/RedissonLockAspect.java
@@ -0,0 +1,50 @@
+package com.abin.mallchat.common.common.aspect;
+
+import cn.hutool.core.util.StrUtil;
+import com.abin.mallchat.common.common.annotation.FrequencyControl;
+import com.abin.mallchat.common.common.annotation.RedissonLock;
+import com.abin.mallchat.common.common.exception.BusinessException;
+import com.abin.mallchat.common.common.exception.CommonErrorEnum;
+import com.abin.mallchat.common.common.service.LockService;
+import com.abin.mallchat.common.common.utils.RedisUtils;
+import com.abin.mallchat.common.common.utils.RequestHolder;
+import com.abin.mallchat.common.common.utils.SpElUtils;
+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.reflect.MethodSignature;
+import org.redisson.api.RedissonClient;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.DefaultParameterNameDiscoverer;
+import org.springframework.expression.EvaluationContext;
+import org.springframework.expression.Expression;
+import org.springframework.expression.ExpressionParser;
+import org.springframework.expression.spel.standard.SpelExpressionParser;
+import org.springframework.expression.spel.support.StandardEvaluationContext;
+import org.springframework.stereotype.Component;
+
+import java.lang.reflect.Method;
+import java.util.*;
+
+/**
+ * Description: 分布式锁切面
+ * Author: abin
+ * Date: 2023-04-20
+ */
+@Slf4j
+@Aspect
+@Component
+public class RedissonLockAspect {
+ @Autowired
+ private LockService lockService;
+
+ @Around("@annotation(com.abin.mallchat.common.common.annotation.RedissonLock)")
+ public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
+ Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
+ RedissonLock redissonLock = method.getAnnotation(RedissonLock.class);
+ String prefix = StrUtil.isBlank(redissonLock.prefixKey()) ? SpElUtils.getMethodKey(method) : redissonLock.prefixKey();//默认方法限定名+注解排名(可能多个)
+ String key = SpElUtils.parseSpEl(method, joinPoint.getArgs(), redissonLock.key());
+ return lockService.executeWithLockThrows(prefix + ":" + key, redissonLock.waitTime(), redissonLock.unit(), joinPoint::proceed);
+ }
+}
diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/common/config/RedissonConfig.java b/mallchat-common/src/main/java/com/abin/mallchat/common/common/config/RedissonConfig.java
new file mode 100644
index 0000000..da33172
--- /dev/null
+++ b/mallchat-common/src/main/java/com/abin/mallchat/common/common/config/RedissonConfig.java
@@ -0,0 +1,30 @@
+package com.abin.mallchat.common.common.config;
+
+import org.redisson.Redisson;
+import org.redisson.api.RedissonClient;
+import org.redisson.config.Config;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * Description:
+ * Author: abin
+ * Date: 2023-04-22
+ */
+@Configuration
+public class RedissonConfig {
+ @Autowired
+ private RedisProperties redisProperties;
+
+ @Bean
+ public RedissonClient redissonClient() {
+ Config config = new Config();
+ config.useSingleServer()
+ .setAddress("redis://" + redisProperties.getHost() + ":" + redisProperties.getPort())
+ .setPassword(redisProperties.getPassword())
+ .setDatabase(redisProperties.getDatabase());
+ return Redisson.create(config);
+ }
+}
diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/common/exception/CommonErrorEnum.java b/mallchat-common/src/main/java/com/abin/mallchat/common/common/exception/CommonErrorEnum.java
index f023de8..10804e8 100644
--- a/mallchat-common/src/main/java/com/abin/mallchat/common/common/exception/CommonErrorEnum.java
+++ b/mallchat-common/src/main/java/com/abin/mallchat/common/common/exception/CommonErrorEnum.java
@@ -15,6 +15,7 @@ public enum CommonErrorEnum implements ErrorEnum {
SYSTEM_ERROR(-1, "系统出小差了,请稍后再试哦~~"),
PARAM_VALID(-2, "参数校验失败"),
FREQUENCY_LIMIT(-3, "请求太频繁了,请稍后再试哦~~"),
+ LOCK_LIMIT(-4, "请求太频繁了,请稍后再试哦~~"),
;
private Integer code;
private String msg;
diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/common/service/LockService.java b/mallchat-common/src/main/java/com/abin/mallchat/common/common/service/LockService.java
new file mode 100644
index 0000000..98c8ffe
--- /dev/null
+++ b/mallchat-common/src/main/java/com/abin/mallchat/common/common/service/LockService.java
@@ -0,0 +1,51 @@
+package com.abin.mallchat.common.common.service;
+
+import com.abin.mallchat.common.common.exception.BusinessException;
+import com.abin.mallchat.common.common.exception.CommonErrorEnum;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.redisson.api.RLock;
+import org.redisson.api.RedissonClient;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Supplier;
+
+@Service
+@Slf4j
+public class LockService {
+
+ @Autowired
+ private RedissonClient redissonClient;
+
+ public T executeWithLockThrows(String key, int waitTime, TimeUnit unit, SupplierThrow supplier) throws Throwable {
+ RLock lock = redissonClient.getLock(key);
+ boolean lockSuccess = lock.tryLock(waitTime, unit);
+ if (!lockSuccess) {
+ throw new BusinessException(CommonErrorEnum.LOCK_LIMIT);
+ }
+ try {
+ return supplier.get();//执行锁内的代码逻辑
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ @SneakyThrows
+ public T executeWithLock(String key, int waitTime, TimeUnit unit, Supplier supplier) {
+ return executeWithLockThrows(key, waitTime, unit, supplier::get);
+ }
+
+ @FunctionalInterface
+ public interface SupplierThrow {
+
+ /**
+ * Gets a result.
+ *
+ * @return a result
+ */
+ T get() throws Throwable;
+ }
+}
\ No newline at end of file
diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/SpElUtils.java b/mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/SpElUtils.java
new file mode 100644
index 0000000..79c5c8c
--- /dev/null
+++ b/mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/SpElUtils.java
@@ -0,0 +1,35 @@
+package com.abin.mallchat.common.common.utils;
+
+import org.springframework.core.DefaultParameterNameDiscoverer;
+import org.springframework.expression.EvaluationContext;
+import org.springframework.expression.Expression;
+import org.springframework.expression.ExpressionParser;
+import org.springframework.expression.spel.standard.SpelExpressionParser;
+import org.springframework.expression.spel.support.StandardEvaluationContext;
+import org.springframework.stereotype.Component;
+
+import java.lang.reflect.Method;
+
+/**
+ * Description: spring el表达式解析
+ * Author: abin
+ * Date: 2023-04-22
+ */
+public class SpElUtils {
+ private static final ExpressionParser parser = new SpelExpressionParser();
+ private static final DefaultParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
+
+ public static String parseSpEl(Method method, Object[] args, String spEl) {
+ String[] params = parameterNameDiscoverer.getParameterNames(method);//解析参数名
+ EvaluationContext context = new StandardEvaluationContext();//el解析需要的上下文对象
+ for (int i = 0; i < params.length; i++) {
+ context.setVariable(params[i], args[i]);//所有参数都作为原材料扔进去
+ }
+ Expression expression = parser.parseExpression(spEl);
+ return expression.getValue(context, String.class);
+ }
+
+ public static String getMethodKey(Method method){
+ return method.getDeclaringClass()+"#"+method.getName();
+ }
+}
diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/user/service/impl/UserBackpackServiceImpl.java b/mallchat-common/src/main/java/com/abin/mallchat/common/user/service/impl/UserBackpackServiceImpl.java
index 01daeca..d702329 100644
--- a/mallchat-common/src/main/java/com/abin/mallchat/common/user/service/impl/UserBackpackServiceImpl.java
+++ b/mallchat-common/src/main/java/com/abin/mallchat/common/user/service/impl/UserBackpackServiceImpl.java
@@ -1,5 +1,6 @@
package com.abin.mallchat.common.user.service.impl;
+import com.abin.mallchat.common.common.annotation.RedissonLock;
import com.abin.mallchat.common.common.domain.enums.IdempotentEnum;
import com.abin.mallchat.common.common.domain.enums.YesOrNoEnum;
import com.abin.mallchat.common.user.dao.ItemConfigDao;
@@ -32,6 +33,7 @@ public class UserBackpackServiceImpl implements IUserBackpackService {
private ItemCache itemCache;
@Override
+ @RedissonLock(key = "#uid")
public void acquireItem(Long uid, Long itemId, IdempotentEnum idempotentEnum, String businessId) {//todo 分布式锁
String idempotent = getIdempotent(itemId, idempotentEnum, businessId);
UserBackpack userBackpack = userBackpackDao.getByIdp(idempotent);
diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/common/config/SwaggerConfig.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/common/config/SwaggerConfig.java
index 796657e..4446156 100644
--- a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/common/config/SwaggerConfig.java
+++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/common/config/SwaggerConfig.java
@@ -1,10 +1,19 @@
package com.abin.mallchat.custom.common.config;
+import org.springframework.boot.actuate.autoconfigure.endpoint.web.CorsEndpointProperties;
+import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties;
+import org.springframework.boot.actuate.autoconfigure.web.server.ManagementPortType;
+import org.springframework.boot.actuate.endpoint.ExposableEndpoint;
+import org.springframework.boot.actuate.endpoint.web.*;
+import org.springframework.boot.actuate.endpoint.web.annotation.ControllerEndpointsSupplier;
+import org.springframework.boot.actuate.endpoint.web.annotation.ServletEndpointsSupplier;
+import org.springframework.boot.actuate.endpoint.web.servlet.WebMvcEndpointHandlerMapping;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.env.Environment;
import org.springframework.core.env.Profiles;
+import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
@@ -17,6 +26,10 @@ import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2WebMvc;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
/**
* Description:
* Author: abin
@@ -47,21 +60,22 @@ public class SwaggerConfig {
.build();
}
/**
- * swagger 配置
- * @param environment 环境
- */
-// @Bean
-// public Docket docket(Environment environment) {
-//
-// // 设置环境范围
-// Profiles profiles = Profiles.of("dev","test");
-// // 如果在该环境返回内则返回:true,反之返回 false
-// boolean flag = environment.acceptsProfiles(profiles);
-//
-// // 创建一个 swagger 的 bean 实例
-// return new Docket(DocumentationType.SWAGGER_2)
-// .enable(flag) // 是否开启 swagger:true -> 开启,false -> 关闭
-// ;
-// }
+ * 增加如下配置可解决Spring Boot 6.x 与Swagger 3.0.0 不兼容问题
+ **/
+ @Bean
+ public WebMvcEndpointHandlerMapping webEndpointServletHandlerMapping(WebEndpointsSupplier webEndpointsSupplier, ServletEndpointsSupplier servletEndpointsSupplier, ControllerEndpointsSupplier controllerEndpointsSupplier, EndpointMediaTypes endpointMediaTypes, CorsEndpointProperties corsProperties, WebEndpointProperties webEndpointProperties, Environment environment) {
+ List> allEndpoints = new ArrayList();
+ Collection webEndpoints = webEndpointsSupplier.getEndpoints();
+ allEndpoints.addAll(webEndpoints);
+ allEndpoints.addAll(servletEndpointsSupplier.getEndpoints());
+ allEndpoints.addAll(controllerEndpointsSupplier.getEndpoints());
+ String basePath = webEndpointProperties.getBasePath();
+ EndpointMapping endpointMapping = new EndpointMapping(basePath);
+ boolean shouldRegisterLinksMapping = this.shouldRegisterLinksMapping(webEndpointProperties, environment, basePath);
+ return new WebMvcEndpointHandlerMapping(endpointMapping, webEndpoints, endpointMediaTypes, corsProperties.toCorsConfiguration(), new EndpointLinksResolver(allEndpoints, basePath), shouldRegisterLinksMapping, null);
+ }
+ private boolean shouldRegisterLinksMapping(WebEndpointProperties webEndpointProperties, Environment environment, String basePath) {
+ return webEndpointProperties.getDiscovery().isEnabled() && (StringUtils.hasText(basePath) || ManagementPortType.get(environment).equals(ManagementPortType.DIFFERENT));
+ }
}