SpringBoot集成jedis+protostuff 简单实现分布式session

一、首先介绍我们的业务需求:

  • 1、用户浏览产品列表的时候必须处于登陆状态
  • 2、当用户没有登陆就去访问产品列表的时候,跳转到登陆页面
  • 3、每次用户重新打开浏览器的时候必须重新登陆
  • 4、在一定时间之后登陆状态置为失效
  • 5、注销功能暂且不做,只是初步实现一个简单的分布式session方案。

二、补充知识点简介

1、protostuff是一款谷歌开源的序列化工具,他的效率相比jdk自带的序列化要快很多。我们使用它来序列化User对象到redis中存储。并且也使用它从redis取出User的byte[]数据反序列化成User对象。

2、(重要!)Jedis和BinaryJedis的区别:jedis是redis的java客户端,用户可以很轻松的通过Jedis类的set方法传入key和value保存到redis中,BinaryJedis是Jedis类的父类,它的set方法key和value都是需要传入byte[]类型的数据的。

下图是Jedis类的set方法

SpringBoot集成jedis+protostuff 简单实现分布式session_第1张图片

它调用的是client.set方法,如下图所示

大家注意这个encode方法:它的作用就是将key和value转成byte[]类型的数据,之后其实调用的是父类BinaryJedis的set方法,如下图所示:

因此我们如果要将User对象存入redis中,必须要对user对象进行序列化,而且key也要进行序列化,这样才能调用这个BinaryJedis的set方法!这步一定要注意,不能只序列化value不序列化key。

三、实现思路

1、编写一个UserVO,用来承接用户信息,请务必注意这里需要实现Serializable接口。因为你要序列化的是这个VO。其他get/set代码不在这里阐述,不是重点。

SpringBoot集成jedis+protostuff 简单实现分布式session_第2张图片

2、编写登录页面,商品列表页面及后台代码。相关代码不是本章重点所以不占用太多篇幅展示。

3、编写我们的redisService,主要是方便我们执行redis的相关增删改查操作以及其他功能。这部分重载的set表明了Jedis和BinaryJedis中set的区别。

package com.umbrella.core.common.redis_manage;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

@Service
public class RedisService {
	
	@Autowired
	JedisPool jedisPool;

	/**
	 * get单个redis数据
	 * @param key
	 * @return T
	 */
	public  T get(String key) {
		 Jedis jedis = null;
		 try {
			 jedis =  jedisPool.getResource();
			 T t = (T) jedis.get(key);
			 return t;
		 }finally {
			 if(jedis != null) {
				 jedis.close();
			 }
		 }
	}

	/**
	 * get单个redis数据
	 * @param key
	 * @return T
	 */
	public byte[] get(byte[] key) {
		Jedis jedis = null;
		try {
			jedis =  jedisPool.getResource();
			byte[] bytes = jedis.get(key);
			return bytes;
		}finally {
			if(jedis != null) {
				jedis.close();
			}
		}
	}

	/**
	 * 存redis单个值
	 * @param key
	 * @param value
	 * @param 
	 * @return
	 */
	public  boolean set(String key,  T value) {
		 Jedis jedis = null;
		 try {
			 jedis =  jedisPool.getResource();
			 if(key == null || key.length() <= 0) {
				 return false;
			 }
			jedis.set(key, value.toString());
			 return true;
		 }finally {
			 if(jedis != null) {
				 jedis.close();
			 }
		 }
	}

	/**
	 * 存redis单个值
	 * @param key
	 * @param value
	 * @return
	 */
	public boolean set(String key,  byte[] value) {
		Jedis jedis = null;
		try {
			jedis =  jedisPool.getResource();
			if(key == null || key.length() <= 0) {
				return false;
			}
			jedis.set(key.getBytes(), value);
			return true;
		}finally {
			if(jedis != null) {
				jedis.close();
			}
		}
	}
	
	/**
	 * 删除
	 * */
	public boolean delete(String key) {
		Jedis jedis = null;
		try {
			jedis = jedisPool.getResource();
			long ret = jedis.del(key);
			return ret > 0;
		} finally {
			if (jedis != null) {
				jedis.close();
			}
		}
	}


	/**
	 * 更新redis失效日期
	 * @param key
	 * @param second
	 * @param 
	 * @return
	 */
	public long expire(String key,int second) {
		Jedis jedis = null;
		try {
			jedis = jedisPool.getResource();
			long ret = jedis.expire(key,second);
			return ret;
		} finally {
			if (jedis != null) {
				jedis.close();
			}
		}
	}
}

4、编写我们的序列化,反序列化方法:

(1)引入pom

        
            io.protostuff
            protostuff-core
            1.6.0
        

        
            io.protostuff
            protostuff-runtime
            1.6.0
        

(2)编写工具类

package com.umbrella.core.common.common_util;

import io.protostuff.LinkedBuffer;
import io.protostuff.ProtostuffIOUtil;
import io.protostuff.Schema;
import io.protostuff.runtime.RuntimeSchema;

/**
 * @program: com.umbrella.core.common.common_util
 * @description:
 * @author: liujinghui
 * @create: 2018-12-08 15:32
 **/
public class ProtostuffUtil {

    /**
     * 序列化
     */
    public static  byte[] serializer(T t){
        Schema schema = RuntimeSchema.getSchema(t.getClass());
        return ProtostuffIOUtil.toByteArray(t,schema,
                LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE));

    }

    /**
     * 反序列化
     */
    public static  T deserializer(byte []bytes,Class c) {
        T t = null;
        try {
            t = c.newInstance();
            Schema schema = RuntimeSchema.getSchema(t.getClass());
            ProtostuffIOUtil.mergeFrom(bytes,t,schema);
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return t;
    }

}

4、编写我们对于session,cookie的方法:

实现思路如下:

4.1 用户登录后取sessionId作为token,并且作为redis中session缓存信息的key值。接下来取用户userVO对象作为session缓存的value值,并且以序列化的方式存储。

4.2 并且设置Cookie,Cookie的key我们定义一个常量表示,value定义成刚才的token。

4.3 这样用户在请求页面的时候就会带着这个token,而我们拿到这个token时去redis中get,得到用户信息,并且重置redis失效时间。如果失效或者无法找到用户信息,那么就跳转登陆页面。

下面我们一步一步去实现:

编写常量:

public class SessionCookieConstant {
    public static final String  COOKIE_TOKEN_NAME = "CookieToken";
    public static final int  COOKIE_MAX_AGE = 60;
    public static final String  COOKIE_PATH = "/";
}

编写CookieHelper,方便对Cookie的操作:

/**
 * @program: com.umbrella.core.common.session_manage
 * @description:
 * @author: liujinghui
 * @create: 2018-12-02 21:53
 **/
@Component
public class CookieHelper {

    @Autowired
    RedisService redisService;

    public boolean addCookie(HttpServletResponse response, String token, UserVO user) {
        try{
            if(null == token || "".equals(token)){
                token = UUID.randomUUID().toString().replace("-","");
            }
            if(null == user){
                return false;
            }
            Cookie cookie = new Cookie(SessionCookieConstant.COOKIE_TOKEN_NAME, token);
            cookie.setMaxAge(SessionCookieConstant.COOKIE_MAX_AGE);//60秒过期
            cookie.setPath(SessionCookieConstant.COOKIE_PATH);//网站根目录
            response.addCookie(cookie);//写入到客户端
            //写入Redis
            redisService.set(token, ProtostuffUtil.serializer(user));
            //因为登陆成功所以设置过期时间
            redisService.expire(token,SessionCookieConstant.COOKIE_MAX_AGE);
        }catch(Exception e){
            e.printStackTrace();
            return false;
        }
        return true;
    }

    public UserVO getCookie(HttpServletRequest request){
        try{
            String token = request.getSession().getId();
            byte[] bytes = redisService.get(token.getBytes());
            UserVO user = ProtostuffUtil.deserializer(bytes,UserVO.class);
            if(null != user){
                //因为用户有持续行为 所以重置过期时间
                redisService.expire(token,SessionCookieConstant.COOKIE_MAX_AGE);
                return user;
            }
        }catch(Exception e){
            e.printStackTrace();
        }
        return null;
    }
}

之后我们改进登陆代码:当根据用户名密码获取到用户时,我们认为登陆成功,并且设置Cookie -> cookieHelper.addCookie

    /**
     * 执行登陆
     * @param //request
     * @return
     * @throws Exception
     */
    @RequestMapping("/doLogin")
    @ResponseBody
    public ResultVO doLogin(HttpServletRequest request, HttpServletResponse response, HttpSession session, Model model, UserVO user) throws Exception {
        //输入内容校验
        if(null == user || null == user.getUserName() || null == user.getUserPassword()){
            return Result.error(LoginConstant.EMPTY_INPUT);
        }
        UserVO userVO = null;
        try{
            userVO = loginService.loginUserByUserNameAndUserPassword(user.getUserName(),user.getUserPassword());
            if(null == userVO){
                return Result.error(LoginConstant.WRONG_USERNAME_PASSWORD);
            }
            //!!!!登陆成功后我们就设置我们的Cookie
            cookieHelper.addCookie(response,session.getId(),userVO);
        }catch(Exception e){
            e.printStackTrace();
           return Result.error(e,LoginConstant.QUERY_USER_INFO_ERROR);
        }
        return Result.success(LoginConstant.LOGIN_SUCCESS);
    }

之后多次访问商品列表页面的时候教研我们的Cookie中的token是否过期:

    @RequestMapping("/listDefaultProducts")
    public String listDefaultProducts(HttpServletRequest request,HttpServletResponse response, Model model) {
        UserVO user = cookieHelper.getCookie(request);
        if(null != user){
            //如果获取到User 则放入model中
            model.addAttribute("user", user);
        }else{
            //如果没有获得User,那就去登陆吧
            model.addAttribute("redirectPath", "/listDefaultProducts");
            return "login/login";
        }
        List defaultProductVOList = iProductManageService.getDefaultProductVOList();
        model.addAttribute("defaultProductVOList", defaultProductVOList);
        iProductManageService.initProductsToRedis();
        return "defaultproductslist";
    }

注意:以上代码均为部分关键代码,也是我的初步实现,后续会改进的更强大更丰富。在实践中不断学习。

你可能感兴趣的:(Spring,Boot,redis,分布式技术)