feat: Spring AOP + Redisson 实现分布式锁

This commit is contained in:
xiongxiaoyang
2022-06-20 13:20:00 +08:00
parent ac1628aa2a
commit 9bd95d3f28
8 changed files with 208 additions and 22 deletions

View File

@ -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 "";
}

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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())

View File

@ -0,0 +1,3 @@
singleServerConfig:
address: "redis://127.0.0.1:6379"
password: 123456