From 6941b984be66806eaad983495ed5f7fb34112230 Mon Sep 17 00:00:00 2001
From: TikiWong <778563781@qq.com>
Date: Thu, 27 Jan 2022 14:18:41 +0800
Subject: [PATCH 1/2] =?UTF-8?q?[=E4=BB=A3=E7=A0=81=E4=BC=98=E5=8C=96]=20?=
=?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=94=AF=E6=8C=81=E9=85=8D=E7=BD=AE=E7=9A=84?=
=?UTF-8?q?=E5=8F=AF=E5=9B=9E=E6=94=B6=E7=94=A8=E6=88=B7=E7=BC=93=E5=AD=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../security/service/UserCacheClean.java | 8 +-
.../security/service/UserCacheManager.java | 110 ++++++++++++++++++
.../service/UserDetailsServiceImpl.java | 37 +++---
.../src/main/resources/config/application.yml | 9 ++
.../test/java/me/zhengjie/LoginCacheTest.java | 20 ++++
5 files changed, 161 insertions(+), 23 deletions(-)
create mode 100644 eladmin-system/src/main/java/me/zhengjie/modules/security/service/UserCacheManager.java
diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/security/service/UserCacheClean.java b/eladmin-system/src/main/java/me/zhengjie/modules/security/service/UserCacheClean.java
index 70292269..fdddde31 100644
--- a/eladmin-system/src/main/java/me/zhengjie/modules/security/service/UserCacheClean.java
+++ b/eladmin-system/src/main/java/me/zhengjie/modules/security/service/UserCacheClean.java
@@ -16,6 +16,7 @@
package me.zhengjie.modules.security.service;
+import lombok.AllArgsConstructor;
import me.zhengjie.utils.StringUtils;
import org.springframework.stereotype.Component;
@@ -25,8 +26,11 @@ import org.springframework.stereotype.Component;
* @apiNote: 用于清理 用户登录信息缓存,为防止Spring循环依赖与安全考虑 ,单独构成工具类
*/
@Component
+@AllArgsConstructor
public class UserCacheClean {
+ private final UserCacheManager userCacheManager;
+
/**
* 清理特定用户缓存信息
* 用户信息变更时
@@ -35,7 +39,7 @@ public class UserCacheClean {
*/
public void cleanUserCache(String userName) {
if (StringUtils.isNotEmpty(userName)) {
- UserDetailsServiceImpl.USER_DTO_CACHE.remove(userName);
+ userCacheManager.remove(userName);
}
}
@@ -44,6 +48,6 @@ public class UserCacheClean {
* ,如发生角色授权信息变化,可以简便的全部失效缓存
*/
public void cleanAll() {
- UserDetailsServiceImpl.USER_DTO_CACHE.clear();
+ userCacheManager.clear();
}
}
diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/security/service/UserCacheManager.java b/eladmin-system/src/main/java/me/zhengjie/modules/security/service/UserCacheManager.java
new file mode 100644
index 00000000..3f8a8a4a
--- /dev/null
+++ b/eladmin-system/src/main/java/me/zhengjie/modules/security/service/UserCacheManager.java
@@ -0,0 +1,110 @@
+package me.zhengjie.modules.security.service;
+
+import lombok.extern.slf4j.Slf4j;
+import me.zhengjie.modules.security.service.dto.JwtUserDto;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+import java.util.Iterator;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Future;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * 用户缓存
+ *
+ * @author TikiWong
+ * @date 2022/1/27 8:23
+ **/
+@Slf4j
+@Component
+public class UserCacheManager {
+
+ @Value("${user-cache.min-evictable-size}")
+ private int minEvictableSize;
+ @Value("${user-cache.min-evictable-interval}")
+ private long minEvictableInterval;
+ @Value("${user-cache.min-idle-time}")
+ private long minIdleTime;
+
+ private final Map cache = new ConcurrentHashMap<>();
+ private final AtomicBoolean expelLock = new AtomicBoolean(true);
+ private long nextMinEvictableTime = 0;
+
+ public Future putIfAbsent(String username, Future ft) {
+ Node tryNode = new Node(ft);
+ Node node = cache.putIfAbsent(username, tryNode);
+ expel();
+ return nodeToDate(node);
+ }
+
+ /**
+ * 缓存回收
+ * 为避免超过边界后回收热点数据设置了最小生存时间
+ * 回收时会保留在最小生存时间内的数据
+ **/
+ public void expel() {
+ long now = System.currentTimeMillis();
+ if (cache.size() < minEvictableSize ||
+ now < nextMinEvictableTime &&
+ !expelLock.compareAndSet(true, false)) {
+ return;
+ }
+ long oldestTime = now;
+ int evictedCount = 0;
+ try {
+ Iterator> iterator = cache.entrySet().iterator();
+ while (iterator.hasNext()) {
+ Map.Entry entry = iterator.next();
+ long nodeTime = entry.getValue().getTime();
+ if (nodeTime + minIdleTime < now) {
+ iterator.remove();
+ evictedCount++;
+ }
+ oldestTime = Math.min(oldestTime, nodeTime);
+ }
+ } finally {
+ this.nextMinEvictableTime = Math.max(now + minEvictableInterval, oldestTime);
+ expelLock.set(true);
+ log.info("回收掉【{}】条用户缓存, 剩余缓存数为【{}】,下次可回收时间为【{}】秒后",
+ evictedCount,
+ cache.size(),
+ (this.nextMinEvictableTime - now) / 1000);
+ }
+ }
+
+ public Future get(String username) {
+ return nodeToDate(cache.get(username));
+ }
+
+ public void clear() {
+ cache.clear();
+ }
+
+ public void remove(String username) {
+ cache.remove(username);
+ }
+
+ private Future nodeToDate(Node node) {
+ return node == null ? null : node.getData();
+ }
+
+ private static class Node {
+ private final Future data;
+ private final long time;
+
+ public Node(Future data) {
+ this.data = data;
+ this.time = System.currentTimeMillis();
+ }
+
+ public Future getData() {
+ return data;
+ }
+
+ public long getTime() {
+ return time;
+ }
+ }
+}
diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/security/service/UserDetailsServiceImpl.java b/eladmin-system/src/main/java/me/zhengjie/modules/security/service/UserDetailsServiceImpl.java
index 51d09fb8..7e5befd9 100644
--- a/eladmin-system/src/main/java/me/zhengjie/modules/security/service/UserDetailsServiceImpl.java
+++ b/eladmin-system/src/main/java/me/zhengjie/modules/security/service/UserDetailsServiceImpl.java
@@ -29,7 +29,6 @@ import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.List;
-import java.util.Map;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
@@ -45,17 +44,12 @@ public class UserDetailsServiceImpl implements UserDetailsService {
private final DataService dataService;
private final LoginProperties loginProperties;
+ private final UserCacheManager USER_DTO_CACHE;
+
public void setEnableCache(boolean enableCache) {
this.loginProperties.setCacheEnable(enableCache);
}
- /**
- * 用户信息缓存
- *
- * @see {@link UserCacheClean}
- */
-
- final static Map> USER_DTO_CACHE = new ConcurrentHashMap<>();
public static ExecutorService executor = newThreadPool();
@Override
@@ -68,7 +62,7 @@ public class UserDetailsServiceImpl implements UserDetailsService {
user = userService.findByName(username);
} catch (EntityNotFoundException e) {
// SpringSecurity会自动转换UsernameNotFoundException为BadCredentialsException
- throw new UsernameNotFoundException("", e);
+ throw new UsernameNotFoundException(username, e);
}
if (user == null) {
throw new UsernameNotFoundException("");
@@ -85,25 +79,26 @@ public class UserDetailsServiceImpl implements UserDetailsService {
return jwtUserDto;
}
- if (future==null) {
- Callable call=()->getJwtBySearchDb(username);
- FutureTask ft=new FutureTask<>(call);
- future=USER_DTO_CACHE.putIfAbsent(username,ft);
- if(future==null){
- future=ft;
+ if (future == null) {
+ Callable call = () -> getJwtBySearchDb(username);
+ FutureTask ft = new FutureTask<>(call);
+ future = USER_DTO_CACHE.putIfAbsent(username, ft);
+ if (future == null) {
+ future = ft;
executor.submit(ft);
}
- try{
+ try {
return future.get();
- }catch(CancellationException e){
+ } catch (CancellationException e) {
USER_DTO_CACHE.remove(username);
- }catch (InterruptedException | ExecutionException e) {
+ System.out.println("error" + Thread.currentThread().getName());
+ } catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e.getMessage());
}
- }else{
+ } else {
try {
- jwtUserDto=future.get();
- }catch (InterruptedException | ExecutionException e) {
+ jwtUserDto = future.get();
+ } catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e.getMessage());
}
// 检查dataScope是否修改
diff --git a/eladmin-system/src/main/resources/config/application.yml b/eladmin-system/src/main/resources/config/application.yml
index 7b5316cb..8a3b228a 100644
--- a/eladmin-system/src/main/resources/config/application.yml
+++ b/eladmin-system/src/main/resources/config/application.yml
@@ -54,3 +54,12 @@ code:
#密码加密传输,前端公钥加密,后端私钥解密
rsa:
private_key: MIIBUwIBADANBgkqhkiG9w0BAQEFAASCAT0wggE5AgEAAkEA0vfvyTdGJkdbHkB8mp0f3FE0GYP3AYPaJF7jUd1M0XxFSE2ceK3k2kw20YvQ09NJKk+OMjWQl9WitG9pB6tSCQIDAQABAkA2SimBrWC2/wvauBuYqjCFwLvYiRYqZKThUS3MZlebXJiLB+Ue/gUifAAKIg1avttUZsHBHrop4qfJCwAI0+YRAiEA+W3NK/RaXtnRqmoUUkb59zsZUBLpvZgQPfj1MhyHDz0CIQDYhsAhPJ3mgS64NbUZmGWuuNKp5coY2GIj/zYDMJp6vQIgUueLFXv/eZ1ekgz2Oi67MNCk5jeTF2BurZqNLR3MSmUCIFT3Q6uHMtsB9Eha4u7hS31tj1UWE+D+ADzp59MGnoftAiBeHT7gDMuqeJHPL4b+kC+gzV4FGTfhR9q3tTbklZkD2A==
+
+# 内存用户缓存配置
+user-cache:
+ # 最小回收数(当缓存数量达到此值时进行回收)
+ min-evictable-size: 512
+ # 最小回收间隔
+ min-evictable-interval: 1800000
+ # 最小存活时间 (ms)
+ min-idle-time: 3600000
\ No newline at end of file
diff --git a/eladmin-system/src/test/java/me/zhengjie/LoginCacheTest.java b/eladmin-system/src/test/java/me/zhengjie/LoginCacheTest.java
index ef608495..4cae4a8f 100644
--- a/eladmin-system/src/test/java/me/zhengjie/LoginCacheTest.java
+++ b/eladmin-system/src/test/java/me/zhengjie/LoginCacheTest.java
@@ -41,4 +41,24 @@ public class LoginCacheTest {
System.out.print("使用缓存:" + (end1 - start1) + "毫秒\n 不使用缓存:" + (end2 - start2) + "毫秒");
}
+ @Test
+ public void testCacheManager() throws InterruptedException {
+ int size = 1000;
+ CountDownLatch latch = new CountDownLatch(size);
+ for (int i = 0; i < size; i++) {
+ int mod = i % 10;
+ executor.submit(() -> {
+ try {
+ Thread.sleep(mod * 2 + (int) (Math.random() * 10000));
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ userDetailsService.loadUserByUsername("admin" + mod);
+ latch.countDown();
+ System.out.println("剩余未完成数量" + latch.getCount());
+ });
+ }
+ latch.await();
+ }
+
}
From c9614bd77c67d97c8532b20f7f831de970ed632f Mon Sep 17 00:00:00 2001
From: TikiWong <778563781@qq.com>
Date: Thu, 27 Jan 2022 14:29:59 +0800
Subject: [PATCH 2/2] =?UTF-8?q?=E7=AC=94=E8=AF=AF?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../zhengjie/modules/security/service/UserCacheManager.java | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/security/service/UserCacheManager.java b/eladmin-system/src/main/java/me/zhengjie/modules/security/service/UserCacheManager.java
index 3f8a8a4a..8eaccd5f 100644
--- a/eladmin-system/src/main/java/me/zhengjie/modules/security/service/UserCacheManager.java
+++ b/eladmin-system/src/main/java/me/zhengjie/modules/security/service/UserCacheManager.java
@@ -47,8 +47,8 @@ public class UserCacheManager {
public void expel() {
long now = System.currentTimeMillis();
if (cache.size() < minEvictableSize ||
- now < nextMinEvictableTime &&
- !expelLock.compareAndSet(true, false)) {
+ now < nextMinEvictableTime ||
+ !expelLock.compareAndSet(true, false)) {
return;
}
long oldestTime = now;