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方法
它调用的是client.set方法,如下图所示
大家注意这个encode方法:它的作用就是将key和value转成byte[]类型的数据,之后其实调用的是父类BinaryJedis的set方法,如下图所示:
因此我们如果要将User对象存入redis中,必须要对user对象进行序列化,而且key也要进行序列化,这样才能调用这个BinaryJedis的set方法!这步一定要注意,不能只序列化value不序列化key。
三、实现思路
1、编写一个UserVO,用来承接用户信息,请务必注意这里需要实现Serializable接口。因为你要序列化的是这个VO。其他get/set代码不在这里阐述,不是重点。
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.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";
}