一、登录功能利用分布式session

基本流程

1 用户(输入用户名、密码),点击登录
login.html

<div class="col-md-5">
	<button class="btn btn-primary btn-block" type="submit" onclick="login()">登录</button>
</div>

2 前端进行md5加密,然后跳转到后端"/login/do_login"进行登录校验

<script>
function login(){
	$("#loginForm").validate({
        submitHandler:function(form){
             doLogin();
        }    
    });
}
function doLogin(){
	g_showLoading();
	
	var inputPass = $("#password").val();
	var salt = g_passsword_salt;
	var str = ""+salt.charAt(0)+salt.charAt(2) + inputPass +salt.charAt(5) + salt.charAt(4);
	var password = md5(str);
	
	$.ajax({
		url: "/login/do_login",
	    type: "POST",
	    data:{
	    	mobile:$("#mobile").val(),
	    	password: password
	    },
	    success:function(data){
	    	layer.closeAll();
	    	if(data.code == 0){
	    		layer.msg("成功");
	    		window.location.href="/goods/to_list";
	    	}else{
	    		layer.msg(data.msg);
	    	}
	    },
	    error:function(){
	    	layer.closeAll();
	    }
	});
}
</script>

3 在"login/do_login"中,有两件事,一是“@Valid LoginVo loginVo”可以接收手机号密码,同时通过jsr303做手机号格式校验,二是调用MiaoshauserService的login()方法。

 @RequestMapping("/do_login")
    @ResponseBody
    public Result<Boolean> doLogin(HttpServletResponse response, @Valid LoginVo loginVo) {
    	log.info(loginVo.toString());
    	//登录
    	userService.login(response, loginVo);
    	return Result.success(true);
    }

4 先说接收手机号密码和手机号校验,它是通过jsr303做校验(面试只要说校验就好了,一开头十一个数字的正则表达式是"1\d{10}",然后校验非空,这不是面试重点)

@Data
public class LoginVo {
    @NotNull
    @IsMobile
    private String mobile;

    @NotNull
    @Length(min=32)
    private String password;
}

5 然后呢,在login()方法中,先通过输入的手机号判断用户是否存在,若不存在,则抛出"MOBILE_NOT_EXIST";若存在,则将前端传来的密码(已经过一次md5)和从数据库中取出的salt再次进行md5,将最终得到的值和数据库中的密码比较。如果二者不相等,则抛出“PASSWORD_ERROR”异常;若相等,则生成cookie,cookie中的值是一随机数token(每个用户不同),同时将此token作为redis的key,user作为redis的value保存在redis中。返回true,用户登录成功。

	public boolean 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);
		}
		//生成cookie
		String token	 = UUIDUtil.uuid();
		addCookie(response, token, user);
		return true;
	}

	public static final String COOKI_NAME_TOKEN = "token";
	
	private void addCookie(HttpServletResponse response, String token, MiaoshaUser user) {
		redisService.set(MiaoshaUserKey.token, token, user);
		Cookie cookie = new Cookie(COOKI_NAME_TOKEN, token);
		cookie.setMaxAge(MiaoshaUserKey.token.expireSeconds());
		cookie.setPath("/");
		response.addCookie(cookie);
	}

6 用户登录后,跳转到"/goods/to_list"

if(data.code == 0){
	    		layer.msg("成功");
	    		window.location.href="/goods/to_list";}

7 在to_list中,判断用户请求是否携带cookie,如果没有携带cookie,让用户重新登录(防止用户直接通过url访问商品列表页面);如果携带了cookie,则调用userService.getByToken方法,从redis中获取用户,然后,跳转到商品列表详情页 “goods_list.html”

@RequestMapping("to_list")
public String toList(@CookieValue(value= CookieUtil.COOKIE_NAME,required = false) String cookieToken,
                   @RequestParam(value = CookieUtil.COOKIE_NAME,required = false) String paramToken,
                     Model model,HttpServletResponse response){
    if(StringUtils.isEmpty(cookieToken) && StringUtils.isEmpty(paramToken)){
        return "login";
    }
    String token = StringUtils.isEmpty(paramToken)?cookieToken:paramToken;
    MiaoshaUser user = userService.getByToken(token,response);
    model.addAttribute("user",user);
    return "goods_list";
}

8 在userService.getByToken方法中,通过传入的token获取用户,同时需要重新设置redis的过期时间

public MiaoshaUser getByToken(String token,HttpServletResponse response) {
    //先判断token是否为空
    if(StringUtils.isEmpty(token)){
        return null;
    }
    //根据token到redis中拿到相应的value
    MiaoshaUser user = redisService.get(MiaoshaUserKey.token,token,MiaoshaUser.class);
    redisService.set(MiaoshaUserKey.token,token,user);//key--->UserKey:tkUUID,value--->Serialized User
    //如果此时拿到user成功了,这里要重新设置一下redis过期时间
    if(user != null){
        redisService.set(MiaoshaUserKey.token,token,user);
    }
    return user;
}

*代码改进(非必须)

我们发现,后面涉及到商品等其他的接口,按照这种写法,每次都要先获取cookie,然后从redis中获取user信息,获取成功,我们才能进行下一步操作。显然太过冗余,我们可以将其剥离出来,写在一个地方,避免冗余的代码。
我们的controller可以写成:

@RequestMapping("to_list")
public String toList(Model model,HttpServletResponse response,MiaoshaUser user){
    model.addAttribute("user",user);
    return "goods_list";
}

那么,我们在一个地方统一判断user是否能获取到。就要用到springmvc的机制了,我们可以试想springmvc支持的参数都是如何进来的呢?比如这里的MiaoShaUser是从什么地方注入进来的呢?

其实在UserArgumentResolver这个类中就可以拿到输入的参数,比如MiaoShaUser这个对象,然后再在resolveArgument这个方法里,对这个参数进行相应的处理:

@Service
public class UserArgumentResolver implements HandlerMethodArgumentResolver{
    @Autowired
    private MiaoshaUserService userService;
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        Class<?> clazz = parameter.getParameterType();
        return clazz== MiaoshaUser.class;
    }

    @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(CookieUtil.COOKIE_NAME);
        String cookieToken = CookieUtil.readLoginToken(request);
        if(StringUtils.isEmpty(cookieToken) && StringUtils.isEmpty(paramToken)){
            return "login";
        }
        String token = StringUtils.isEmpty(paramToken)?cookieToken:paramToken;
        return userService.getByToken(token,response);
    }
}

当然,这个对传入的参数进行修改的UserArgumentResolver要被重新加入进argumentResolvers中,相当于完成对原始的argumentResolvers中某个参数的重写:

@Configuration
public class WebConfig extends WebMvcConfigurerAdapter{
    @Autowired
    private UserArgumentResolver userArgumentResolver;

    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
        argumentResolvers.add(userArgumentResolver);
    }
}

这样,只要某个方法中传入了MiaoShaUser这个对象,那么就会进入resolveArgument()这个方法进行判断是否能拿到这个对象(这个要注意!!!!)。

当然,我们可能更加常用的方式是springmvc拦截器来实现这个功能。并且在拦截器中,还可以实现更加复杂的逻辑,比如不仅可以判断user是否已经登陆,还可以针对特殊的url进行特别的处理。

部分摘自:http://fossi.oursnail.cn/2019/04/23/miaosha/2.整合redis/

猜你喜欢

转载自blog.csdn.net/NIUBILISI/article/details/89853664