mirror of
https://github.com/201206030/novel.git
synced 2025-04-27 07:30:50 +00:00
feat: Spring AOP + Redisson 实现分布式锁
This commit is contained in:
parent
ac1628aa2a
commit
9bd95d3f28
11
README.md
11
README.md
@ -33,7 +33,7 @@ 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/) |
|
||||
@ -42,6 +42,7 @@ novel 是一套基于时下**最新** Java 技术栈 Spring Boot 3 + Vue 3 开
|
||||
| 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) |
|
||||
@ -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 中运行)
|
||||
|
||||
```
|
||||
|
14
pom.xml
14
pom.xml
@ -22,6 +22,7 @@
|
||||
<xxl-job.version>2.3.1</xxl-job.version>
|
||||
<sentinel.version>1.8.4</sentinel.version>
|
||||
<shardingsphere-jdbc.version>5.1.1</shardingsphere-jdbc.version>
|
||||
<redisson.version>3.17.4</redisson.version>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
@ -160,6 +161,19 @@
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Redisson 相关 -->
|
||||
<dependency>
|
||||
<groupId>org.redisson</groupId>
|
||||
<artifactId>redisson</artifactId>
|
||||
<version>${redisson.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Aop 相关 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-aop</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
|
@ -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 "";
|
||||
}
|
@ -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;
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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<Void> saveComment(UserCommentReqDto dto) {
|
||||
public RestResp<Void> saveComment(@Key(expr = "#{userId + '::' + bookId}") UserCommentReqDto dto) {
|
||||
// 校验用户是否已发表评论
|
||||
QueryWrapper<BookComment> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.eq(DatabaseConsts.BookCommentTable.COLUMN_USER_ID, dto.getUserId())
|
||||
|
3
src/main/resources/redisson.yml
Normal file
3
src/main/resources/redisson.yml
Normal file
@ -0,0 +1,3 @@
|
||||
singleServerConfig:
|
||||
address: "redis://127.0.0.1:6379"
|
||||
password: 123456
|
Loading…
x
Reference in New Issue
Block a user