(6)SprintBoot 2.X 基于Redis的分布式session
1.基本原理
1. Session的作用
-
Session 是客户端与服务器通讯会话跟踪技术,服务器与客户端保持整个通讯的会话基本信息。
-
客户端在第一次访问服务端的时候,服务端会响应一个sessionId并且将它存入到本地cookie中,在之后的访问会将cookie中的sessionId放入到请求头中去访问服务器,如果通过这个sessionid没有找到对应的数据那么服务器会创建一个新的sessionid并且响应给客户端。
2. 分布式Session存在的问题?
- 假设第一次访问服务A生成一个sessionid并且存入cookie中,第二次却访问服务B客户端会在cookie中读取sessionid加入到请求头中,如果在服务B通过sessionid没有找到对应的数据那么它创建一个新的并且将sessionid返回给客户端,这样并不能共享我们的Session无法达到我们想要的目的。
3. 解决方案
- 使用Redis作为session存储容器,登录时将session信息存储至cookie客户端,同时服务端将session信息存至redis缓存,双重保障,接下来的接口调用直接可以获取到cookie中的token信息作为参数传递进来即可,如果发现token为空,则再从redis中获取,如果两者都为空,则说明session已过期。
4. 本系统解决方案
- 用户登录成功之后,给这个用户生成一个sessionId(用token来标识这个用户,token利用uuid生成),写到cookie中,传递给客户端。然后客户端在随后的访问中,都在cookie中上传这个token,然后服务端拿到这个token之后,就根据这个token来取得对应的session信息。
2. 代码实现
1. Controller业务逻辑
@RequestMapping("/do_login")
@ResponseBody
public Result<String> doLogin(HttpServletResponse response , @Valid LoginVo loginVo) {
log.info(loginVo.toString());
String token = userService.login(response,loginVo);
return Result.success(token);
}
2. 需要存到redis缓存中的key的前缀
public class MiaoshaUserKey extends BasePrefix {
public static final int TOKEN_EXPIRE = 3600*24 *2;//默认两天
/**
* 防止被外面实例化
*/
private MiaoshaUserKey(int expireSeconds, String prefix) {
super(expireSeconds, prefix);
}
/**
* 需要缓存的字段
*/
public static MiaoshaUserKey token = new MiaoshaUserKey(TOKEN_EXPIRE,"tk");
public static MiaoshaUserKey getById = new MiaoshaUserKey(0, "id");
}
3. MiaoshaUserService实现分布式session
- 将token做为key,用户信息做为value 存入redis模拟session,键值对为(token,user)
同时将token存入cookie,返还给客户端保存登录状态,键值对为(“token”,token) - 当客户端再请发送请求时,如果getByToken()获取缓存对象成功,则需要再次调用addCookie()延长session的有效期。
@Service
public class MiaoshaUserService {
public static final String COOKIE_NAME_TOKEN = "token";
@Autowired
MiaoShaUserDao miaoShaUserDao;
@Autowired
RedisService redisService;
public MiaoshaUser getById(long id){
//对象缓存
MiaoshaUser user = redisService.get(MiaoshaUserKey.getById, "" + id, MiaoshaUser.class);
if (user != null) {
return user;
}
//取数据库
user = miaoShaUserDao.getById(id);
//再存入缓存
if (user != null) {
redisService.set(MiaoshaUserKey.getById, "" + id, user);
}
return user;
}
/**
* 典型缓存同步场景:更新密码
*/
public boolean updatePassword(String token, long id, String formPass) {
//取user
MiaoshaUser user = getById(id);
if(user == null) {
throw new GlobalException(CodeMsg.MOBILE_NOT_EXIST);
}
//更新数据库
MiaoshaUser toBeUpdate = new MiaoshaUser();
toBeUpdate.setId(id);
toBeUpdate.setPassword(MD5Util.formPassToDBPass(formPass, user.getSalt()));
miaoShaUserDao.update(toBeUpdate);
//更新缓存:先删除再插入
redisService.delete(MiaoshaUserKey.getById, ""+id);
user.setPassword(toBeUpdate.getPassword());
redisService.set(MiaoshaUserKey.token, token, user);
return true;
}
public String login(HttpServletResponse response,LoginVo loginVo){
if (loginVo == null) {
throw new GlobalException(CodeMsg.SERVER_ERROR);
}
String mobile = loginVo.getMobile();
String formPass = loginVo.getPassword();
//判断手机号是否存在
MiaoshaUser user = getById(Long.parseLong(mobile));
if (user == null) {
throw new GlobalException(CodeMsg.MOBILE_NOT_EXIST);
}
//验证密码
String dbPass = user.getPassword();
String saltDB = user.getSalt();
String calcPass = MD5Util.formPassToDBPass(formPass, saltDB);
if (!calcPass.equals(dbPass)) {
throw new GlobalException(CodeMsg.PASSWORD_ERROR);
}
String token = UUIDUtil.uuid();
addCookie(response, token, user);
return token;
}
/**
* 将token做为key,用户信息做为value 存入redis模拟session,键值对为(token,user)
* 同时将token存入cookie,保存登录状态,键值对为("token",token)
*/
public void addCookie(HttpServletResponse response, String token , MiaoshaUser user) {
//String token = UUIDUtil.uuid();
redisService.set(MiaoshaUserKey.token, token, user);
Cookie cookie = new Cookie(COOKIE_NAME_TOKEN, token);
cookie.setMaxAge(MiaoshaUserKey.token.expireSeconds());
cookie.setPath("/");//设置为网站根目录
response.addCookie(cookie);
}
public MiaoshaUser getByToken(HttpServletResponse response, String token) {
if(StringUtils.isEmpty(token)){
return null;
}
MiaoshaUser user = redisService.get(MiaoshaUserKey.token,token,MiaoshaUser.class);
//延长缓存key的有效期
if(user !=null){
addCookie(response, token, user);
}
return user;
}
}