From 9bd95d3f28a5cb622019815d21e7b0d8c77d4354 Mon Sep 17 00:00:00 2001
From: xiongxiaoyang <773861846@qq.com>
Date: Mon, 20 Jun 2022 13:20:00 +0800
Subject: [PATCH] =?UTF-8?q?feat:=20Spring=20AOP=20+=20Redisson=20=E5=AE=9E?=
=?UTF-8?q?=E7=8E=B0=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
---
README.md | 49 ++++++-----
pom.xml | 14 ++++
.../xxyopen/novel/core/annotation/Key.java | 21 +++++
.../xxyopen/novel/core/annotation/Lock.java | 31 +++++++
.../xxyopen/novel/core/aspect/LockAspect.java | 81 +++++++++++++++++++
.../novel/core/config/RedissonConfig.java | 26 ++++++
.../novel/service/impl/BookServiceImpl.java | 5 +-
src/main/resources/redisson.yml | 3 +
8 files changed, 208 insertions(+), 22 deletions(-)
create mode 100644 src/main/java/io/github/xxyopen/novel/core/annotation/Key.java
create mode 100644 src/main/java/io/github/xxyopen/novel/core/annotation/Lock.java
create mode 100644 src/main/java/io/github/xxyopen/novel/core/aspect/LockAspect.java
create mode 100644 src/main/java/io/github/xxyopen/novel/core/config/RedissonConfig.java
create mode 100644 src/main/resources/redisson.yml
diff --git a/README.md b/README.md
index 7bedbc3..ae93bcf 100644
--- a/README.md
+++ b/README.md
@@ -32,26 +32,27 @@ novel 是一套基于时下**最新** Java 技术栈 Spring Boot 3 + Vue 3 开
## 后端技术选型
-| 技术 | 版本 | 说明 | 官网 | 学习 |
-|-------------------------------|:--------------:|---------------------| --------------------------------------- |:---------------------------------------------------------------------------------------:|
-| Spring Boot | 3.0.0-SNAPSHOT | 容器 + MVC 框架 | https://spring.io/projects/spring-boot | [进入](https://youdoc.github.io/course/novel/11.html) |
-| MyBatis | 3.5.9 | ORM 框架 | http://www.mybatis.org | [进入](https://mybatis.org/mybatis-3/zh/index.html) |
-| MyBatis-Plus | 3.5.1 | MyBatis 增强工具 | https://baomidou.com/ | [进入](https://baomidou.com/pages/24112f/) |
-| JJWT | 0.11.5 | JWT 登录支持 | https://github.com/jwtk/jjwt | - |
-| Lombok | 1.18.24 | 简化对象封装工具 | https://github.com/projectlombok/lombok | [进入](https://projectlombok.org/features/all) |
-| Caffeine | 3.1.0 | 本地缓存支持 | https://github.com/ben-manes/caffeine | [进入](https://github.com/ben-manes/caffeine/wiki/Home-zh-CN) |
-| Redis | 7.0 | 分布式缓存支持 | https://redis.io | [进入](https://redis.io/docs) |
-| MySQL | 8.0 | 数据库服务 | https://www.mysql.com | [进入](https://docs.oracle.com/en-us/iaas/mysql-database/doc/getting-started.html) |
-| ShardingSphere-JDBC | 5.1.1 | 数据库分库分表支持 | https://shardingsphere.apache.org | [进入](https://shardingsphere.apache.org/document/5.1.1/cn/overview) |
-| Elasticsearch | 8.2.0 | 搜索引擎服务 | https://www.elastic.co | [进入](https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html) |
-| RabbitMQ | 3.10.2 | 开源消息中间件 | https://www.rabbitmq.com | [进入](https://www.rabbitmq.com/tutorials/tutorial-one-java.html) |
-| XXL-JOB | 2.3.1 | 分布式任务调度平台 | https://www.xuxueli.com/xxl-job | [进入](https://www.xuxueli.com/xxl-job) |
-| Sentinel | 1.8.4 | 流量控制组件 | https://github.com/alibaba/Sentinel | [进入](https://github.com/alibaba/Sentinel/wiki/%E4%B8%BB%E9%A1%B5) |
-| Spring Boot Admin | 3.0.0-M1 | 应用管理和监控 | https://github.com/codecentric/spring-boot-admin | [进入](https://codecentric.github.io/spring-boot-admin/3.0.0-M1) |
-| Undertow | 2.2.17.Final | Java 开发的高性能 Web 服务器 | https://undertow.io | [进入](https://undertow.io/documentation.html) |
-| Docker | - | 应用容器引擎 | https://www.docker.com/ | - |
-| Jenkins | - | 自动化部署工具 | https://github.com/jenkinsci/jenkins | - |
-| Sonarqube | - | 代码质量控制 | https://www.sonarqube.org/ | - |
+| 技术 | 版本 | 说明 | 官网 | 学习 |
+|---------------------|:--------------:|---------------------| --------------------------------------- |:---------------------------------------------------------------------------------------:|
+| Spring Boot | 3.0.0-SNAPSHOT | 容器 + MVC 框架 | https://spring.io/projects/spring-boot | [进入](https://youdoc.github.io/course/novel/11.html) |
+| MyBatis | 3.5.9 | ORM 框架 | http://www.mybatis.org | [进入](https://mybatis.org/mybatis-3/zh/index.html) |
+| MyBatis-Plus | 3.5.1 | MyBatis 增强工具 | https://baomidou.com/ | [进入](https://baomidou.com/pages/24112f/) |
+| JJWT | 0.11.5 | JWT 登录支持 | https://github.com/jwtk/jjwt | - |
+| Lombok | 1.18.24 | 简化对象封装工具 | https://github.com/projectlombok/lombok | [进入](https://projectlombok.org/features/all) |
+| Caffeine | 3.1.0 | 本地缓存支持 | https://github.com/ben-manes/caffeine | [进入](https://github.com/ben-manes/caffeine/wiki/Home-zh-CN) |
+| Redis | 7.0 | 分布式缓存支持 | https://redis.io | [进入](https://redis.io/docs) |
+| MySQL | 8.0 | 数据库服务 | https://www.mysql.com | [进入](https://docs.oracle.com/en-us/iaas/mysql-database/doc/getting-started.html) |
+| Redisson | 3.17.4 | 分布式锁实现 | https://github.com/redisson/redisson | [进入](https://github.com/redisson/redisson/wiki/%E7%9B%AE%E5%BD%95) |
+| ShardingSphere-JDBC | 5.1.1 | 数据库分库分表支持 | https://shardingsphere.apache.org | [进入](https://shardingsphere.apache.org/document/5.1.1/cn/overview) |
+| Elasticsearch | 8.2.0 | 搜索引擎服务 | https://www.elastic.co | [进入](https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html) |
+| RabbitMQ | 3.10.2 | 开源消息中间件 | https://www.rabbitmq.com | [进入](https://www.rabbitmq.com/tutorials/tutorial-one-java.html) |
+| XXL-JOB | 2.3.1 | 分布式任务调度平台 | https://www.xuxueli.com/xxl-job | [进入](https://www.xuxueli.com/xxl-job) |
+| Sentinel | 1.8.4 | 流量控制组件 | https://github.com/alibaba/Sentinel | [进入](https://github.com/alibaba/Sentinel/wiki/%E4%B8%BB%E9%A1%B5) |
+| Spring Boot Admin | 3.0.0-M1 | 应用管理和监控 | https://github.com/codecentric/spring-boot-admin | [进入](https://codecentric.github.io/spring-boot-admin/3.0.0-M1) |
+| Undertow | 2.2.17.Final | Java 开发的高性能 Web 服务器 | https://undertow.io | [进入](https://undertow.io/documentation.html) |
+| Docker | - | 应用容器引擎 | https://www.docker.com/ | - |
+| Jenkins | - | 自动化部署工具 | https://github.com/jenkinsci/jenkins | - |
+| Sonarqube | - | 代码质量控制 | https://www.sonarqube.org/ | - |
**注:更多热门新技术待集成。**
## 前端技术选型
@@ -203,7 +204,7 @@ git clone https://gitee.com/novel_dev_team/novel.git
password: test123456
```
- 2. 修改`src/resources/application.yml`配置文件中的`redis`连接配置
+ 2. 修改`src/resources/application.yml` 和 `src/resources/redisson.yml` 配置文件中的`redis`连接配置
```
spring:
@@ -213,6 +214,12 @@ git clone https://gitee.com/novel_dev_team/novel.git
password: 123456
```
+ ```
+ singleServerConfig:
+ address: "redis://127.0.0.1:6379"
+ password: 123456
+ ```
+
3. 项目根目录下运行如下命令来启动后端服务(有安装 IDE 的可以导入源码到 IDE 中运行)
```
diff --git a/pom.xml b/pom.xml
index 04b3c58..9b69ada 100644
--- a/pom.xml
+++ b/pom.xml
@@ -22,6 +22,7 @@
2.3.1
1.8.4
5.1.1
+ 3.17.4
@@ -160,6 +161,19 @@
spring-boot-starter-security
+
+
+ org.redisson
+ redisson
+ ${redisson.version}
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-aop
+
+
mysql
mysql-connector-java
diff --git a/src/main/java/io/github/xxyopen/novel/core/annotation/Key.java b/src/main/java/io/github/xxyopen/novel/core/annotation/Key.java
new file mode 100644
index 0000000..7a1e2a8
--- /dev/null
+++ b/src/main/java/io/github/xxyopen/novel/core/annotation/Key.java
@@ -0,0 +1,21 @@
+package io.github.xxyopen.novel.core.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+/**
+ * 分布式锁-Key 注解
+ *
+ * @author xiongxiaoyang
+ * @date 2022/6/20
+ */
+@Documented
+@Retention(RUNTIME)
+@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER})
+public @interface Key {
+ String expr() default "";
+}
diff --git a/src/main/java/io/github/xxyopen/novel/core/annotation/Lock.java b/src/main/java/io/github/xxyopen/novel/core/annotation/Lock.java
new file mode 100644
index 0000000..a5e5be2
--- /dev/null
+++ b/src/main/java/io/github/xxyopen/novel/core/annotation/Lock.java
@@ -0,0 +1,31 @@
+package io.github.xxyopen.novel.core.annotation;
+
+import io.github.xxyopen.novel.core.common.constant.ErrorCodeEnum;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+/**
+ * 分布式锁 注解
+ *
+ * @author xiongxiaoyang
+ * @date 2022/6/20
+ */
+@Documented
+@Retention(RUNTIME)
+@Target(METHOD)
+public @interface Lock {
+
+ String prefix();
+
+ boolean isWait() default false;
+
+ long waitTime() default 3L;
+
+ ErrorCodeEnum failCode() default ErrorCodeEnum.OK;
+
+}
diff --git a/src/main/java/io/github/xxyopen/novel/core/aspect/LockAspect.java b/src/main/java/io/github/xxyopen/novel/core/aspect/LockAspect.java
new file mode 100644
index 0000000..a003edd
--- /dev/null
+++ b/src/main/java/io/github/xxyopen/novel/core/aspect/LockAspect.java
@@ -0,0 +1,81 @@
+package io.github.xxyopen.novel.core.aspect;
+
+import io.github.xxyopen.novel.core.annotation.Lock;
+import io.github.xxyopen.novel.core.common.exception.BusinessException;
+import lombok.SneakyThrows;
+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.RLock;
+import org.redisson.api.RedissonClient;
+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.stereotype.Component;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Parameter;
+import java.util.Objects;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 分布式锁 切面
+ *
+ * @author xiongxiaoyang
+ * @date 2022/6/20
+ */
+@Aspect
+@Component
+public record LockAspect(RedissonClient redissonClient) {
+
+ private static final String KEY_PREFIX = "Lock";
+
+ private static final String KEY_SEPARATOR = "::";
+
+ @Around(value = "@annotation(io.github.xxyopen.novel.core.annotation.Lock)")
+ @SneakyThrows
+ public Object doAround(ProceedingJoinPoint joinPoint) {
+ MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
+ Method targetMethod = methodSignature.getMethod();
+ Lock lock = targetMethod.getAnnotation(Lock.class);
+ String lockKey = KEY_PREFIX + buildLockKey(lock.prefix(), targetMethod,
+ joinPoint.getArgs());
+ RLock rLock = redissonClient.getLock(lockKey);
+ if (lock.isWait() ? rLock.tryLock(lock.waitTime(), TimeUnit.SECONDS) : rLock.tryLock()) {
+ try {
+ return joinPoint.proceed();
+ } finally {
+ rLock.unlock();
+ }
+ }
+ throw new BusinessException(lock.failCode());
+ }
+
+ private String buildLockKey(String prefix, Method method, Object[] args) {
+ StringBuilder builder = new StringBuilder();
+ if (Objects.nonNull(prefix) && !prefix.isEmpty()) {
+ builder.append(KEY_SEPARATOR).append(prefix);
+ }
+ Parameter[] parameters = method.getParameters();
+ for (int i = 0; i < parameters.length; i++) {
+ builder.append(KEY_SEPARATOR);
+ if (parameters[i].isAnnotationPresent(io.github.xxyopen.novel.core.annotation.Key.class)) {
+ io.github.xxyopen.novel.core.annotation.Key key = parameters[i].getAnnotation(io.github.xxyopen.novel.core.annotation.Key.class);
+ builder.append(parseKeyExpr(key.expr(), args[i]));
+ }
+ }
+ return builder.toString();
+ }
+
+ private String parseKeyExpr(String expr, Object arg) {
+ if (Objects.isNull(expr) || expr.isEmpty()) {
+ return arg.toString();
+ }
+ ExpressionParser parser = new SpelExpressionParser();
+ Expression expression = parser.parseExpression(expr, new TemplateParserContext());
+ return expression.getValue(arg, String.class);
+ }
+
+}
diff --git a/src/main/java/io/github/xxyopen/novel/core/config/RedissonConfig.java b/src/main/java/io/github/xxyopen/novel/core/config/RedissonConfig.java
new file mode 100644
index 0000000..68136d5
--- /dev/null
+++ b/src/main/java/io/github/xxyopen/novel/core/config/RedissonConfig.java
@@ -0,0 +1,26 @@
+package io.github.xxyopen.novel.core.config;
+
+import lombok.SneakyThrows;
+import org.redisson.Redisson;
+import org.redisson.api.RedissonClient;
+import org.redisson.config.Config;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * Redisson 配置类
+ *
+ * @author xiongxiaoyang
+ * @date 2022/6/20
+ */
+@Configuration
+public class RedissonConfig {
+
+ @Bean
+ @SneakyThrows
+ public RedissonClient redissonClient(){
+ Config config = Config.fromYAML(getClass().getResource("/redisson.yml"));
+ return Redisson.create(config);
+ }
+
+}
diff --git a/src/main/java/io/github/xxyopen/novel/service/impl/BookServiceImpl.java b/src/main/java/io/github/xxyopen/novel/service/impl/BookServiceImpl.java
index 31d07e2..dc0c996 100644
--- a/src/main/java/io/github/xxyopen/novel/service/impl/BookServiceImpl.java
+++ b/src/main/java/io/github/xxyopen/novel/service/impl/BookServiceImpl.java
@@ -3,6 +3,8 @@ package io.github.xxyopen.novel.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import io.github.xxyopen.novel.core.annotation.Key;
+import io.github.xxyopen.novel.core.annotation.Lock;
import io.github.xxyopen.novel.core.auth.UserHolder;
import io.github.xxyopen.novel.core.common.constant.ErrorCodeEnum;
import io.github.xxyopen.novel.core.common.req.PageReqDto;
@@ -200,8 +202,9 @@ public class BookServiceImpl implements BookService {
return RestResp.ok(bookCategoryCacheManager.listCategory(workDirection));
}
+ @Lock(prefix = "userComment")
@Override
- public RestResp saveComment(UserCommentReqDto dto) {
+ public RestResp saveComment(@Key(expr = "#{userId + '::' + bookId}") UserCommentReqDto dto) {
// 校验用户是否已发表评论
QueryWrapper queryWrapper = new QueryWrapper<>();
queryWrapper.eq(DatabaseConsts.BookCommentTable.COLUMN_USER_ID, dto.getUserId())
diff --git a/src/main/resources/redisson.yml b/src/main/resources/redisson.yml
new file mode 100644
index 0000000..bfe1a47
--- /dev/null
+++ b/src/main/resources/redisson.yml
@@ -0,0 +1,3 @@
+singleServerConfig:
+ address: "redis://127.0.0.1:6379"
+ password: 123456