修改小程序登录逻辑
This commit is contained in:
@@ -0,0 +1,211 @@
|
||||
package com.ruoyi.common.core.redis;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
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.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
/**
|
||||
* JDK 1.8 兼容版分布式锁工具类(基于 Redis)
|
||||
*/
|
||||
@Component
|
||||
public class DistributedLockUtil {
|
||||
@Autowired
|
||||
private RedisCache redisCache;
|
||||
|
||||
// 锁默认配置
|
||||
private static final long DEFAULT_EXPIRE_SECONDS = 30;
|
||||
private static final long DEFAULT_ACQUIRE_TIMEOUT_SECONDS = 5;
|
||||
private static final long RENEW_INTERVAL_SECONDS = DEFAULT_EXPIRE_SECONDS / 3;
|
||||
|
||||
// 续期线程池
|
||||
private final ScheduledExecutorService renewExecutor = Executors.newScheduledThreadPool(
|
||||
Runtime.getRuntime().availableProcessors(),
|
||||
new ThreadFactory() {
|
||||
private final AtomicBoolean init = new AtomicBoolean(false);
|
||||
|
||||
@Override
|
||||
public Thread newThread(Runnable r) {
|
||||
Thread thread = new Thread(r, "distributed-lock-renewer");
|
||||
thread.setDaemon(true);
|
||||
if (init.compareAndSet(false, true)) {
|
||||
Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
renewExecutor.shutdown();
|
||||
}
|
||||
}));
|
||||
}
|
||||
return thread;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// 释放锁 Lua 脚本(原子操作)
|
||||
private static final String RELEASE_LOCK_LUA_SCRIPT =
|
||||
"if redis.call('get', KEYS[1]) == ARGV[1] then " +
|
||||
" return redis.call('del', KEYS[1]) " +
|
||||
"else " +
|
||||
" return 0 " +
|
||||
"end";
|
||||
private final DefaultRedisScript<Long> releaseScript = new DefaultRedisScript<>();
|
||||
|
||||
// 初始化 Lua 脚本
|
||||
{
|
||||
releaseScript.setScriptText(RELEASE_LOCK_LUA_SCRIPT);
|
||||
releaseScript.setResultType(Long.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取锁(带自动续期)
|
||||
*/
|
||||
public String acquireLockWithRenewal(String lockKey) {
|
||||
return acquireLockWithRenewal(lockKey, DEFAULT_EXPIRE_SECONDS, DEFAULT_ACQUIRE_TIMEOUT_SECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义参数获取锁
|
||||
*/
|
||||
public String acquireLockWithRenewal(String lockKey, long expireSeconds, long acquireTimeoutSeconds) {
|
||||
String identifier = UUID.randomUUID().toString();
|
||||
long endTime = System.currentTimeMillis() + acquireTimeoutSeconds * 1000;
|
||||
|
||||
while (System.currentTimeMillis() < endTime) {
|
||||
boolean locked = tryLockOnce(lockKey, identifier, expireSeconds);
|
||||
if (locked) {
|
||||
startRenewal(lockKey, identifier, expireSeconds);
|
||||
return identifier;
|
||||
}
|
||||
|
||||
// 指数退避重试
|
||||
try {
|
||||
long sleepMs = calculateBackoffSleep(System.currentTimeMillis() - (endTime - acquireTimeoutSeconds * 1000));
|
||||
Thread.sleep(sleepMs);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 原子释放锁
|
||||
*/
|
||||
public boolean releaseLockSafely(String lockKey, String identifier) {
|
||||
if (identifier == null) {
|
||||
return false;
|
||||
}
|
||||
Long result = (Long) redisCache.redisTemplate.execute(
|
||||
releaseScript,
|
||||
Collections.singletonList(lockKey),
|
||||
identifier
|
||||
);
|
||||
return result != null && result > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 尝试获取一次锁
|
||||
*/
|
||||
private boolean tryLockOnce(String lockKey, String identifier, long expireSeconds) {
|
||||
try {
|
||||
if (!redisCache.hasKey(lockKey)) {
|
||||
redisCache.setCacheObject(lockKey, identifier, (int) expireSeconds, TimeUnit.SECONDS);
|
||||
String storedId = redisCache.getCacheObject(lockKey);
|
||||
return identifier.equals(storedId);
|
||||
}
|
||||
return false;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动锁自动续期
|
||||
*/
|
||||
private void startRenewal(final String lockKey, final String identifier, final long expireSeconds) {
|
||||
final AtomicReference<ScheduledFuture<?>> futureRef = new AtomicReference<>();
|
||||
Runnable renewalTask = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
String storedId = redisCache.getCacheObject(lockKey);
|
||||
if (identifier.equals(storedId)) {
|
||||
redisCache.expire(lockKey, expireSeconds);
|
||||
} else {
|
||||
cancelFuture();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
cancelFuture();
|
||||
}
|
||||
}
|
||||
|
||||
private void cancelFuture() {
|
||||
ScheduledFuture<?> future = futureRef.get();
|
||||
if (future != null && !future.isCancelled()) {
|
||||
future.cancel(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ScheduledFuture<?> future = renewExecutor.scheduleAtFixedRate(
|
||||
renewalTask,
|
||||
RENEW_INTERVAL_SECONDS,
|
||||
RENEW_INTERVAL_SECONDS,
|
||||
TimeUnit.SECONDS
|
||||
);
|
||||
futureRef.set(future);
|
||||
}
|
||||
|
||||
/**
|
||||
* 指数退避算法(计算重试间隔)
|
||||
*/
|
||||
private long calculateBackoffSleep(long elapsedMs) {
|
||||
int retryCount = (int) (elapsedMs / 100);
|
||||
long sleepMs = 100L * (1 << Math.min(retryCount, 10));
|
||||
return Math.min(sleepMs, 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
* 自动释放锁工具(支持 try-with-resources)
|
||||
*/
|
||||
public static class AutoReleaseLock implements AutoCloseable {
|
||||
private final DistributedLockUtil lockUtil;
|
||||
private final String lockKey;
|
||||
private final String identifier;
|
||||
|
||||
public AutoReleaseLock(DistributedLockUtil lockUtil, String lockKey, String identifier) {
|
||||
this.lockUtil = lockUtil;
|
||||
this.lockKey = lockKey;
|
||||
this.identifier = identifier;
|
||||
}
|
||||
|
||||
public boolean isLocked() {
|
||||
return identifier != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (isLocked()) {
|
||||
lockUtil.releaseLockSafely(lockKey, identifier);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 简化锁使用
|
||||
*/
|
||||
public AutoReleaseLock tryLock(String lockKey) {
|
||||
String identifier = acquireLockWithRenewal(lockKey);
|
||||
return new AutoReleaseLock(this, lockKey, identifier);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user