登录加锁

This commit is contained in:
none 2023-03-12 10:56:44 +08:00
parent 08a9f5ab73
commit 235e36facc
5 changed files with 121 additions and 56 deletions

View File

@ -12,6 +12,7 @@ import xyz.playedu.api.constant.SystemConstant;
import xyz.playedu.api.domain.User;
import xyz.playedu.api.event.UserLoginEvent;
import xyz.playedu.api.exception.LimitException;
import xyz.playedu.api.middleware.Lock;
import xyz.playedu.api.request.frontend.LoginPasswordRequest;
import xyz.playedu.api.service.JWTService;
import xyz.playedu.api.service.UserService;
@ -45,6 +46,7 @@ public class LoginController {
private UserLoginCache userLoginCache;
@PostMapping("/password")
@Lock(key = "user-login:{#req.getEmail()}")
public JsonResponse password(@RequestBody @Validated LoginPasswordRequest req) throws LimitException {
String email = req.getEmail();
userLoginCache.check(email);

View File

@ -0,0 +1,21 @@
package xyz.playedu.api.middleware;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.concurrent.TimeUnit;
/**
* @Author 杭州白书科技有限公司
* @create 2023/3/12 10:44
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Lock {
String key();
long expire() default 10;
TimeUnit timeUnit() default TimeUnit.SECONDS;
}

View File

@ -0,0 +1,46 @@
package xyz.playedu.api.middleware.impl;
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.springframework.stereotype.Component;
import xyz.playedu.api.exception.LimitException;
import xyz.playedu.api.middleware.Lock;
import xyz.playedu.api.util.RedisDistributedLock;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;
/**
* @Author 杭州白书科技有限公司
* @create 2023/3/12 10:45
*/
@Aspect
@Component
public class LockImpl {
private final RedisDistributedLock redisDistributedLock;
public LockImpl(RedisDistributedLock redisDistributedLock) {
this.redisDistributedLock = redisDistributedLock;
}
@Around("@annotation(xyz.playedu.api.middleware.Lock)")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
Lock distributedLock = method.getAnnotation(Lock.class);
String key = distributedLock.key();
long expire = distributedLock.expire();
TimeUnit timeUnit = distributedLock.timeUnit();
boolean success = redisDistributedLock.tryLock(key, expire, timeUnit);
if (!success) {
throw new LimitException("请稍后再试");
}
try {
return joinPoint.proceed();
} finally {
redisDistributedLock.releaseLock(key);
}
}
}

View File

@ -0,0 +1,52 @@
package xyz.playedu.api.util;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;
import java.util.Collections;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
/**
* @Author 杭州白书科技有限公司
* @create 2023/3/12 10:43
*/
@Component
public class RedisDistributedLock {
private final StringRedisTemplate redisTemplate;
private final ThreadLocal<String> lockValue = new ThreadLocal<>();
public RedisDistributedLock(StringRedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}
public boolean tryLock(String key, long expire, TimeUnit timeUnit) {
String value = UUID.randomUUID().toString();
Boolean success = redisTemplate.opsForValue().setIfAbsent(key, value, expire, timeUnit);
if (Boolean.TRUE.equals(success)) {
lockValue.set(value);
return true;
}
return false;
}
public boolean releaseLock(String key) {
String value = lockValue.get();
if (value == null) {
return false;
}
DefaultRedisScript<Boolean> script = new DefaultRedisScript<>(
"if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end",
Boolean.class
);
Boolean success = redisTemplate.execute(script, Collections.singletonList(key), value);
if (Boolean.TRUE.equals(success)) {
lockValue.remove();
return true;
}
return false;
}
}

View File

@ -1,56 +0,0 @@
package xyz.playedu.api.util;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import java.util.ArrayList;
import java.util.List;
/**
* @Author 杭州白书科技有限公司
* @create 2023/3/10 14:48
*/
@Slf4j
public class RedisLockUtil {
public final static String LUA_LOCK_CREATE = """
if redis.call("GET", KEYS[1]) == ARGV[1] then
redis.call("SET", KEYS[1], ARGV[1], "PX", ARGV[2])
return "OK"
else
return redis.call("SET", KEYS[1], ARGV[1], "NX", "PX", ARGV[2])
end
""";
public final static String LUA_LOCK_REMOVE = """
if redis.call("GET", KEYS[1]) == ARGV[1] then
return redis.call("DEL", KEYS[1])
else
return 0
end""";
public static boolean lock(String key, String value, Integer expire) {
DefaultRedisScript<Object> script = new DefaultRedisScript<>();
script.setScriptText(LUA_LOCK_CREATE);
// 脚本中的keys
List<String> keys = new ArrayList<>();
keys.add(key);
Object result = RedisUtil.handler().execute(script, keys, value, expire);
log.info("上锁结果 {}", result);
return false;
}
public static boolean remove(String key, String value) {
DefaultRedisScript<String> script = new DefaultRedisScript<>();
script.setScriptText(LUA_LOCK_REMOVE);
script.setResultType(String.class);
// 脚本中的keys
List<String> keys = new ArrayList<>();
keys.add(key);
String result = RedisUtil.handler().execute(script, keys, value);
log.info("解锁结果 {}", result);
return "OK".equals(result);
}
}