mirror of https://github.com/elunez/eladmin
				
				
				
			[新增功能](master): 用户登录缓存改为存储到 Redis
							parent
							
								
									0e7c4fcbff
								
							
						
					
					
						commit
						78b7d21ddc
					
				| 
						 | 
				
			
			@ -20,7 +20,7 @@ import me.zhengjie.annotation.AnonymousAccess;
 | 
			
		|||
import me.zhengjie.modules.security.config.bean.SecurityProperties;
 | 
			
		||||
import me.zhengjie.modules.security.security.*;
 | 
			
		||||
import me.zhengjie.modules.security.service.OnlineUserService;
 | 
			
		||||
import me.zhengjie.modules.security.service.UserCacheClean;
 | 
			
		||||
import me.zhengjie.modules.security.service.UserCacheManager;
 | 
			
		||||
import me.zhengjie.utils.enums.RequestMethodEnum;
 | 
			
		||||
import org.springframework.context.ApplicationContext;
 | 
			
		||||
import org.springframework.context.annotation.Bean;
 | 
			
		||||
| 
						 | 
				
			
			@ -58,7 +58,7 @@ public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
 | 
			
		|||
    private final ApplicationContext applicationContext;
 | 
			
		||||
    private final SecurityProperties properties;
 | 
			
		||||
    private final OnlineUserService onlineUserService;
 | 
			
		||||
    private final UserCacheClean userCacheClean;
 | 
			
		||||
    private final UserCacheManager userCacheManager;
 | 
			
		||||
 | 
			
		||||
    @Bean
 | 
			
		||||
    GrantedAuthorityDefaults grantedAuthorityDefaults() {
 | 
			
		||||
| 
						 | 
				
			
			@ -138,7 +138,7 @@ public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    private TokenConfigurer securityConfigurerAdapter() {
 | 
			
		||||
        return new TokenConfigurer(tokenProvider, properties, onlineUserService, userCacheClean);
 | 
			
		||||
        return new TokenConfigurer(tokenProvider, properties, onlineUserService, userCacheManager);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private Map<String, Set<String>> getAnonymousUrl(Map<RequestMappingInfo, HandlerMethod> handlerMethodMap) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -20,7 +20,6 @@ import com.wf.captcha.base.Captcha;
 | 
			
		|||
import lombok.Data;
 | 
			
		||||
import me.zhengjie.exception.BadConfigurationException;
 | 
			
		||||
import me.zhengjie.utils.StringUtils;
 | 
			
		||||
 | 
			
		||||
import java.awt.*;
 | 
			
		||||
import java.util.Objects;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -40,19 +39,12 @@ public class LoginProperties {
 | 
			
		|||
 | 
			
		||||
    private LoginCode loginCode;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 用户登录信息缓存
 | 
			
		||||
     */
 | 
			
		||||
    private boolean cacheEnable;
 | 
			
		||||
    public static final String cacheKey = "USER-LOGIN-DATA";
 | 
			
		||||
 | 
			
		||||
    public boolean isSingleLogin() {
 | 
			
		||||
        return singleLogin;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public boolean isCacheEnable() {
 | 
			
		||||
        return cacheEnable;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取验证码生产类
 | 
			
		||||
     *
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -18,7 +18,7 @@ package me.zhengjie.modules.security.security;
 | 
			
		|||
import lombok.RequiredArgsConstructor;
 | 
			
		||||
import me.zhengjie.modules.security.config.bean.SecurityProperties;
 | 
			
		||||
import me.zhengjie.modules.security.service.OnlineUserService;
 | 
			
		||||
import me.zhengjie.modules.security.service.UserCacheClean;
 | 
			
		||||
import me.zhengjie.modules.security.service.UserCacheManager;
 | 
			
		||||
import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
 | 
			
		||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
 | 
			
		||||
import org.springframework.security.web.DefaultSecurityFilterChain;
 | 
			
		||||
| 
						 | 
				
			
			@ -33,11 +33,11 @@ public class TokenConfigurer extends SecurityConfigurerAdapter<DefaultSecurityFi
 | 
			
		|||
    private final TokenProvider tokenProvider;
 | 
			
		||||
    private final SecurityProperties properties;
 | 
			
		||||
    private final OnlineUserService onlineUserService;
 | 
			
		||||
    private final UserCacheClean userCacheClean;
 | 
			
		||||
    private final UserCacheManager userCacheManager;
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void configure(HttpSecurity http) {
 | 
			
		||||
        TokenFilter customFilter = new TokenFilter(tokenProvider, properties, onlineUserService, userCacheClean);
 | 
			
		||||
        TokenFilter customFilter = new TokenFilter(tokenProvider, properties, onlineUserService, userCacheManager);
 | 
			
		||||
        http.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -18,7 +18,7 @@ package me.zhengjie.modules.security.security;
 | 
			
		|||
import cn.hutool.core.util.StrUtil;
 | 
			
		||||
import io.jsonwebtoken.ExpiredJwtException;
 | 
			
		||||
import me.zhengjie.modules.security.config.bean.SecurityProperties;
 | 
			
		||||
import me.zhengjie.modules.security.service.UserCacheClean;
 | 
			
		||||
import me.zhengjie.modules.security.service.UserCacheManager;
 | 
			
		||||
import me.zhengjie.modules.security.service.dto.OnlineUserDto;
 | 
			
		||||
import me.zhengjie.modules.security.service.OnlineUserService;
 | 
			
		||||
import org.slf4j.Logger;
 | 
			
		||||
| 
						 | 
				
			
			@ -27,7 +27,6 @@ import org.springframework.security.core.Authentication;
 | 
			
		|||
import org.springframework.security.core.context.SecurityContextHolder;
 | 
			
		||||
import org.springframework.util.StringUtils;
 | 
			
		||||
import org.springframework.web.filter.GenericFilterBean;
 | 
			
		||||
 | 
			
		||||
import javax.servlet.FilterChain;
 | 
			
		||||
import javax.servlet.ServletException;
 | 
			
		||||
import javax.servlet.ServletRequest;
 | 
			
		||||
| 
						 | 
				
			
			@ -46,19 +45,19 @@ public class TokenFilter extends GenericFilterBean {
 | 
			
		|||
    private final TokenProvider tokenProvider;
 | 
			
		||||
    private final SecurityProperties properties;
 | 
			
		||||
    private final OnlineUserService onlineUserService;
 | 
			
		||||
    private final UserCacheClean userCacheClean;
 | 
			
		||||
    private final UserCacheManager userCacheManager;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @param tokenProvider     Token
 | 
			
		||||
     * @param properties        JWT
 | 
			
		||||
     * @param onlineUserService 用户在线
 | 
			
		||||
     * @param userCacheClean    用户缓存清理工具
 | 
			
		||||
     * @param userCacheManager    用户缓存工具
 | 
			
		||||
     */
 | 
			
		||||
    public TokenFilter(TokenProvider tokenProvider, SecurityProperties properties, OnlineUserService onlineUserService, UserCacheClean userCacheClean) {
 | 
			
		||||
    public TokenFilter(TokenProvider tokenProvider, SecurityProperties properties, OnlineUserService onlineUserService, UserCacheManager userCacheManager) {
 | 
			
		||||
        this.properties = properties;
 | 
			
		||||
        this.onlineUserService = onlineUserService;
 | 
			
		||||
        this.tokenProvider = tokenProvider;
 | 
			
		||||
        this.userCacheClean = userCacheClean;
 | 
			
		||||
        this.userCacheManager = userCacheManager;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
| 
						 | 
				
			
			@ -77,7 +76,7 @@ public class TokenFilter extends GenericFilterBean {
 | 
			
		|||
                cleanUserCache = true;
 | 
			
		||||
            } finally {
 | 
			
		||||
                if (cleanUserCache || Objects.isNull(onlineUserDto)) {
 | 
			
		||||
                    userCacheClean.cleanUserCache(String.valueOf(tokenProvider.getClaims(token).get(TokenProvider.AUTHORITIES_KEY)));
 | 
			
		||||
                    userCacheManager.cleanUserCache(String.valueOf(tokenProvider.getClaims(token).get(TokenProvider.AUTHORITIES_KEY)));
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            if (onlineUserDto != null && StringUtils.hasText(token)) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,53 +0,0 @@
 | 
			
		|||
/*
 | 
			
		||||
 * Copyright 2019-2020 the original author or authors.
 | 
			
		||||
 *
 | 
			
		||||
 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
 * you may not use this file except in compliance with the License.
 | 
			
		||||
 * You may obtain a copy of the License at
 | 
			
		||||
 *
 | 
			
		||||
 *      http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 *
 | 
			
		||||
 * Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
 * See the License for the specific language governing permissions and
 | 
			
		||||
 * limitations under the License.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package me.zhengjie.modules.security.service;
 | 
			
		||||
 | 
			
		||||
import lombok.AllArgsConstructor;
 | 
			
		||||
import me.zhengjie.utils.StringUtils;
 | 
			
		||||
import org.springframework.stereotype.Component;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @author: liaojinlong
 | 
			
		||||
 * @date: 2020/6/11 18:01
 | 
			
		||||
 * @apiNote: 用于清理 用户登录信息缓存,为防止Spring循环依赖与安全考虑 ,单独构成工具类
 | 
			
		||||
 */
 | 
			
		||||
@Component
 | 
			
		||||
@AllArgsConstructor
 | 
			
		||||
public class UserCacheClean {
 | 
			
		||||
 | 
			
		||||
    private final UserCacheManager userCacheManager;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 清理特定用户缓存信息<br>
 | 
			
		||||
     * 用户信息变更时
 | 
			
		||||
     *
 | 
			
		||||
     * @param userName /
 | 
			
		||||
     */
 | 
			
		||||
    public void cleanUserCache(String userName) {
 | 
			
		||||
        if (StringUtils.isNotEmpty(userName)) {
 | 
			
		||||
            userCacheManager.remove(userName);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 清理所有用户的缓存信息<br>
 | 
			
		||||
     * ,如发生角色授权信息变化,可以简便的全部失效缓存
 | 
			
		||||
     */
 | 
			
		||||
    public void cleanAll() {
 | 
			
		||||
        userCacheManager.clear();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,110 +1,82 @@
 | 
			
		|||
/*
 | 
			
		||||
 *  Copyright 2019-2020 Zheng Jie
 | 
			
		||||
 *
 | 
			
		||||
 *  Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
 *  you may not use this file except in compliance with the License.
 | 
			
		||||
 *  You may obtain a copy of the License at
 | 
			
		||||
 *
 | 
			
		||||
 *  http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 *
 | 
			
		||||
 *  Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 *  distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
 *  See the License for the specific language governing permissions and
 | 
			
		||||
 *  limitations under the License.
 | 
			
		||||
 */
 | 
			
		||||
package me.zhengjie.modules.security.service;
 | 
			
		||||
 | 
			
		||||
import lombok.extern.slf4j.Slf4j;
 | 
			
		||||
import cn.hutool.core.util.RandomUtil;
 | 
			
		||||
import me.zhengjie.modules.security.config.bean.LoginProperties;
 | 
			
		||||
import me.zhengjie.modules.security.service.dto.JwtUserDto;
 | 
			
		||||
import me.zhengjie.utils.RedisUtils;
 | 
			
		||||
import me.zhengjie.utils.StringUtils;
 | 
			
		||||
import org.springframework.beans.factory.annotation.Value;
 | 
			
		||||
import org.springframework.scheduling.annotation.Async;
 | 
			
		||||
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;
 | 
			
		||||
import javax.annotation.Resource;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 用户缓存
 | 
			
		||||
 *
 | 
			
		||||
 * @author TikiWong
 | 
			
		||||
 * @date 2022/1/27 8:23
 | 
			
		||||
 * @author Zheng Jie
 | 
			
		||||
 * @description 用户缓存管理
 | 
			
		||||
 * @date 2022-05-26
 | 
			
		||||
 **/
 | 
			
		||||
@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;
 | 
			
		||||
    @Resource
 | 
			
		||||
    private RedisUtils redisUtils;
 | 
			
		||||
    @Value("${login.user-cache.idle-time}")
 | 
			
		||||
    private long idleTime;
 | 
			
		||||
 | 
			
		||||
    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);
 | 
			
		||||
    /**
 | 
			
		||||
     * 返回用户缓存
 | 
			
		||||
     * @param userName 用户名
 | 
			
		||||
     * @return JwtUserDto
 | 
			
		||||
     */
 | 
			
		||||
    public JwtUserDto getUserCache(String userName) {
 | 
			
		||||
        if (StringUtils.isNotEmpty(userName)) {
 | 
			
		||||
            // 获取数据
 | 
			
		||||
            Object obj = redisUtils.hget(LoginProperties.cacheKey, userName);
 | 
			
		||||
            if(obj != null){
 | 
			
		||||
                return (JwtUserDto)obj;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 缓存回收
 | 
			
		||||
     * 为避免超过边界后回收热点数据设置了最小生存时间
 | 
			
		||||
     * 回收时会保留在最小生存时间内的数据
 | 
			
		||||
     **/
 | 
			
		||||
    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);
 | 
			
		||||
     *  添加缓存到Redis
 | 
			
		||||
     * @param userName 用户名
 | 
			
		||||
     */
 | 
			
		||||
    @Async
 | 
			
		||||
    public void addUserCache(String userName, JwtUserDto user) {
 | 
			
		||||
        if (StringUtils.isNotEmpty(userName)) {
 | 
			
		||||
            // 添加数据, 避免数据同时过期
 | 
			
		||||
            long time = idleTime + RandomUtil.randomInt(900, 1800);
 | 
			
		||||
            redisUtils.hset(LoginProperties.cacheKey, userName, user, time);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    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;
 | 
			
		||||
    /**
 | 
			
		||||
     * 清理用户缓存信息
 | 
			
		||||
     * 用户信息变更时
 | 
			
		||||
     * @param userName 用户名
 | 
			
		||||
     */
 | 
			
		||||
    @Async
 | 
			
		||||
    public void cleanUserCache(String userName) {
 | 
			
		||||
        if (StringUtils.isNotEmpty(userName)) {
 | 
			
		||||
            // 清除数据
 | 
			
		||||
            redisUtils.hdel(LoginProperties.cacheKey, userName);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -16,50 +16,38 @@
 | 
			
		|||
package me.zhengjie.modules.security.service;
 | 
			
		||||
 | 
			
		||||
import lombok.RequiredArgsConstructor;
 | 
			
		||||
import lombok.extern.slf4j.Slf4j;
 | 
			
		||||
import me.zhengjie.exception.BadRequestException;
 | 
			
		||||
import me.zhengjie.exception.EntityNotFoundException;
 | 
			
		||||
import me.zhengjie.modules.security.config.bean.LoginProperties;
 | 
			
		||||
import me.zhengjie.modules.security.service.dto.JwtUserDto;
 | 
			
		||||
import me.zhengjie.modules.system.service.DataService;
 | 
			
		||||
import me.zhengjie.modules.system.service.RoleService;
 | 
			
		||||
import me.zhengjie.modules.system.service.UserService;
 | 
			
		||||
import me.zhengjie.modules.system.service.dto.UserDto;
 | 
			
		||||
import me.zhengjie.modules.system.service.dto.UserLoginDto;
 | 
			
		||||
import org.springframework.security.core.userdetails.UserDetailsService;
 | 
			
		||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
 | 
			
		||||
import org.springframework.stereotype.Service;
 | 
			
		||||
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.concurrent.*;
 | 
			
		||||
import java.util.concurrent.atomic.AtomicInteger;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @author Zheng Jie
 | 
			
		||||
 * @date 2018-11-22
 | 
			
		||||
 */
 | 
			
		||||
@Slf4j
 | 
			
		||||
@RequiredArgsConstructor
 | 
			
		||||
@Service("userDetailsService")
 | 
			
		||||
public class UserDetailsServiceImpl implements UserDetailsService {
 | 
			
		||||
    private final UserService userService;
 | 
			
		||||
    private final RoleService roleService;
 | 
			
		||||
    private final DataService dataService;
 | 
			
		||||
    private final LoginProperties loginProperties;
 | 
			
		||||
 | 
			
		||||
    private final UserCacheManager USER_DTO_CACHE;
 | 
			
		||||
 | 
			
		||||
    public void setEnableCache(boolean enableCache) {
 | 
			
		||||
        this.loginProperties.setCacheEnable(enableCache);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static ExecutorService executor = newThreadPool();
 | 
			
		||||
    private final UserCacheManager userCacheManager;
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public JwtUserDto loadUserByUsername(String username) {
 | 
			
		||||
        JwtUserDto jwtUserDto = null;
 | 
			
		||||
        Future<JwtUserDto> future = USER_DTO_CACHE.get(username);
 | 
			
		||||
        if (!loginProperties.isCacheEnable()) {
 | 
			
		||||
            UserDto user;
 | 
			
		||||
        JwtUserDto jwtUserDto = userCacheManager.getUserCache(username);
 | 
			
		||||
        if(jwtUserDto == null){
 | 
			
		||||
            UserLoginDto user;
 | 
			
		||||
            try {
 | 
			
		||||
                user = userService.findByName(username);
 | 
			
		||||
                user = userService.getLoginData(username);
 | 
			
		||||
            } catch (EntityNotFoundException e) {
 | 
			
		||||
                // SpringSecurity会自动转换UsernameNotFoundException为BadCredentialsException
 | 
			
		||||
                throw new UsernameNotFoundException(username, e);
 | 
			
		||||
| 
						 | 
				
			
			@ -75,85 +63,10 @@ public class UserDetailsServiceImpl implements UserDetailsService {
 | 
			
		|||
                        dataService.getDeptIds(user),
 | 
			
		||||
                        roleService.mapToGrantedAuthorities(user)
 | 
			
		||||
                );
 | 
			
		||||
                // 添加缓存数据
 | 
			
		||||
                userCacheManager.addUserCache(username, jwtUserDto);
 | 
			
		||||
            }
 | 
			
		||||
            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;
 | 
			
		||||
                executor.submit(ft);
 | 
			
		||||
            }
 | 
			
		||||
            try {
 | 
			
		||||
                return future.get();
 | 
			
		||||
            } catch (CancellationException e) {
 | 
			
		||||
                USER_DTO_CACHE.remove(username);
 | 
			
		||||
                System.out.println("error" + Thread.currentThread().getName());
 | 
			
		||||
            } catch (InterruptedException | ExecutionException e) {
 | 
			
		||||
                throw new RuntimeException(e.getMessage());
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            try {
 | 
			
		||||
                jwtUserDto = future.get();
 | 
			
		||||
            } catch (InterruptedException | ExecutionException e) {
 | 
			
		||||
                throw new RuntimeException(e.getMessage());
 | 
			
		||||
            }
 | 
			
		||||
            // 检查dataScope是否修改
 | 
			
		||||
            List<Long> dataScopes = jwtUserDto.getDataScopes();
 | 
			
		||||
            dataScopes.clear();
 | 
			
		||||
            dataScopes.addAll(dataService.getDeptIds(jwtUserDto.getUser()));
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
        return jwtUserDto;
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private JwtUserDto getJwtBySearchDb(String username) {
 | 
			
		||||
        UserDto user;
 | 
			
		||||
        try {
 | 
			
		||||
            user = userService.findByName(username);
 | 
			
		||||
        } catch (EntityNotFoundException e) {
 | 
			
		||||
            // SpringSecurity会自动转换UsernameNotFoundException为BadCredentialsException
 | 
			
		||||
            throw new UsernameNotFoundException("", e);
 | 
			
		||||
        }
 | 
			
		||||
        if (user == null) {
 | 
			
		||||
            throw new UsernameNotFoundException("");
 | 
			
		||||
        } else {
 | 
			
		||||
            if (!user.getEnabled()) {
 | 
			
		||||
                throw new BadRequestException("账号未激活!");
 | 
			
		||||
            }
 | 
			
		||||
            return new JwtUserDto(
 | 
			
		||||
                    user,
 | 
			
		||||
                    dataService.getDeptIds(user),
 | 
			
		||||
                    roleService.mapToGrantedAuthorities(user)
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static ExecutorService newThreadPool() {
 | 
			
		||||
        ThreadFactory namedThreadFactory = new ThreadFactory() {
 | 
			
		||||
            final AtomicInteger sequence = new AtomicInteger(1);
 | 
			
		||||
 | 
			
		||||
            @Override
 | 
			
		||||
            public Thread newThread(Runnable r) {
 | 
			
		||||
                Thread thread = new Thread(r);
 | 
			
		||||
                int seq = this.sequence.getAndIncrement();
 | 
			
		||||
                thread.setName("future-task-thread" + (seq > 1 ? "-" + seq : ""));
 | 
			
		||||
                if (!thread.isDaemon()) {
 | 
			
		||||
                    thread.setDaemon(true);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                return thread;
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
        return new ThreadPoolExecutor(10, 200,
 | 
			
		||||
                0L, TimeUnit.MILLISECONDS,
 | 
			
		||||
                new LinkedBlockingQueue<>(1024),
 | 
			
		||||
                namedThreadFactory,
 | 
			
		||||
                new ThreadPoolExecutor.AbortPolicy());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -18,8 +18,7 @@ package me.zhengjie.modules.security.service.dto;
 | 
			
		|||
import com.alibaba.fastjson.annotation.JSONField;
 | 
			
		||||
import lombok.AllArgsConstructor;
 | 
			
		||||
import lombok.Getter;
 | 
			
		||||
import me.zhengjie.modules.system.service.dto.UserDto;
 | 
			
		||||
import org.springframework.security.core.GrantedAuthority;
 | 
			
		||||
import me.zhengjie.modules.system.service.dto.UserLoginDto;
 | 
			
		||||
import org.springframework.security.core.userdetails.UserDetails;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Set;
 | 
			
		||||
| 
						 | 
				
			
			@ -33,11 +32,10 @@ import java.util.stream.Collectors;
 | 
			
		|||
@AllArgsConstructor
 | 
			
		||||
public class JwtUserDto implements UserDetails {
 | 
			
		||||
 | 
			
		||||
    private final UserDto user;
 | 
			
		||||
    private final UserLoginDto user;
 | 
			
		||||
 | 
			
		||||
    private final List<Long> dataScopes;
 | 
			
		||||
 | 
			
		||||
    @JSONField(serialize = false)
 | 
			
		||||
    private final List<AuthorityDto> authorities;
 | 
			
		||||
 | 
			
		||||
    public Set<String> getRoles() {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -17,6 +17,7 @@ package me.zhengjie.modules.system.service;
 | 
			
		|||
 | 
			
		||||
import me.zhengjie.modules.system.domain.User;
 | 
			
		||||
import me.zhengjie.modules.system.service.dto.UserDto;
 | 
			
		||||
import me.zhengjie.modules.system.service.dto.UserLoginDto;
 | 
			
		||||
import me.zhengjie.modules.system.service.dto.UserQueryCriteria;
 | 
			
		||||
import org.springframework.data.domain.Pageable;
 | 
			
		||||
import org.springframework.web.multipart.MultipartFile;
 | 
			
		||||
| 
						 | 
				
			
			@ -65,6 +66,13 @@ public interface UserService {
 | 
			
		|||
     */
 | 
			
		||||
    UserDto findByName(String userName);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 根据用户名查询
 | 
			
		||||
     * @param userName /
 | 
			
		||||
     * @return /
 | 
			
		||||
     */
 | 
			
		||||
    UserLoginDto getLoginData(String userName);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 修改密码
 | 
			
		||||
     * @param username 用户名
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,28 @@
 | 
			
		|||
/*
 | 
			
		||||
 *  Copyright 2019-2020 Zheng Jie
 | 
			
		||||
 *
 | 
			
		||||
 *  Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
 *  you may not use this file except in compliance with the License.
 | 
			
		||||
 *  You may obtain a copy of the License at
 | 
			
		||||
 *
 | 
			
		||||
 *  http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 *
 | 
			
		||||
 *  Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 *  distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
 *  See the License for the specific language governing permissions and
 | 
			
		||||
 *  limitations under the License.
 | 
			
		||||
 */
 | 
			
		||||
package me.zhengjie.modules.system.service.dto;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @author Zheng Jie
 | 
			
		||||
 * @description 用户缓存时使用
 | 
			
		||||
 * @date 2022-05-26
 | 
			
		||||
 **/
 | 
			
		||||
public class UserLoginDto extends UserDto {
 | 
			
		||||
 | 
			
		||||
    private String password;
 | 
			
		||||
 | 
			
		||||
    private Boolean isAdmin;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -18,7 +18,7 @@ package me.zhengjie.modules.system.service.impl;
 | 
			
		|||
import cn.hutool.core.collection.CollectionUtil;
 | 
			
		||||
import lombok.RequiredArgsConstructor;
 | 
			
		||||
import me.zhengjie.exception.BadRequestException;
 | 
			
		||||
import me.zhengjie.modules.security.service.UserCacheClean;
 | 
			
		||||
import me.zhengjie.modules.security.service.UserCacheManager;
 | 
			
		||||
import me.zhengjie.modules.security.service.dto.AuthorityDto;
 | 
			
		||||
import me.zhengjie.modules.system.domain.Menu;
 | 
			
		||||
import me.zhengjie.modules.system.domain.Role;
 | 
			
		||||
| 
						 | 
				
			
			@ -60,7 +60,7 @@ public class RoleServiceImpl implements RoleService {
 | 
			
		|||
    private final RoleSmallMapper roleSmallMapper;
 | 
			
		||||
    private final RedisUtils redisUtils;
 | 
			
		||||
    private final UserRepository userRepository;
 | 
			
		||||
    private final UserCacheClean userCacheClean;
 | 
			
		||||
    private final UserCacheManager userCacheManager;
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public List<RoleDto> queryAll() {
 | 
			
		||||
| 
						 | 
				
			
			@ -213,7 +213,7 @@ public class RoleServiceImpl implements RoleService {
 | 
			
		|||
    public void delCaches(Long id, List<User> users) {
 | 
			
		||||
        users = CollectionUtil.isEmpty(users) ? userRepository.findByRoleId(id) : users;
 | 
			
		||||
        if (CollectionUtil.isNotEmpty(users)) {
 | 
			
		||||
            users.forEach(item -> userCacheClean.cleanUserCache(item.getUsername()));
 | 
			
		||||
            users.forEach(item -> userCacheManager.cleanUserCache(item.getUsername()));
 | 
			
		||||
            Set<Long> userIds = users.stream().map(User::getId).collect(Collectors.toSet());
 | 
			
		||||
            redisUtils.delByKeys(CacheKey.DATA_USER, userIds);
 | 
			
		||||
            redisUtils.delByKeys(CacheKey.MENU_USER, userIds);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -19,16 +19,14 @@ import lombok.RequiredArgsConstructor;
 | 
			
		|||
import me.zhengjie.config.FileProperties;
 | 
			
		||||
import me.zhengjie.exception.BadRequestException;
 | 
			
		||||
import me.zhengjie.modules.security.service.OnlineUserService;
 | 
			
		||||
import me.zhengjie.modules.security.service.UserCacheClean;
 | 
			
		||||
import me.zhengjie.modules.security.service.UserCacheManager;
 | 
			
		||||
import me.zhengjie.modules.system.domain.User;
 | 
			
		||||
import me.zhengjie.exception.EntityExistException;
 | 
			
		||||
import me.zhengjie.exception.EntityNotFoundException;
 | 
			
		||||
import me.zhengjie.modules.system.repository.UserRepository;
 | 
			
		||||
import me.zhengjie.modules.system.service.UserService;
 | 
			
		||||
import me.zhengjie.modules.system.service.dto.JobSmallDto;
 | 
			
		||||
import me.zhengjie.modules.system.service.dto.RoleSmallDto;
 | 
			
		||||
import me.zhengjie.modules.system.service.dto.UserDto;
 | 
			
		||||
import me.zhengjie.modules.system.service.dto.UserQueryCriteria;
 | 
			
		||||
import me.zhengjie.modules.system.service.dto.*;
 | 
			
		||||
import me.zhengjie.modules.system.service.mapstruct.UserLoginMapper;
 | 
			
		||||
import me.zhengjie.modules.system.service.mapstruct.UserMapper;
 | 
			
		||||
import me.zhengjie.utils.*;
 | 
			
		||||
import org.springframework.cache.annotation.CacheConfig;
 | 
			
		||||
| 
						 | 
				
			
			@ -58,8 +56,9 @@ public class UserServiceImpl implements UserService {
 | 
			
		|||
    private final UserMapper userMapper;
 | 
			
		||||
    private final FileProperties properties;
 | 
			
		||||
    private final RedisUtils redisUtils;
 | 
			
		||||
    private final UserCacheClean userCacheClean;
 | 
			
		||||
    private final UserCacheManager userCacheManager;
 | 
			
		||||
    private final OnlineUserService onlineUserService;
 | 
			
		||||
    private final UserLoginMapper userLoginMapper;
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public Object queryAll(UserQueryCriteria criteria, Pageable pageable) {
 | 
			
		||||
| 
						 | 
				
			
			@ -175,6 +174,16 @@ public class UserServiceImpl implements UserService {
 | 
			
		|||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public UserLoginDto getLoginData(String userName) {
 | 
			
		||||
        User user = userRepository.findByUsername(userName);
 | 
			
		||||
        if (user == null) {
 | 
			
		||||
            throw new EntityNotFoundException(User.class, "name", userName);
 | 
			
		||||
        } else {
 | 
			
		||||
            return userLoginMapper.toDto(user);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    @Transactional(rollbackFor = Exception.class)
 | 
			
		||||
    public void updatePass(String username, String pass) {
 | 
			
		||||
| 
						 | 
				
			
			@ -252,6 +261,6 @@ public class UserServiceImpl implements UserService {
 | 
			
		|||
     * @param username /
 | 
			
		||||
     */
 | 
			
		||||
    private void flushCache(String username) {
 | 
			
		||||
        userCacheClean.cleanUserCache(username);
 | 
			
		||||
        userCacheManager.cleanUserCache(username);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,30 @@
 | 
			
		|||
/*
 | 
			
		||||
 *  Copyright 2019-2020 Zheng Jie
 | 
			
		||||
 *
 | 
			
		||||
 *  Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
 *  you may not use this file except in compliance with the License.
 | 
			
		||||
 *  You may obtain a copy of the License at
 | 
			
		||||
 *
 | 
			
		||||
 *  http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 *
 | 
			
		||||
 *  Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 *  distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
 *  See the License for the specific language governing permissions and
 | 
			
		||||
 *  limitations under the License.
 | 
			
		||||
 */
 | 
			
		||||
package me.zhengjie.modules.system.service.mapstruct;
 | 
			
		||||
 | 
			
		||||
import me.zhengjie.base.BaseMapper;
 | 
			
		||||
import me.zhengjie.modules.system.domain.User;
 | 
			
		||||
import me.zhengjie.modules.system.service.dto.UserLoginDto;
 | 
			
		||||
import org.mapstruct.Mapper;
 | 
			
		||||
import org.mapstruct.ReportingPolicy;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @author Zheng Jie
 | 
			
		||||
 * @date 2018-11-23
 | 
			
		||||
 */
 | 
			
		||||
@Mapper(componentModel = "spring",uses = {RoleMapper.class, DeptMapper.class, JobMapper.class},unmappedTargetPolicy = ReportingPolicy.IGNORE)
 | 
			
		||||
public interface UserLoginMapper extends BaseMapper<UserLoginDto, User> {
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -51,10 +51,12 @@ spring:
 | 
			
		|||
 | 
			
		||||
# 登录相关配置
 | 
			
		||||
login:
 | 
			
		||||
  # 登录缓存
 | 
			
		||||
  cache-enable: true
 | 
			
		||||
  #  是否限制单用户登录
 | 
			
		||||
  single-login: false
 | 
			
		||||
  # Redis用户登录缓存配置
 | 
			
		||||
  user-cache:
 | 
			
		||||
    # 存活时间/秒
 | 
			
		||||
    idle-time: 7200
 | 
			
		||||
  #  验证码
 | 
			
		||||
  login-code:
 | 
			
		||||
    #  验证码类型配置 查看 LoginProperties 类
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -52,10 +52,12 @@ spring:
 | 
			
		|||
 | 
			
		||||
# 登录相关配置
 | 
			
		||||
login:
 | 
			
		||||
  # 登录缓存
 | 
			
		||||
  cache-enable: true
 | 
			
		||||
  #  是否限制单用户登录
 | 
			
		||||
  single-login: false
 | 
			
		||||
  # Redis用户登录缓存配置
 | 
			
		||||
  user-cache:
 | 
			
		||||
    # 存活时间/秒
 | 
			
		||||
    idle-time: 7200
 | 
			
		||||
  #  验证码
 | 
			
		||||
  login-code:
 | 
			
		||||
    #  验证码类型配置 查看 LoginProperties 类
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -55,13 +55,4 @@ 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
 | 
			
		||||
  private_key: MIIBUwIBADANBgkqhkiG9w0BAQEFAASCAT0wggE5AgEAAkEA0vfvyTdGJkdbHkB8mp0f3FE0GYP3AYPaJF7jUd1M0XxFSE2ceK3k2kw20YvQ09NJKk+OMjWQl9WitG9pB6tSCQIDAQABAkA2SimBrWC2/wvauBuYqjCFwLvYiRYqZKThUS3MZlebXJiLB+Ue/gUifAAKIg1avttUZsHBHrop4qfJCwAI0+YRAiEA+W3NK/RaXtnRqmoUUkb59zsZUBLpvZgQPfj1MhyHDz0CIQDYhsAhPJ3mgS64NbUZmGWuuNKp5coY2GIj/zYDMJp6vQIgUueLFXv/eZ1ekgz2Oi67MNCk5jeTF2BurZqNLR3MSmUCIFT3Q6uHMtsB9Eha4u7hS31tj1UWE+D+ADzp59MGnoftAiBeHT7gDMuqeJHPL4b+kC+gzV4FGTfhR9q3tTbklZkD2A==
 | 
			
		||||
		Loading…
	
		Reference in New Issue