17.session不共享问题

问题


多台Tomcat并不共享session存储空间,当请求切换到不同的tomcat服务时导致数据丢失问题。

考虑到以后微服务部署多个项目,也就是多个tomcat就会出现session不共享问题。

替代方案满足条件


1.数据共享

2.内存存储,因为session就是基于内存的,访问效率高。

3.key,value结构。

解决方案


redis是存在于tomcat以外一个服务,就能实现数据共享。

基于redis实现共享session登录

17.session不共享问题_第1张图片

17.session不共享问题_第2张图片

17.session不共享问题_第3张图片

UserService发送验证码、用户登录功能

package com.xkj.org.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.RandomUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.xkj.org.dto.LoginFormDTO;
import com.xkj.org.dto.Result;
import com.xkj.org.dto.UserDTO;
import com.xkj.org.entity.User;
import com.xkj.org.mapper.UserMapper;
import com.xkj.org.service.IUserService;
import com.xkj.org.utils.RedisConstants;
import com.xkj.org.utils.RegexUtils;
import com.xkj.org.utils.SystemConstants;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpSession;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@Slf4j
@Service
public class UserServiceImpl extends ServiceImpl implements IUserService{

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Override
    public Result sendCode(String phone, HttpSession session) {
        //检验手机号
        if (RegexUtils.isPhoneInvalid(phone)) {
            return Result.fail("手机号无效");
        }
        //生成验证码
        String code = RandomUtil.randomString(6);
        //存入Redis,key值定义带上业务功能,防止其他使用手机号的功能模块重复,设置过期时间
        stringRedisTemplate.opsForValue().set(RedisConstants.LOGIN_CODE_KEY + phone, code,
                RedisConstants.LOGIN_CODE_TTL, TimeUnit.MINUTES);
        //发送验证码给用户
        log.info("给用户发送验证码============={}", code);
        //返回
        return Result.ok("发送成功");
    }

    @Override
    public Result login(LoginFormDTO loginFormDTO, HttpSession session) {
        //校验手机号
        if (RegexUtils.isPhoneInvalid(loginFormDTO.getPhone())) {
            return Result.fail("手机号无效");
        }
        //校验验证码
        String cacheCode = stringRedisTemplate.opsForValue().get(RedisConstants.LOGIN_CODE_KEY + loginFormDTO.getPhone());
        String code = loginFormDTO.getCode();
        if (code == null || !code.equals(cacheCode)) {
            return Result.fail("验证码错误");
        }
        //根据手机号查询用户
        User user = query().eq("phone", loginFormDTO.getPhone()).one();
        if (user == null) {
            //用户不存在则创建用户
            user = createUser(loginFormDTO.getPhone());
        }
        //将用户放入redis
        //随机生成token,作为登录令牌
        String token = UUID.randomUUID().toString(true);
        //将user对象转化为Hash存储
        //存储到redis
        UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);

        Map map = BeanUtil.beanToMap(userDTO, new HashMap<>(3),
                CopyOptions.create().ignoreNullValue()
                                    //把所有字段的value都转成String,因为StringRedisTemplate的key-value只支持String类型
                                    .setFieldValueEditor((fieldName, fieldValue) -> fieldValue.toString()));
        String userKey = RedisConstants.LOGIN_USER_KEY + token;
        stringRedisTemplate.opsForHash().putAll(userKey, map);
        //设置有效期,否则大量用户登录,长期占用内存,半个小时过期,需要重新登录
        stringRedisTemplate.expire(userKey, RedisConstants.LOGIN_USER_TTL, TimeUnit.MINUTES);
        //返回user的token
        return Result.ok(token);
    }

    private User createUser(String phone) {
        User user = new User();
        user.setPhone(phone);
        user.setNickName(SystemConstants.USER_NICK_NAME_PREFIX + RandomUtil.randomString(6));
        save(user);
        return user;
    }
}

拦截器

package com.xkj.org.interceptor;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import com.xkj.org.dto.UserDTO;
import com.xkj.org.utils.RedisConstants;
import com.xkj.org.utils.UserHolder;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;
import java.util.concurrent.TimeUnit;

public class LoginInterceptor implements HandlerInterceptor {

    private StringRedisTemplate stringRedisTemplate;

    public LoginInterceptor(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        //获取浏览器传递的token,在请求头上
        String token = request.getHeader("authorization");
        if(StrUtil.isBlank(token)) {
            //未登录,拦截
            response.setStatus(401);
            return false;
        }
        //基于token获取User
        Map entries = stringRedisTemplate.opsForHash().entries(RedisConstants.LOGIN_USER_KEY + token);
        // 这里map不会为null,只需要判断是否有元素即可
        if(entries.isEmpty()) {
            //未登录,拦截,redis中的token可能失效了,需要重新登录
            response.setStatus(401);
            return false;
        }
        //将map转成UserDTO对象
        UserDTO userDTO = BeanUtil.fillBeanWithMap(entries, new UserDTO(), false);
        //保存用户信息到ThreadLocal
        UserHolder.saveUser(userDTO);
        //刷新token在redis中有效期,访问一次token的有效期重新设置为30分钟
        stringRedisTemplate.expire(RedisConstants.LOGIN_USER_KEY + token, RedisConstants.LOGIN_USER_TTL,
                TimeUnit.MINUTES);
        //5.放行
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        //请求执行完毕,释放内存,threadlocal避免内存泄漏
        UserHolder.removeUser();
    }
}

给拦截器传递StringRedisTemplate对象

向拦截器中注入RedisTemplate对象,因为拦截器本身是通过new创建的,所以没有被spring管理,所以只有通过构造器的方式注入。 

package com.xkj.org.config;

import com.xkj.org.interceptor.LoginInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class MvcConfig implements WebMvcConfigurer{

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //添加拦截器,排除一些不需要拦截的url
        registry.addInterceptor(new LoginInterceptor(stringRedisTemplate))
        .excludePathPatterns(
                "/user/code",
                "/user/login",
                "/blog/hot",
                "/shop/**",
                "/shop-type/**",
                "/upload/**",
                "voucher/**"

        );
    }
}

你可能感兴趣的:(Redis,redis,java)