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;