(6)SprintBoot 2.X 基于Redis的分布式session
1.基本原理1. Session的作用2. 分布式Session存在的问题?3. 解决方案4. 本系统解决方案
2. 代码实现1. Controller业务逻辑2. 需要存到redis缓存中的key的前缀3. MiaoshaUserService实现分布式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
) {
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
;
}
public void addCookie(HttpServletResponse response
, String token
, MiaoshaUser user
) {
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);
if(user
!=null
){
addCookie(response
, token
, user
);
}
return user
;
}
}