当前位置 博文首页 > 快乐的小三菊的博客:springboot + shiro 整合 redis 缓存用户并

    快乐的小三菊的博客:springboot + shiro 整合 redis 缓存用户并

    作者:[db:作者] 时间:2021-07-28 20:45

    背景:

    ? ? ? ?上一篇文章,我们讲述了如何使用 redis 缓存用户的数据和 session 的数据,它使用?shiro 自带的?RedisManager 类即可实现。但是,现在我们想要实现用户并发登录限制用户登录错误次数的缓存,这个就需要特殊处理了。我们的 redis 需要单独配置;而且还借鉴了开源项目中的一些类。

    整合 redis:

    ? ? ? ?首先在我们的项目中单独引入 redis ,方便以后的优化,redis 客户端使用的是 RedisTemplate,我们先自己写了一个序列化工具类继承 RedisSerializer ,如下所示:

    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.data.redis.serializer.RedisSerializer;
    import org.springframework.data.redis.serializer.SerializationException;
    
    import java.io.*;
    
    /**
     * 
     * redis的value序列化工具
     */
    public class SerializeUtils implements RedisSerializer {
    
    	private static Logger logger = LoggerFactory.getLogger(SerializeUtils.class);
    
    	public static boolean isEmpty(byte[] data) {
    		return (data == null || data.length == 0);
    	}
    
    	/**
    	 * 序列化
    	 * @param object
    	 * @return
    	 * @throws SerializationException
    	 */
    	@Override
    	public byte[] serialize(Object object) throws SerializationException {
    		byte[] result = null;
    		if (object == null) {
    			return new byte[0];
    		}
    		try (
    				ByteArrayOutputStream byteStream = new ByteArrayOutputStream(128);
    				ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteStream)
    				){
    			if (!(object instanceof Serializable)) {
    				throw new IllegalArgumentException(SerializeUtils.class.getSimpleName() + " requires a Serializable payload " +
    						"but received an object of type [" + object.getClass().getName() + "]");
    			}
    			objectOutputStream.writeObject(object);
    			objectOutputStream.flush();
    			result =  byteStream.toByteArray();
    		} catch (Exception ex) {
    			logger.error("Failed to serialize",ex);
    		}
    		return result;
    	}
    	/**
    	 * 反序列化
    	 * @param bytes
    	 * @return
    	 * @throws SerializationException
    	 */
    	@Override
    	public Object deserialize(byte[] bytes) throws SerializationException {
    
    		Object result = null;
    		if (isEmpty(bytes)) {
    			return null;
    		}
    		try (
    				ByteArrayInputStream byteStream = new ByteArrayInputStream(bytes);
    				ObjectInputStream objectInputStream = new ObjectInputStream(byteStream)
    				){
    			result = objectInputStream.readObject();
    		} catch (Exception e) {
    			logger.error("Failed to deserialize",e);
    		}
    		return result;
    	}
    }
    

    ? ? ? ?编写 redis 的配置类?RedisConfig ,用于初始化 redis 的相关配置,千万记得要在配置文件里面配置 redisip 地址和用户名密码。

    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.data.redis.connection.RedisConnectionFactory;
    import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.data.redis.serializer.StringRedisSerializer;
    import redis.clients.jedis.JedisPoolConfig;
    
    
    @Configuration
    public class RedisConfig {
    
        /**
         * redis地址
         */
        @Value("${spring.redis.host}")
        private String host;
    
        /**
         * redis端口号
         */
        @Value("${spring.redis.port}")
        private Integer port;
    
        /**
         * redis密码
         */
        @Value("${spring.redis.password}")
        private String password;
    
        /**
         * JedisPoolConfig 连接池
         * @return
         */
        @Bean
        public JedisPoolConfig jedisPoolConfig(){
            JedisPoolConfig jedisPoolConfig=new JedisPoolConfig();
            // 最大空闲数
            jedisPoolConfig.setMaxIdle(300);
            // 连接池的最大数据库连接数
            jedisPoolConfig.setMaxTotal(1000);
            // 最大建立连接等待时间
            jedisPoolConfig.setMaxWaitMillis(1000);
            // 逐出连接的最小空闲时间 默认1800000毫秒(30分钟)
            jedisPoolConfig.setMinEvictableIdleTimeMillis(300000);
            // 每次逐出检查时 逐出的最大数目 如果为负数就是 : 1/abs(n), 默认3
            jedisPoolConfig.setNumTestsPerEvictionRun(10);
            // 逐出扫描的时间间隔(毫秒) 如果为负数,则不运行逐出线程, 默认-1
            jedisPoolConfig.setTimeBetweenEvictionRunsMillis(30000);
            // 是否在从池中取出连接前进行检验,如果检验失败,则从池中去除连接并尝试取出另一个
            jedisPoolConfig.setTestOnBorrow(true);
            // 在空闲时检查有效性, 默认false
            jedisPoolConfig.setTestWhileIdle(true);
            return jedisPoolConfig;
        }
    
        /**
         * 配置工厂
         * @param jedisPoolConfig
         * @return
         */
        @SuppressWarnings("deprecation")
    	@Bean
        public JedisConnectionFactory jedisConnectionFactory(JedisPoolConfig jedisPoolConfig){
            JedisConnectionFactory jedisConnectionFactory=new JedisConnectionFactory();
            // 连接池
            jedisConnectionFactory.setPoolConfig(jedisPoolConfig);
            // IP地址
            jedisConnectionFactory.setHostName(host);
            // 端口号
            jedisConnectionFactory.setPort(port);
            // 如果Redis设置有密码
            //jedisConnectionFactory.setPassword(password);
            // 客户端超时时间单位是毫秒
            jedisConnectionFactory.setTimeout(5000);
            return jedisConnectionFactory;
        }
    
        /**
         * shiro redis缓存使用的模板
         * 实例化 RedisTemplate 对象
         * @return
         */
        @Bean("shiroRedisTemplate")
        public RedisTemplate shiroRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
    
            RedisTemplate redisTemplate = new RedisTemplate();
            redisTemplate.setKeySerializer(new StringRedisSerializer());
            redisTemplate.setHashKeySerializer(new StringRedisSerializer());
            redisTemplate.setHashValueSerializer(new SerializeUtils());
            redisTemplate.setValueSerializer(new SerializeUtils());
            // 开启事务
            //stringRedisTemplate.setEnableTransactionSupport(true);
            redisTemplate.setConnectionFactory(redisConnectionFactory);
            return redisTemplate;
        }
    }
    

    ? ? ? ?编写 redis 的管理类?RedisManager ,封装一些常用的方法,方便对缓存中?keyvalue 进行操作。

    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.dao.DataAccessException;
    import org.springframework.data.redis.connection.RedisConnection;
    import org.springframework.data.redis.core.*;
    import org.springframework.util.CollectionUtils;
    
    import java.util.*;
    import java.util.concurrent.TimeUnit;
    
    /**
     * 基于spring和redis的redisTemplate工具类
     */
    public class RedisManager {
    
        @Autowired
        private RedisTemplate<String, Object> redisTemplate;
    
        /**
         * 指定缓存失效时间
         * @param key 键
         * @param time 时间(秒)
         */
        public void expire(String key,long time){
            redisTemplate.expire(key, time, TimeUnit.SECONDS);
        }
    
        /**
         * 判断key是否存在
         * @param key 键
         * @return true 存在 false不存在
         */
        public Boolean hasKey(String key){
            return redisTemplate.hasKey(key);
        }
    
        /**
         * 删除缓存
         * @param key 可以传一个值 或多个
         */
        @SuppressWarnings("unchecked")
        public void del(String ... key){
            if(key!=null&&key.length>0){
                if(key.length==1){
                    redisTemplate.delete(key[0]);
                }else{
                    redisTemplate.delete(CollectionUtils.arrayToList(key));
                }
            }
        }
    
        /**
         * 批量删除key
         * @param keys
         */
        @SuppressWarnings("unchecked")
    	public void del(@SuppressWarnings("rawtypes") Collection keys){
            redisTemplate.delete(keys);
        }
    
        //============================String=============================
        /**
         * 普通缓存获取
         * @param key 键
         * @return 值
         */
        public Object get(String key){
            return redisTemplate.opsForValue().get(key);
        }
    
        /**
         * 普通缓存放入
         * @param key 键
         * @param value 值
         */
        public void set(String key,Object value) {
            redisTemplate.opsForValue().set(key, value);
        }
    
        /**
         * 普通缓存放入并设置时间
         * @param key 键
         * @param value 值
         * @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
         */
        public void set(String key,Object value,long time){
            if(time>0){
                redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
            }else{
                set(key, value);
            }
        }
    
        /**
         * 使用scan命令 查询某些前缀的key
         * @param key
         * @return
         */
        public Set<String> scan(String key){
            Set<String> execute = this.redisTemplate.execute(new RedisCallback<Set<String>>() {
    
                @Override
                public Set<String> doInRedis(RedisConnection connection) throws DataAccessException {
                    Set<String> binaryKeys = new HashSet<>();
                    Cursor<byte[]> cursor = connection.scan(new ScanOptions.ScanOptionsBuilder().match(key).count(1000).build());
                    while (cursor.hasNext()) {
                        binaryKeys.add(new String(cursor.next()));
                    }
                    return binaryKeys;
                }
            });
            return execute;
        }
    
        /**
         * 使用scan命令 查询某些前缀的key 有多少个
         * 用来获取当前session数量,也就是在线用户
         * @param key
         * @return
         */
        public Long scanSize(String key){
            long dbSize = this.redisTemplate.execute(new RedisCallback<Long>() {
                @Override
                public Long doInRedis(RedisConnection connection) throws DataAccessException {
                    long count = 0L;
                    Cursor<byte[]> cursor = connection.scan(ScanOptions.scanOptions().match(key).count(1000).build());
                    while (cursor.hasNext()) {
                        cursor.next();
                        count++;
                    }
                    return count;
                }
            });
            return dbSize;
        }
    }
    

    配置 redis 缓存:

    ? ? ? ?如果想要使用 redis 作为缓存,需要重写 shiro 里面的 cache cacheManager 和?SessionDAO,下面分别将这三个类的代码贴出。首先贴出的是?RedisCache 类的内容,

    import org.apache.shiro.cache.Cache;
    import org.apache.shiro.cache.CacheException;
    import org.apache.shiro.subject.PrincipalCollection;
    import org.apache.shiro.util.CollectionUtils;
    import org.crazycake.shiro.exception.PrincipalIdNullException;
    import org.crazycake.shiro.exception.PrincipalInstanceException;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    import com.redis.RedisManager;
    
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    import java.util.*;
    
    /**
     * 参考 shiro-redis 开源项目 Git地址 https://github.com/alexxiyang/shiro-redis
     */
    public class RedisCache<K, V> implements Cache<K, V> {
    
        private static Logger logger = LoggerFactory.getLogger(RedisCache.class);
    
        private RedisManager redisManager;
        private String keyPrefix = "";
        private int expire = 0;
        private String principalIdFieldName = RedisCacheManager.DEFAULT_PRINCIPAL_ID_FIELD_NAME;
    
        /**
         * Construction
         * @param redisManager
         */
        public RedisCache(RedisManager redisManager, String prefix, int expire, String principalIdFieldName) {
            if (redisManager == null) {
                throw new IllegalArgumentException("redisManager cannot be null.");
            }
            this.redisManager = redisManager;
            if (prefix != null && !"".equals(prefix)) {
                this.keyPrefix = prefix;
            }
            if (expire != -1) {
                this.expire = expire;
            }
            if (principalIdFieldName != null && !"".equals(principalIdFieldName)) {
                this.principalIdFieldName = principalIdFieldName;
            }
        }
    
        @Override
        public V get(K key) throws CacheException {
            logger.debug("get key [{}]",key);
    
            if (key == null) {
                return null;
            }
    
            try {
                String redisCacheKey = getRedisCacheKey(key);
                Object rawValue = redisManager.get(redisCacheKey);
                if (rawValue == null) {
                    return null;
                }
                V value = (V) rawValue;
                return value;
            } catch (Exception e) {
                throw new CacheException(e);
            }
        }
    
        @Override
        public V put(K key, V value) throws CacheException {
            logger.debug("put key [{}]",key);
            if (key == null) {
                logger.warn("Saving a null key is meaningless, return value directly without call Redis.");
                return value;
            }
            try {
                String redisCacheKey = getRedisCacheKey(key);
                redisManager.set(redisCacheKey, value != null ? value : null, expire);
                return value;
            } catch (Exception e) {
                throw new CacheException(e);
            }
        }
    
        @Override
        public V remove(K key) throws CacheException {
            logger.debug("remove key [{}]",key);
            if (key == null) {
                return null;
            }
            try {
                String redisCacheKey = getRedisCacheKey(key);
                Object rawValue = redisManager.get(redisCacheKey);
                V previous = (V) rawValue;
                redisManager.del(redisCacheKey);
                return previous;
            } catch (Exception e) {
                throw new CacheException(e);
            }
        }
    
        private String getRedisCacheKey(K key) {
            if (key == null) {
                return null;
            }
            return this.keyPrefix + getStringRedisKey(key);
        }
    
        private String getStringRedisKey(K key) {
            String redisKey;
            if (key instanceof PrincipalCollection) {
                redisKey = getRedisKeyFromPrincipalIdField((PrincipalCollection) key);
            } else {
                redisKey = key.toString();
            }
            return redisKey;
        }
    
        private String getRedisKeyFromPrincipalIdField(PrincipalCollection key) {
            String redisKey;
            Object principalObject = key.getPrimaryPrincipal();
            Method pincipalIdGetter = null;
            Method[] methods = principalObject.getClass().getDeclaredMethods();
            for (Method m:methods) {
                if (RedisCacheManager.DEFAULT_PRINCIPAL_ID_FIELD_NAME.equals(this.principalIdFieldName)
                        && ("getAuthCacheKey".equals(m.getName()) || "getId".equals(m.getName()))) {
                    pincipalIdGetter = m;
                    break;
                }
                if (m.getName().equals("get" + this.principalIdFieldName.substring(0, 1).toUpperCase() + this.principalIdFieldName.substring(1))) {
                    pincipalIdGetter = m;
                    break;
                }
            }
            if (pincipalIdGetter == null) {
                throw new PrincipalInstanceException(principalObject.getClass(), this.principalIdFieldName);
            }
    
            try {
                Object idObj = pincipalIdGetter.invoke(principalObject);
                if (idObj == null) {
                    throw new PrincipalIdNullException(principalObject.getClass(), this.principalIdFieldName);
                }
                redisKey = idObj.toString();
            } catch (IllegalAccessException e) {
                throw new PrincipalInstanceException(principalObject.getClass(), this.principalIdFieldName, e);
            } catch (InvocationTargetException e) {
                throw new PrincipalInstanceException(principalObject.getClass(), this.principalIdFieldName, e);
            }
    
            return redisKey;
        }
    
    
        @Override
        public void clear() throws CacheException {
            logger.debug("clear cache");
            Set<String> keys = null;
            try {
                keys = redisManager.scan(this.keyPrefix + "*");
            } catch (Exception e) {
                logger.error("get keys error", e);
            }
            if (keys == null || keys.size() == 0) {
                return;
            }
            for (String key: keys) {
                redisManager.del(key);
            }
        }
    
        @Override
        public int size() {
            Long longSize = 0L;
            try {
                longSize = new Long(redisManager.scanSize(this.keyPrefix + "*"));
            } catch (Exception e) {
                logger.error("get keys error", e);
            }
            return longSize.intValue();
        }
    
        @SuppressWarnings("unchecked")
        @Override
        public Set<K> keys() {
            Set<String> keys = null;
            try {
                keys = redisManager.scan(this.keyPrefix + "*");
            } catch (Exception e) {
                logger.error("get keys error", e);
                return Collections.emptySet();
            }
    
            if (CollectionUtils.isEmpty(keys)) {
                return Collections.emptySet();
            }
    
            Set<K> convertedKeys = new HashSet<K>();
            for (String key:keys) {
                try {
                    convertedKeys.add((K) key);
                } catch (Exception e) {
                    logger.error("deserialize keys error", e);
                }
            }
            return convertedKeys;
        }
    
        @Override
        public Collection<V> values() {
            Set<String> keys = null;
            try {
                keys = redisManager.scan(this.keyPrefix + "*");
            } catch (Exception e) {
                logger.error("get values error", e);
                return Collections.emptySet();
            }
    
            if (CollectionUtils.isEmpty(keys)) {
                return Collections.emptySet();
            }
    
            List<V> values = new ArrayList<V>(keys.size());
            for (String key : keys) {
                V value = null;
                try {
                    value = (V) redisManager.get(key);
                } catch (Exception e) {
                    logger.error("deserialize values= error", e);
                }
                if (value != null) {
                    values.add(value);
                }
            }
            return Collections.unmodifiableList(values);
        }
    
        public String getKeyPrefix() {
            return keyPrefix;
        }
    
        public void setKeyPrefix(String keyPrefix) {
            this.keyPrefix = keyPrefix;
        }
    
        public String getPrincipalIdFieldName() {
            return principalIdFieldName;
        }
    
        public void setPrincipalIdFieldName(String principalIdFieldName) {
            this.principalIdFieldName = principalIdFieldName;
        }
    }
    

    ? ? ? ?在上面这个类里面有个?getRedisKeyFromPrincipalIdField() 方法,它是获取缓存的用户身份信息和用户权限信息。 它里面有一个属性 principalIdFieldNameRedisCacheManager?中也有这个属性。设置其中一个就可以。设置它是为了让缓存用户身份和权限信息在 redis 中的 key 是唯一的,登录用户名可能是 userName phoneNum 或者是 Email 中的一个,如:我的 User 实体类中有一个 userName 字段,也是登录时候使用的用户名,在 redis 中缓存的权限信息 key 如下,?这个 zhangsan 就是 通过 getUserName() 获得的。如下图所示:

    ?? ? ? 接下来贴出的是?RedisCacheManager 类的代码,如下所示:

    import org.apache.shiro.cache.Cache;
    import org.apache.shiro.cache.CacheException;
    import org.apache.shiro.cache.CacheManager;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    import com.redis.RedisManager;
    
    import java.util.concurrent.ConcurrentHashMap;
    import java.util.concurrent.ConcurrentMap;
    
    /**
     * 参考 shiro-redis 开源项目 Git地址 https://github.com/alexxiyang/shiro-redis
     */
    public class RedisCacheManager implements CacheManager {
    
        private final Logger logger = LoggerFactory.getLogger(RedisCacheManager.class);
    
        /**
         * fast lookup by name map
         */
        private final ConcurrentMap<String, Cache> caches = new ConcurrentHashMap<String, Cache>();
    
        private RedisManager redisManager;
    
        /**
         * expire time in seconds
         */
        private static final int DEFAULT_EXPIRE = 1800;
        private int expire = DEFAULT_EXPIRE;
    
        /**
         * The Redis key prefix for caches
         */
        public static final String DEFAULT_CACHE_KEY_PREFIX = "shiro:cache:";
        private String keyPrefix = DEFAULT_CACHE_KEY_PREFIX;
    
        public static final String DEFAULT_PRINCIPAL_ID_FIELD_NAME = "authCacheKey or id";
        private String principalIdFieldName = DEFAULT_PRINCIPAL_ID_FIELD_NAME;
    
        @Override
        public <K, V> Cache<K, V> getCache(String name) throws CacheException {
            logger.debug("get cache, name={}",name);
    
            Cache cache = caches.get(name);
    
            if (cache == null) {
                cache = new RedisCache<K, V>(redisManager,keyPrefix + name + ":", expire, principalIdFieldName);
                caches.put(name, cache);
            }
            return cache;
        }
    
        public RedisManager getRedisManager() {
            return redisManager;
        }
    
        public void setRedisManager(RedisManager redisManager) {
            this.redisManager = redisManager;
        }
    
        public String getKeyPrefix() {
            return keyPrefix;
        }
    
        public void setKeyPrefix(String keyPrefix) {
            this.keyPrefix = keyPrefix;
        }
    
        public int getExpire() {
            return expire;
        }
    
        public void setExpire(int expire) {
            this.expire = expire;
        }
    
        public String getPrincipalIdFieldName() {
            return principalIdFieldName;
        }
    
        public void setPrincipalIdFieldName(String principalIdFieldName) {
            this.principalIdFieldName = principalIdFieldName;
        }
    }

    ?? ? ? 接下来贴出的是?RedisSessionDAO?类的代码,如下所示:

    import org.apache.shiro.session.Session;
    import org.apache.shiro.session.UnknownSessionException;
    import org.apache.shiro.session.mgt.ValidatingSession;
    import org.apache.shiro.session.mgt.eis.AbstractSessionDAO;
    import org.crazycake.shiro.SessionInMemory;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    import com.redis.RedisManager;
    
    import java.io.Serializable;
    import java.util.*;
    
    /**
     * 参考 shiro-redis 开源项目 Git地址 https://github.com/alexxiyang/shiro-redis
     */
    public class RedisSessionDAO extends AbstractSessionDAO {
    
        private static Logger logger = LoggerFactory.getLogger(RedisSessionDAO.class);
    
        private static final String DEFAULT_SESSION_KEY_PREFIX = "shiro:session:";
        private String keyPrefix = DEFAULT_SESSION_KEY_PREFIX;
    
        private static final long DEFAULT_SESSION_IN_MEMORY_TIMEOUT = 1000L;
        /**
         * doReadSession be called about 10 times when login.
         * Save Session in ThreadLocal to resolve this problem. sessionInMemoryTimeout is expiration of Session in ThreadLocal.
         * The default value is 1000 milliseconds (1s).
         * Most of time, you don't need to change it.
         */
        private long sessionInMemoryTimeout = DEFAULT_SESSION_IN_MEMORY_TIMEOUT;
    
        /**
         * expire time in seconds
         */
        private static final int DEFAULT_EXPIRE = -2;
        private static final int NO_EXPIRE = -1;
    
        /**
         * Please make sure expire is longer than sesion.getTimeout()
         */
        private int expire = DEFAULT_EXPIRE;
    
        private static final int MILLISECONDS_IN_A_SECOND = 1000;
    
        private RedisManager redisManager;
        private static ThreadLocal sessionsInThread = new ThreadLocal();
    
        @Override
        public void update(Session session) throws UnknownSessionException {
            // 如果会话过期/停止 没必要再更新了
            try {
                if (session instanceof ValidatingSession && !((ValidatingSession) session).isValid()) {
                    return;
                }
    
                if (session instanceof ShiroSession) {
                    // 如果没有主要字段(除lastAccessTime以外其他字段)发生改变
                    ShiroSession ss = (ShiroSession) session;
                    if (!ss.isChanged()) {
                        return;
                    }
                    // 如果没有返回 证明有调用 setAttribute往redis 放的时候永远设置为false
                    ss.setChanged(false);
                }
    
                this.saveSession(session);
            } catch (Exception e) {
                logger.warn("update Session is failed", e);
            }
        }
    
        /**
         * save session
         * @param session
         * @throws UnknownSessionException
         */
        private void saveSession(Session session) throws UnknownSessionException {
            if (session == null || session.getId() == null) {
                logger.error("session or session id is null");
                throw new UnknownSessionException("session or session id is null");
            }
            String key = getRedisSessionKey(session.getId());
            if (expire == DEFAULT_EXPIRE) {
                this.redisManager.set(key, session, (int) (session.getTimeout() / MILLISECONDS_IN_A_SECOND));
                return;
            }
            if (expire != NO_EXPIRE && expire * MILLISECONDS_IN_A_SECOND < session.getTimeout()) {
                logger.warn("Redis session expire time: "
                        + (expire * MILLISECONDS_IN_A_SECOND)
                        + " is less than Session timeout: "
                        + session.getTimeout()
                        + " . It may cause some problems.");
            }
            this.redisManager.set(key, session, expire);
        }
    
        @Override
        public void delete(Session session) {
            if (session == null || session.getId() == null) {
                logger.error("session or session id is null");
                return;
            }
            try {
                redisManager.del(getRedisSessionKey(session.getId()));
            } catch (Exception e) {
                logger.error("delete session error. session id= {}",session.getId());
            }
        }
    
        @Override
        public Collection<Session> getActiveSessions() {
            Set<Session> sessions = new HashSet<Session>();
            try {
                Set<String> keys = redisManager.scan(this.keyPrefix + "*");
                if (keys != null && keys.size() > 0) {
                    for (String key:keys) {
                        Session s = (Session) redisManager.get(key);
                        sessions.add(s);
                    }
                }
            } catch (Exception e) {
                logger.error("get active sessions error.");
            }
            return sessions;
        }
    
        public Long getActiveSessionsSize() {
            Long size = 0L;
            try {
                size = redisManager.scanSize(this.keyPrefix + "*");
            } catch (Exception e) {
                logger.error("get active sessions error.");
            }
            return size;
        }
    
        @Override
        protected Serializable doCreate(Session session) {
            if (session == null) {
                logger.error("session is null");
                throw new UnknownSessionException("session is null");
            }
            Serializable sessionId = this.generateSessionId(session);
            this.assignSessionId(session, sessionId);
            this.saveSession(session);
            return sessionId;
        }
    
        @Override
        protected Session doReadSession(Serializable sessionId) {
            if (sessionId == null) {
                logger.warn("session id is null");
                return null;
            }
            Session s = getSessionFromThreadLocal(sessionId);
    
            if (s != null) {
                return s;
            }
    
            logger.debug("read session from redis");
            try {
                s = (Session) redisManager.get(getRedisSessionKey(sessionId));
                setSessionToThreadLocal(sessionId, s);
            } catch (Exception e) {
                logger.error("read session error. settionId= {}",sessionId);
            }
            return s;
        }
    
        private void setSessionToThreadLocal(Serializable sessionId, Session s) {
            Map<Serializable, SessionInMemory> sessionMap = (Map<Serializable, SessionInMemory>) sessionsInThread.get();
            if (sessionMap == null) {
                sessionMap = new HashMap<Serializable, SessionInMemory>();
                sessionsInThread.set(sessionMap);
            }
            SessionInMemory sessionInMemory = new SessionInMemory();
            sessionInMemory.setCreateTime(new Date());
            sessionInMemory.setSession(s);
            sessionMap.put(sessionId, sessionInMemory);
        }
    
        private Session getSessionFromThreadLocal(Serializable sessionId) {
            Session s = null;
    
            if (sessionsInThread.get() == null) {
                return null;
            }
    
            Map<Serializable, SessionInMemory> sessionMap = (Map<Serializable, SessionInMemory>) sessionsInThread.get();
            SessionInMemory sessionInMemory = sessionMap.get(sessionId);
            if (sessionInMemory == null) {
                return null;
            }
            Date now = new Date();
            long duration = now.getTime() - sessionInMemory.getCreateTime().getTime();
            if (duration < sessionInMemoryTimeout) {
                s = sessionInMemory.getSession();
                logger.debug("read session from memory");
            } else {
                sessionMap.remove(sessionId);
            }
    
            return s;
        }
    
        private String getRedisSessionKey(Serializable sessionId) {
            return this.keyPrefix + sessionId;
        }
    
        public RedisManager getRedisManager() {
            return redisManager;
        }
    
        public void setRedisManager(RedisManager redisManager) {
            this.redisManager = redisManager;
        }
    
        public String getKeyPrefix() {
            return keyPrefix;
        }
    
        public void setKeyPrefix(String keyPrefix) {
            this.keyPrefix = keyPrefix;
        }
    
        public long getSessionInMemoryTimeout() {
            return sessionInMemoryTimeout;
        }
    
        public void setSessionInMemoryTimeout(long sessionInMemoryTimeout) {
            this.sessionInMemoryTimeout = sessionInMemoryTimeout;
        }
    
        public int getExpire() {
            return expire;
        }
    
        public void setExpire(int expire) {
            this.expire = expire;
        }
    }
    
    下一篇:没有了