原文:https://blog.csdn.net/qq_34021712/article/details/80791219
之前写过一篇博客,使用的一个开源项目,实现了redis作为缓存 缓存用户的权限 和 session信息,还有两个功能没有修改,一个是用户并发登录限制,一个是用户密码错误次数.本篇中几个类 也是使用的开源项目中的类,只不过是拿出来了,redis单独做的配置,方便进行优化。
整合过程
1.首先是整合Redis
Redis客户端使用的是RedisTemplate,自己写了一个序列化工具继承RedisSerializer
SerializeUtils.java
package com.springboot.test.shiro.global.utils;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.*;/**
* @author: wangsaichao
* @date: 2018/6/20
* @description: redis的value序列化工具
*/publicclassSerializeUtilsimplementsRedisSerializer{privatestatic Logger logger= LoggerFactory.getLogger(SerializeUtils.class);publicstaticbooleanisEmpty(byte[] data){return(data== null|| data.length==0);}/**
* 序列化
* @param object
* @return
* @throws SerializationException
*/@Overridepublicbyte[]serialize(Object object)throws SerializationException{byte[] result= null;if(object== null){returnnewbyte[0];}try(
ByteArrayOutputStream byteStream=newByteArrayOutputStream(128);
ObjectOutputStream objectOutputStream=newObjectOutputStream(byteStream)){if(!(objectinstanceofSerializable)){thrownewIllegalArgumentException(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
*/@Overridepublic Objectdeserialize(byte[] bytes)throws SerializationException{
Object result= null;if(isEmpty(bytes)){return null;}try(
ByteArrayInputStream byteStream=newByteArrayInputStream(bytes);
ObjectInputStream objectInputStream=newObjectInputStream(byteStream)){
result= objectInputStream.readObject();}catch(Exception e){
logger.error("Failed to deserialize",e);}return result;}}
RedisConfig.java
package com.springboot.test.shiro.config;import com.springboot.test.shiro.global.utils.SerializeUtils;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;/**
* @author: wangsaichao
* @date: 2017/11/23
* @description: redis配置
*/@ConfigurationpublicclassRedisConfig{/**
* 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
*/@Beanpublic JedisPoolConfigjedisPoolConfig(){
JedisPoolConfig jedisPoolConfig=newJedisPoolConfig();//最大空闲数
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
*/@Beanpublic JedisConnectionFactoryjedisConnectionFactory(JedisPoolConfig jedisPoolConfig){
JedisConnectionFactory jedisConnectionFactory=newJedisConnectionFactory();//连接池
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 RedisTemplateshiroRedisTemplate(RedisConnectionFactory redisConnectionFactory){
RedisTemplate redisTemplate=newRedisTemplate();
redisTemplate.setKeySerializer(newStringRedisSerializer());
redisTemplate.setHashKeySerializer(newStringRedisSerializer());
redisTemplate.setHashValueSerializer(newSerializeUtils());
redisTemplate.setValueSerializer(newSerializeUtils());//开启事务//stringRedisTemplate.setEnableTransactionSupport(true);
redisTemplate.setConnectionFactory(redisConnectionFactory);return redisTemplate;}}
RedisManager.java
package com.springboot.test.shiro.config.shiro;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;/**
*
* @author wangsaichao
* 基于spring和redis的redisTemplate工具类
*/publicclassRedisManager{@Autowiredprivate RedisTemplate<String, Object> redisTemplate;//=============================common============================/**
* 指定缓存失效时间
* @param key 键
* @param time 时间(秒)
*/publicvoidexpire(String key,long time){
redisTemplate.expire(key, time, TimeUnit.SECONDS);}/**
* 判断key是否存在
* @param key 键
* @return true 存在 false不存在
*/public BooleanhasKey(String key){return redisTemplate.hasKey(key);}/**
* 删除缓存
* @param key 可以传一个值 或多个
*/@SuppressWarnings("unchecked")publicvoiddel(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
*/publicvoiddel(Collection keys){
redisTemplate.delete(keys);}//============================String=============================/**
* 普通缓存获取
* @param key 键
* @return 值
*/public Objectget(String key){return redisTemplate.opsForValue().get(key);}/**
* 普通缓存放入
* @param key 键
* @param value 值
*/publicvoidset(String key,Object value){
redisTemplate.opsForValue().set(key, value);}/**
* 普通缓存放入并设置时间
* @param key 键
* @param value 值
* @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
*/publicvoidset(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(newRedisCallback<Set<String>>(){@Overridepublic Set<String>doInRedis(RedisConnection connection)throws DataAccessException{
Set<String> binaryKeys=newHashSet<>();
Cursor<byte[]> cursor= connection.scan(newScanOptions.ScanOptionsBuilder().match(key).count(1000).build());while(cursor.hasNext()){
binaryKeys.add(newString(cursor.next()));}return binaryKeys;}});return execute;}/**
* 使用scan命令 查询某些前缀的key 有多少个
* 用来获取当前session数量,也就是在线用户
* @param key
* @return
*/public LongscanSize(String key){long dbSize=this.redisTemplate.execute(newRedisCallback<Long>(){@Overridepublic LongdoInRedis(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;}}
2.使用Redis作为缓存需要shiro重写cache、cacheManager、SessionDAO
RedisCache.java
package com.springboot.test.shiro.config.shiro;import com.springboot.test.shiro.global.exceptions.PrincipalIdNullException;import com.springboot.test.shiro.global.exceptions.PrincipalInstanceException;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.slf4j.Logger;import org.slf4j.LoggerFactory;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;import java.util.*;/**
* @author: wangsaichao
* @date: 2018/6/22
* @description: 参考 shiro-redis 开源项目 Git地址 https://github.com/alexxiyang/shiro-redis
*/publicclassRedisCache<K, V>implementsCache<K, V>{privatestatic Logger logger= LoggerFactory.getLogger(RedisCache.class);private RedisManager redisManager;private String keyPrefix="";privateint expire=0;private String principalIdFieldName= RedisCacheManager.DEFAULT_PRINCIPAL_ID_FIELD_NAME;/**
* Construction
* @param redisManager
*/publicRedisCache(RedisManager redisManager, String prefix,int expire, String principalIdFieldName){if(redisManager== null){thrownewIllegalArgumentException("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;}}@Overridepublic Vget(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){thrownewCacheException(e);}}@Overridepublic Vput(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){thrownewCacheException(e);}}@Overridepublic Vremove(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){thrownewCacheException(e);}}private StringgetRedisCacheKey(K key){