1.Redis连接池构建
首先maven导入依赖包
<dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.6.0</version> </dependency>
下面正式写一个连接池
public class RedisPool { private static JedisPool pool;//jedis连接池 private static Integer maxTotal = Integer.valueOf(PropertiesUtil.getProperty("redis.max.total","20")); //最大连接数 private static Integer maxIdle = Integer.valueOf(PropertiesUtil.getProperty("redis.max.idle","10"));//在jedispool中最大的idle状态(空闲的)的jedis实例的个数 private static Integer minIdle = Integer.valueOf(PropertiesUtil.getProperty("redis.min.idle","2"));//在jedispool中最小的idle状态(空闲的)的jedis实例的个数 private static Boolean testOnBorrow = Boolean.valueOf(PropertiesUtil.getProperty("redis.test.borrow","true"));//在borrow一个Jedis实例的时候,是否要进行验证操作,如果赋值true,那么得到的jedis实例肯定是可用的 private static Boolean testOnReturn = Boolean.valueOf(PropertiesUtil.getProperty("redis.test.return","true"));//在borrow一个Jedis实例的时候,是否要进行验证操作,如果赋值true,则放回jedispool的jedis实例肯定是可用的 private static String redisIp = PropertiesUtil.getProperty("redis.ip");//在jedispool中最大的idle状态(空闲的)的jedis实例的个数 private static Integer redisPort = Integer.valueOf(PropertiesUtil.getProperty("redis.port"));//在jedispool中最小的idle状态(空闲的)的jedis实例的个数 private static void initPool(){ JedisPoolConfig config = new JedisPoolConfig(); config.setMaxTotal(maxTotal); config.setMaxIdle(maxIdle); config.setMinIdle(minIdle); config.setTestOnBorrow(testOnBorrow); config.setTestOnReturn(testOnReturn); config.setBlockWhenExhausted(true);//连接耗尽的时候是否阻塞,true:阻塞,false:抛出异常 pool = new JedisPool(config,redisIp,redisPort,1000*2); } static { initPool(); } public static Jedis getJedis(){ return pool.getResource(); } public static void returnBrokenResource(Jedis jedis){ pool.returnBrokenResource(jedis); } public static void returnResource(Jedis jedis){ pool.returnResource(jedis); } public static void main (String[] args){ //Jedis jedis = pool.getResource(); Jedis jedis = RedisPool.getJedis(); jedis.set("key","value"); RedisPool.returnResource(jedis); pool.destroy();//临时调用 System.out.println("program is end"); } }
代码内的PropertiesUtil是一个读取properties的工具类
public class PropertiesUtil { private static Logger logger = LoggerFactory.getLogger(PropertiesUtil.class); private static Properties props; static { String fileName = "zfb.properties"; props = new Properties(); try { props.load(new InputStreamReader(PropertiesUtil.class.getClassLoader().getResourceAsStream(fileName),"UTF-8")); } catch (IOException e) { logger.error("配置文件读取异常",e); } } public static String getProperty(String key){ String value = props.getProperty(key.trim()); if(StringUtils.isBlank(value)){ return null; } return value.trim(); } public static String getProperty(String key,String defaultValue){ String value = props.getProperty(key.trim()); if(StringUtils.isBlank(value)){ value = defaultValue; } return value.trim(); } }
与redis的连接搞定了,那接下来就要封装一下操作redis的方法了
2.Jedis API封装
@Slf4j public class RedisPoolUtil { /*设置key的有效期,单位是秒*/ public static Long expire(String key,int exTime){ Jedis jedis = null; Long result = null; try { jedis = RedisPool.getJedis(); result = jedis.expire(key,exTime); } catch (Exception e) { log.error("expire key:{} exTime:{} error",key,exTime,e); RedisPool.returnBrokenResource(jedis); return result; } RedisPool.returnResource(jedis); return result; } //exTime的单位是秒 public static String setEx(String key,String value,int exTime){ Jedis jedis = null; String result = null; try { jedis = RedisPool.getJedis(); result = jedis.setex(key,exTime,value); } catch (Exception e) { log.error("setex key:{} exTime:{} value:{} error",key,exTime,value,e); RedisPool.returnBrokenResource(jedis); return result; } RedisPool.returnResource(jedis); return result; } public static String set(String key,String value){ Jedis jedis = null; String result = null; try { jedis = RedisPool.getJedis(); result = jedis.set(key, value); } catch (Exception e) { log.error("set key:{} value:{} error",key,value,e); RedisPool.returnBrokenResource(jedis); return result; } RedisPool.returnResource(jedis); return result; } public static String get(String key){ Jedis jedis = null; String result = null; try { jedis = RedisPool.getJedis(); result = jedis.get(key); } catch (Exception e) { log.error("get key:{} error",key,e); RedisPool.returnBrokenResource(jedis); return result; } RedisPool.returnResource(jedis); return result; } public static Long del(String key){ Jedis jedis = null; Long result = null; try { jedis = RedisPool.getJedis(); result = jedis.del(key); } catch (Exception e) { log.error("del key:{} error",key,e); RedisPool.returnBrokenResource(jedis); return result; } RedisPool.returnResource(jedis); return result; } public static void main (String[] args){ Jedis jedis = RedisPool.getJedis(); RedisShardedPoolUtil.set("keyTest","value"); String value = RedisShardedPoolUtil.get("keyTest"); RedisShardedPoolUtil.setEx("kevex","valueex",60*10); RedisShardedPoolUtil.expire("keyTest",60*20); RedisShardedPoolUtil.del("keyTest"); System.out.println("end"); } }
类里面封装了一些常用的操作redis的方法,set(),设置键值;del(),删除键值;setEx(),set的时候就把生存时间设置了等等
3.JSONUtil封装
RedisPoolUtil 工具类是对String操作的,但是我们在登录的时候,用户User是一个对象,里面有具体的参数,那么我们就需要把这个对象User序列化,然后存储到redis里面
<dependency> <groupId>org.codehaus.jackson</groupId> <artifactId>jackson-mapper-asl</artifactId> <version>1.9.12</version> </dependency>
public class JsonUtil { private static ObjectMapper objectMapper = new ObjectMapper(); static { //对象的所有字段全部列入 objectMapper.setSerializationInclusion(JsonSerialize.Inclusion.ALWAYS); //取消默认转换timestamps形式 objectMapper.configure(SerializationConfig.Feature.WRITE_DATES_AS_TIMESTAMPS,false); //忽略空bean转json的错误 objectMapper.configure(SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS,false); //所有日期格式都统一为一下格式,即yyyy-MM--dd HH:mm:ss objectMapper.setDateFormat(new SimpleDateFormat(DateTimeUtil.STANDARD_FORMAT)); //忽略 在json字符串中存在,但在Java对象中不存在对应属性的情况,防止错误 objectMapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES,false); } public static <T> String obj2String(T obj){ if (obj==null){ return null; } try { return obj instanceof String? (String)obj : objectMapper.writeValueAsString(obj); } catch (Exception e) { log.warn("Parse object to String error",e); return null; } } public static <T> String obj2StringPretty(T obj){ if (obj==null){ return null; } try { return obj instanceof String? (String)obj : objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(obj); } catch (Exception e) { log.warn("Parse object to String error",e); return null; } } public static <T> T string2Obj(String str,Class<T> clazz){ if (StringUtils.isEmpty(str)||clazz == null){ return null; } try { return clazz.equals(String.class)?(T) str : objectMapper.readValue(str,clazz); } catch (IOException e) { log.warn("Parse String to Object error",e); return null; } } public static <T> T string2Obj(String str, Class<?> collectionClass,Class<?>... elementClasses){ JavaType javaType = objectMapper.getTypeFactory().constructParametricType(collectionClass,elementClasses); try { return objectMapper.readValue(str,javaType); } catch (IOException e) { log.warn("Parse String to Object error",e); return null; } } public static <T> T string2Obj(String str, TypeReference<T> typeReference){ if (StringUtils.isEmpty(str)|| typeReference == null){ return null; } try { return (T)( typeReference.getType().equals(String.class)?str : objectMapper.readValue(str,typeReference)); } catch (IOException e) { log.warn("Parse String to Object error",e); return null; } } public static void main (String[] args){ User u1 = new User(); u1.setId(1); u1.setEmail("[email protected]"); User u2 = new User(); u2.setId(2); u2.setEmail("[email protected]"); String user1Json = JsonUtil.obj2String(u1); String user1JsonPretty = JsonUtil.obj2StringPretty(u1); log.info("user1Json:{}",user1Json); log.info("user1JsonPretty:{}",user1JsonPretty); User user = JsonUtil.string2Obj(user1Json,User.class); List<User> userList = Lists.newArrayList(); userList.add(u1); userList.add(u2); String userListStr = JsonUtil.obj2StringPretty(userList); log.info("=================="); log.info(userListStr); List<User> userListObj1 = JsonUtil.string2Obj(userListStr, new TypeReference<List<User>>() { }); List<User> userListObj2 = JsonUtil.string2Obj(userListStr,List.class,User.class); System.out.println("end"); } }
准备工作做完以后,就是使用redis来存储session了
4.单点登录Redis存储Session
以下代码是以单机多部署两个tomcat集群为基础的,如果只是一个tomcat,那么执行代码
RedisShardedPoolUtil.setEx(session.getId(), JsonUtil.obj2String(serverResponse.getData()),Const.RedisCacheExtime.REDIS_SESSION_EXTIME);
浏览器端cookie的value值和redis存储的key值肯定是一样的。
如果是两个tomcat,使用tomcat1登录,那么Cookie有了值,且保存到redis上了,再次发起请求,负载均衡到tomcat2上了,那么这个时候session.getId()得到的值在redis上是没有这个key,就会认为用户没有登录
@RequestMapping(value = "login.do",method = RequestMethod.POST) @ResponseBody public ServerResponse<User> login(String username, String password, HttpSession session, HttpServletResponse httpServletResponse){ ServerResponse<User> serverResponse = iUserService.login(username,password); if (serverResponse.isSuccess()){ // session.setAttribute(Const.CURRENT_USER,serverResponse.getData()); RedisShardedPoolUtil.setEx(session.getId(), JsonUtil.obj2String(serverResponse.getData()),Const.RedisCacheExtime.REDIS_SESSION_EXTIME); } return serverResponse; }
那么我们就写一个Cookie的工具类
@Slf4j public class CookieUtil { private final static String COOKIE_DOMAIN = ".XXX.com"; private final static String COOKIE_NAME = "study_login_token"; public static String readLoginToken(HttpServletRequest request){ Cookie[] cks = request.getCookies(); if (cks != null){ for (Cookie ck : cks){ log.info("read cookieName:{},cookieValue:{}",ck.getName(),ck.getValue()); if (StringUtils.equals(ck.getName(),COOKIE_NAME)){ log.info("read cookieName:{},cookieValue:{}",ck.getName(),ck.getValue()); return ck.getValue(); } } } return null; } public static void writeLoginToken(HttpServletResponse response,String token){ Cookie ck = new Cookie(COOKIE_NAME,token); ck.setDomain(COOKIE_DOMAIN); ck.setPath("/");//代表设置在根目录下 ck.setHttpOnly(true); //单位是秒。 //如果这个maxage不设置的话,cookie就不会写入硬盘,而是写在内存。只在当前页面有效 ck.setMaxAge(60*60*24*365);//如果是-1,代表永久 log.info("write cookieName:{},cookieValue:{}",ck.getName(),ck.getValue()); response.addCookie(ck); } public static void delLoginToken(HttpServletRequest request,HttpServletResponse response){ Cookie[] cks = request.getCookies(); if (cks != null){ for (Cookie ck : cks){ if (StringUtils.equals(ck.getName(),COOKIE_NAME)){ ck.setDomain(COOKIE_DOMAIN); ck.setPath("/"); ck.setMaxAge(0);//设置成0,代表删除此cookie log.info("del cookieName:{},cookieValue:{}",ck.getName(),ck.getValue()); response.addCookie(ck); return; } } } } }
通过这个类,我们登录的时候就从服务端给客户端种上了一个cookie,只要我们是在.XXX.com这个一级域名下进行操作,就能拿到这个cookie
@RequestMapping(value = "login.do",method = RequestMethod.POST) @ResponseBody public ServerResponse<User> login(String username, String password, HttpSession session, HttpServletResponse httpServletResponse){ ServerResponse<User> serverResponse = iUserService.login(username,password); if (serverResponse.isSuccess()){ // session.setAttribute(Const.CURRENT_USER,serverResponse.getData());
//登录的时候我们就把cookie种上返回给客户端,那么我们校验是否登录的时候就readLoginToken
//就是说我们现在不管你走哪个tomcat,cookie是一样的 CookieUtil.writeLoginToken(httpServletResponse,session.getId()); RedisShardedPoolUtil.setEx(session.getId(), JsonUtil.obj2String(serverResponse.getData()),Const.RedisCacheExtime.REDIS_SESSION_EXTIME); } return serverResponse; }
校验是否登录
@RequestMapping("cancel.do") @ResponseBody public ServerResponse cancel(HttpServletRequest request, Long orderNo){ //User user = (User) session.getAttribute(Const.CURRENT_USER); String loginToken = CookieUtil.readLoginToken(request); if(StringUtils.isEmpty(loginToken)){ return ServerResponse.createByErrorMessage("用户未登录,无法获取当前用户的信息"); } String userJsonStr = RedisShardedPoolUtil.get(loginToken); User user = JsonUtil.string2Obj(userJsonStr,User.class); if (user == null){ return ServerResponse.createByErrorCode(ResponseCode.NEED_LOGIN.getCode(),ResponseCode.NEED_LOGIN.getDesc()); } return iOrderService.cancle(user.getId(),orderNo); }
tomcat1和tomcat2还是会产生两个不同的Cookie,但同时我们又自己增加了name为study_login_token的cookie,所以我们只需要读取这个name,就可以判断用户登录状态
但现在又有了一个问题,我们存储在redis里面的“session”,一次登录以后有效期是30分钟,我们后面再执行其他操作,这个session的有效期是不会更新的,就是说一次登录只能玩30分钟,所以我们就用过滤器来解决这个问题
5.过滤器Filter重置session有效期
首先写好类
public class SessionExpireFilter implements Filter{ @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest; String loginToken = CookieUtil.readLoginToken(httpServletRequest); if (StringUtils.isNotEmpty(loginToken)){ //判断logintoken是否为空或者"" //如果不为空,符合条件,继续拿user信息 String userJsonStr = RedisShardedPoolUtil.get(loginToken); User user = JsonUtil.string2Obj(userJsonStr,User.class); if (user != null){ //如果user不为空,则重置session的时间,即调用expire命令 RedisShardedPoolUtil.expire(loginToken, Const.RedisCacheExtime.REDIS_SESSION_EXTIME); } } filterChain.doFilter(servletRequest,servletResponse); } @Override public void destroy() { } }
然后进入web.xml配置上这个过滤器
<filter> <filter-name>sesssionExpireFilter</filter-name> <filter-class>com.study.controller.commom.SessionExpireFilter</filter-class> </filter> <filter-mapping> <filter-name>sesssionExpireFilter</filter-name> <url-pattern>*.do</url-pattern> </filter-mapping>
现在单点登录就到这里了