八、分布式Session和参数解析

实际应用中使用分布式的多台应用服务器,若使用原生的Session,用户第一个请求在第一台服务器上,第二个请求在另一台服务器上,用户的Session信息就丢失了。

解决方法是使用Session同步,从一台服务器上同步到另一台服务器上,但因性能问题,实现复杂,并不常用。

分布式Session的几种实现方式

这里使用分布式缓存存储Session信息来实现分布式Session

  1. 用户登录成功后生成token(SessionId)来标识用户,写到Cookie中传递给客户端,在随后的访问中Cookie中都包含这个token。
  2. 服务端根据传来的token取得用户的Session信息

实现分布式Session

MiaoshaUserService中

  1. 使用UUID生成token
  2. 将token(key)和对应用户(Session信息,value)写入第三方缓存Redis
  3. 生成Cookie,将token写入Cookie
  4. 将Cookie写入response
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);
        }
        //生成token,Cookie,将token和对应用户写入第三方缓存redis
        String token = UUIDUtil.uuid();
        addCookie(user, response, token);
        return token;
    }
    
    private void addCookie(MiaoshaUser user, HttpServletResponse response, String token) {
        //将token和对应用户写入第三方缓存redis
        //set(prefix, key, value),这里使用prefix+key作为redis的key
        redisService.set(MiaoshaUserKey.token,token,user);
        //生成Cookie
        Cookie cookie = new Cookie(COOKIE_NAME_TOKEN, token);
        cookie.setMaxAge(MiaoshaUserKey.token.expireSeconds()); //设置有效期和session有效期一致
        //设置网站的根目录
        cookie.setPath("/");
        //写到response中
        response.addCookie(cookie);
    } 
    //从redis获得session信息
    //session有效期为最后一次访问时间加有效期时长
    public MiaoshaUser getByToken(String token, HttpServletResponse response) {
        if (StringUtils.isEmpty(token)) {
            return null;
        }
        MiaoshaUser user = redisService.get(MiaoshaUserKey.token, token, MiaoshaUser.class);
        //生成新cookie,来延长有效期
        if (user != null) {
            addCookie(user, response, token);
        }
        return user;
    }

UUIDUtil

public class UUIDUtil {

    public static String uuid() {
        //去掉原生UUID中的横杠
        return UUID.randomUUID().toString().replace("-","");
    }
}

登录成功后查看发现请求Cookie中包含了token
在这里插入图片描述
这里登录成功后跳转到goods/to_list

$.ajax({
            url:"/login/do_login",
            type:"POST",
            data:{
                mobile:$("#mobile").val(),
                password: password
            },
            success:function (res) {
                layer.closeAll();
                if (res.code == 0) {
                    layer.msg("成功");
                    window.location.href="/goods/to_list";
                } else {
                    layer.msg(res.msg);
                }
                console.log(res);
            },
            error:function () {
                layer.closeAll();
            }
        });

跳转到goods/to_list,服务端接收token以获取Session信息,若无token跳转回登录页面,若成功获得session信息,生成新Cookie装入token,并修改缓存来延长session有效期

//有时候手机端不会把token放入cookie,直接作为参数传
//首先先从param中取token,取不到再从cookie中取
@RequestMapping("/to_list")
	public String toList(Model model,@CookieValue(value=MiaoshaUserService.COOKIE_NAME_TOKEN)String cookieToken,
			@RequestParam(value=MiaoshaUserService.COOKIE_NAME_TOKEN)String paramToken,HttpServletResponse response) {
		if(StringUtils.isEmpty(paramToken)&&StringUtils.isEmpty(cookieToken)) {
			return "login";//返回登录页面
		}
		String token=StringUtils.isEmpty(paramToken)?cookieToken:paramToken;	
		MiaoshaUser user=miaoshaUserService.getByToken(token,response);
		model.addAttribute("user", user);
		return "goods_list";//返回商品列表页面
	}

参数解析

其他页面有可能也需要获取token,获取MiaoshaUser对象,为了简化代码
我们希望直接将MiaoshaUser作为参数注入到Controller的方法中,像request, response, model一样可以直接获取,这些参数都是由ArgumentResolver往Controller框架里赋值。同样我们要对MiaoshaUser做参数解析,这样遍历方法参数时,发现有该参数,就会给它赋值。

@RequestMapping("/to_list")
    public String toList(Model model, MiaoshaUser user) {
        model.addAttribute("user", user);
        return "goods_list";
    }
  1. 为MiaoshaUser写一个UserArgumentResolver,实现接口HandlerMethodArgumentResolver
@Service
public class UserArgumentResolver implements HandlerMethodArgumentResolver {

    @Autowired
    MiaoshaUserService userService;

    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        Class<?> clazz = parameter.getParameterType();
        return clazz == MiaoshaUser.class; //类型是MiaoshaUser才做处理
    }

    //做获取token和session信息的操作
    @Override
    public Object resolveArgument(MethodParameter methodParameter,
           ModelAndViewContainer modelAndViewContainer, NativeWebRequest webRequest,
           WebDataBinderFactory webDataBinderFactory) throws Exception {
        HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
        HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);

        String paramToken = request.getParameter(MiaoshaUserService.COOKIE_NAME_TOKEN);
        String cookieToken = getCookieValue(request, MiaoshaUserService.COOKIE_NAME_TOKEN);
        if (StringUtils.isEmpty(cookieToken) && StringUtils.isEmpty(paramToken)) {
            return "login";
        }
        String token = StringUtils.isEmpty(paramToken)? cookieToken : paramToken;
        return userService.getByToken(token, response);
    }

    private String getCookieValue(HttpServletRequest request, String cookieName) {
        Cookie[] cookies = request.getCookies(); 
        //遍历所有cookie
        for (Cookie cookie:cookies) {
            if (cookie.getName().equals(cookieName)) {
                return cookie.getValue();
            }
        }
        return null;
    }
}
  1. 新建WebConfig继承WebMvcConfigurerAdapter,注册UserArgumentResolver
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter{

    @Autowired
    UserArgumentResolver userArgumentResolver;

    //给controller的方法赋值
    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
        argumentResolvers.add(userArgumentResolver);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值