mirror of https://github.com/elunez/eladmin
Merge branch 'master' of https://github.com/Tiki-77/eladmin
commit
0705b123be
|
@ -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;
|
||||
|
||||
/**
|
||||
* 清理特定用户缓存信息<br>
|
||||
* 用户信息变更时
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<String, Node> cache = new ConcurrentHashMap<>();
|
||||
private final AtomicBoolean expelLock = new AtomicBoolean(true);
|
||||
private long nextMinEvictableTime = 0;
|
||||
|
||||
public Future<JwtUserDto> putIfAbsent(String username, Future<JwtUserDto> 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<Map.Entry<String, Node>> iterator = cache.entrySet().iterator();
|
||||
while (iterator.hasNext()) {
|
||||
Map.Entry<String, Node> 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<JwtUserDto> get(String username) {
|
||||
return nodeToDate(cache.get(username));
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
cache.clear();
|
||||
}
|
||||
|
||||
public void remove(String username) {
|
||||
cache.remove(username);
|
||||
}
|
||||
|
||||
private Future<JwtUserDto> nodeToDate(Node node) {
|
||||
return node == null ? null : node.getData();
|
||||
}
|
||||
|
||||
private static class Node {
|
||||
private final Future<JwtUserDto> data;
|
||||
private final long time;
|
||||
|
||||
public Node(Future<JwtUserDto> data) {
|
||||
this.data = data;
|
||||
this.time = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public Future<JwtUserDto> getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public long getTime() {
|
||||
return time;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<String, Future<JwtUserDto>> 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<JwtUserDto> call=()->getJwtBySearchDb(username);
|
||||
FutureTask<JwtUserDto> ft=new FutureTask<>(call);
|
||||
future=USER_DTO_CACHE.putIfAbsent(username,ft);
|
||||
if(future==null){
|
||||
future=ft;
|
||||
if (future == null) {
|
||||
Callable<JwtUserDto> call = () -> getJwtBySearchDb(username);
|
||||
FutureTask<JwtUserDto> 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是否修改
|
||||
|
|
|
@ -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
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue