关于集群状态下session共享问题解决
问题: 集群状态下,登录时,如果使用传统的方式将session记录到本地,那么当访问其他服务器时,无法获取到session,又需要进行登录。
解决办法:使用redis解决,将 session保存在redis服务器(key,value格式保存),用户登录时,服务器都去redis中进行查询session
redis保存session的存储格式
本地session的过程和格式:
Request.getSession.getAttr(Constants.USER_NAME);
第一步: cookie 中 JESSIONID
K : JESSIONID
V : Session
而 session: 格式: K : V
K: Constants.USER_NAME
V: 用户对象
K: 验证码:名称
V: 验证码值
在redis中以 K,V的格式进行存储,因为key唯一,所以通过拼接。
用户名:
K : JESSIONID:Contants.USER_NAME
V : 用户名或用户对象(用户对象需要转json字符串)
验证码:
K: JESSIONID:Contants.CODE_NAME
V: 验证码的值
redis保存session的过期时间设置
浏览器访问不同服务器时
问题:当用户进行请求时,经过负载均衡,传到 tomcat01,从本地查找session,如果找不到request请求就创建一个JSESSIONID。当用户下次请求tomcat02,从本地查找session,还是找不到,则request请求又创建一个JSESSIONID,会覆盖之前的JSESSIONID。
解决办法: 因为JSESSIONID会被request请求使用,所有我们自定义一个CSESSIONID(随意起名)
第一次访问时: 自己创建一个CSESSIONID(模拟JSESSIONID的创建) UUID 36 去掉 - 32位 然后保存在cookie中,第二次访问从cookie中取出CSESSIONID。
代码实现:SessionProvider.java
保存用户名或验证码到Redis中,实现session共享
注意事项: session的有效时间,是从最后一次请求开始计算时间。
package cn.jay.core.service.user;
import org.springframework.beans.factory.annotation.Autowired;
import cn.jay.common.web.Constants;
import redis.clients.jedis.Jedis;
/**
* 保存用户名或验证码到Redis中
* Session共享
* @author lx
*
*/
public class SessionProviderImpl implements SessionProvider{
@Autowired
private Jedis jedis;
private Integer exp = 30;// 可以修改session的有效时间
public void setExp(Integer exp) {
this.exp = exp;
}
@Override
public void setAttribuerForUsername(String name, String value) {
// TODO Auto-generated method stub
//保存用户名到Redis中
// K : CSESSIONID:Constants.USER_NAME == name
jedis.set(name + ":" + Constants.USER_NAME, value);
//时间
jedis.expire(name + ":" + Constants.USER_NAME, 60*exp);
}
@Override
public String getAttributeForUsername(String name) {
// TODO Auto-generated method stub
//fbb2016
String value = jedis.get(name + ":" + Constants.USER_NAME);
if(null != value){
//时间 这里就是每次请求重置有效时间
jedis.expire(name + ":" + Constants.USER_NAME, 60*exp);
}
return value;
}
}
手动实例化: 目的在配置文件中手动设置 session有效时间
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:task="http://www.springframework.org/schema/task"
xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
http://www.springframework.org/schema/task
http://www.springframework.org/schema/task/spring-task-4.0.xsd
http://code.alibabatech.com/schema/dubbo
http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
<!-- Session提供类实例化 : 可以在配置文件中修改session的默认有效时间 -->
<bean id="sessionProvider" class="cn.itcast.core.service.user.SessionProviderImpl">
<!-- 设置Session时间 默认30分钟 -->
<property name="exp" value="60"/>
</bean>
</beans>
创建RequestUtil工具类,获取、创建 CSESSIONID
package cn.jay.common.utils;
import java.util.UUID;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 获取CSESSIONID
* @author lx
*
*/
public class RequestUtils {
//获取
public static String getCSESSIONID(HttpServletRequest request,HttpServletResponse response){
//1:取出Cookie
Cookie[] cookies = request.getCookies();
if(null != cookies && cookies.length > 0){
for (Cookie cookie : cookies) {
//2: 判断COokie中是否有CSESSIONID
if("CSESSIONID".equals(cookie.getName())){
//3:有 直接使用
return cookie.getValue();
}
}
}
//4:没有 创建一个CSESSIONID 并保存到COOKIE中 同时 把此COOKIe写回浏览器 使用此生成的CSESSIONID
String csessionid = UUID.randomUUID().toString().replaceAll("-", "");
Cookie cookie = new Cookie("CSESSIONID",csessionid);
//设置 cookie存活时间 -1 0 >0
// -1 关闭浏览器后销毁 0 立刻销毁 >0 这个时间结束后销毁
cookie.setMaxAge(-1);
//设置路径
cookie.setPath("/");
//设置跨域 这是京东域名: www.jd.com search.jd.com item.jd.com
//cookie.setDomain(".jd.com"); // 括号中根跨域的规则
response.addCookie(cookie);
return csessionid;
}
}
登录代码中使用 – 登录成功后保存到redis服务器。并返回到之前的页面:
@Autowired
private BuyerService buyerService;
@Autowired
private SessionProvider sessionProvider;
//提交登陆
@RequestMapping(value = "/login.aspx",method=RequestMethod.POST)
public String login(String username,String password,String returnUrl,
HttpServletRequest request,HttpServletResponse response, Model model){
//1:用户名不能为空
if(null != username){
//2:密码不能为空
if(null != password){
//3:用户名必须正确
Buyer buyer = buyerService.selectBuyerByUsername(username);
if(null != buyer){
//4:密码必须正确
if(buyer.getPassword().equals(encodePassword(password))){
//5:保存用户名到Session中(Redis中)
sessionProvider.setAttribuerForUsername(RequestUtils.getCSESSIONID(request, response), buyer.getUsername());
//6:跳转到之前访问页面
return "redirect:" + returnUrl;
}else{
model.addAttribute("error", "密码必须正确");
}
}else{
model.addAttribute("error", "用户名必须正确");
}
}else{
model.addAttribute("error", "密码不能为空");
}
}else{
model.addAttribute("error", "用户名不能为空");
}
return "login";
}
登录时传的URL
这里对后面的url进行了编码,处理遇到中文是无法传参和中文乱码问题
做法就像jd这样,登录是记录之前正在浏览的页面url,就是登录请求后面拼接原来页面的请求。