Web项目(二)————实现注册、登录

目录:

1.实现概述

2.注册功能实现

3.登录功能实现

4.状态判断

1.实现概述:

总体思想:注册、登陆之后,服务器会生成一个短期有效的ticket,并将这个ticket存入浏览器的cookie中,之后再使用Interception根据cookie中的ticket渲染网页。

configuration.ToutiaoWebConfiguration:用于将interceptor(拦截器)与网页进行关联。

controller.LoginController:用于注册、登录操作的前端交互。

model.LoginTicket和dao.LoginTicketDAO:用于服务器给登录用户下发的ticket,即用户已登录的凭证。

model.HostHolder:所有属性定义成静态ThreadLocal的,可以实现多线程(多个用户同时访问网站)。

interception.PassportInterception:interception拦截器,为一种面向切面的思想,本类用于加载网页前判断是否已登录。

interception.LoginRequiredInterception:设置权限网页,如果权限不够,自动跳回首页。

Service.UserService:用于实现注册、登录的逻辑判断。

2.注册功能实现:

在UserService实现逻辑判断:

public Map<String ,Object> Register(String username, String password){
        Map<String ,Object> map=new HashMap<>();
        if(StringUtils.isBlank(username)){
            map.put("msgname","用户名不能为空");
            return map;
        }
        if(StringUtils.isBlank(username)){
            map.put("msgpassword","密码不能为空");
            return map;

        }
        User user=userDAO.selectByName(username);
        if(user!=null){
            map.put("msgname","用户名已经被注册");
            return map;

        }
        user=new User();
        user.setName(username);
        user.setSalt(UUID.randomUUID().toString().substring(0,5));//为了进一步加密密码而生成的随机数
        user.setHeadUrl(String.format("http://images.nowcoder.com/head/%dt.png", new Random().nextInt(1000)));
        user.setPassword(ToutiaoUtil.MD5(password+user.getSalt()));//使用MD5加密password+salt更加安全
        userDAO.addUser(user);

        //注册结束直接登录
        String ticket=addLoginTicket(user.getId());
        map.put("ticket",ticket);

        return map;
    }



private String addLoginTicket(int userId){
        LoginTicket ticket=new LoginTicket();
        ticket.setUserId(userId);

        Date date=new Date();
        date.setTime(date.getTime()+1000*3600*24);
        ticket.setExpired(date);//设置ticket的过期时间

        ticket.setStatus(0);//用于标记当前ticket是否有效,0为有效,1为失效
        ticket.setTicket(UUID.randomUUID().toString().replaceAll("-",""));

        loginTicketDAO.addTicket(ticket);//存储它的ticket以便于之后一段时间的自动登录的验证

        return ticket.getTicket();
    }

3.登录功能实现:

基本与注册功能相似。

public Map<String ,Object> login(String username, String password){
        Map<String ,Object> map=new HashMap<>();
        if(StringUtils.isBlank(username)){
            map.put("msgname","用户名不能为空");
            return map;
        }
        if(StringUtils.isBlank(username)){
            map.put("msgpassword","密码不能为空");
            return map;

        }
        User user=userDAO.selectByName(username);
        if(user==null){
            map.put("msgname","用户名不存在");
            return map;

        }
        if(!ToutiaoUtil.MD5(password+user.getSalt()).equals(user.getPassword())){//验证密码
            map.put("msgpassword","密码错误");
            return map;
        }


        //登录验证完,自动登录,即下发ticket
        String ticket=addLoginTicket(user.getId());
        map.put("ticket",ticket);


        return map;
    }

4.状态判断:

注册登录的逻辑判断很好实现,但是我们如何在打开或者刷新一个页面的时候,让浏览器记住我们是否登录,或者我们是谁?这些问题呢。因此我们需要两部分来解决这些问题:①HostHandler类来记录当前登陆的用户是谁。②Interception(拦截器),在每个网页加载之前,读取cookie,来判断我们是谁。③LoginController中将ticket写入cookie。

①HostHandle实现类很简单,需要注意的是需要把它的成员变量user(即“我是谁”)设置成ThreadLocal的来应对多线程问题;把其方法设置成static的,方便调用。

package com.nowcoder.model;


import org.springframework.stereotype.Component;

@Component
public class HostHolder {
    private static ThreadLocal<User> users=new ThreadLocal<>();//解决“我是谁”的问题,ThreadLocal解决多线程问题

    public static User getUser(){//定义成static方便调用
        return users.get();
    }

    public static void setUser(User user){
        users.set(user);
    }

    public static void clear(){
        users.remove();
    }

}

②Interception(拦截器)

PassportInterception可以实现在网页加载前后进行一系列的判断和操作。需要实现HandlerInterception接口,这个接口有三个方法。

//HandlerInterceptor.java

public interface HandlerInterceptor {
    boolean preHandle(HttpServletRequest var1, HttpServletResponse var2, Object var3) throws Exception;
//该方法将在请求处理之前进行调用,只有该方法返回true,才会继续执行后续的Interceptor和Controller,当返回值为true 时就会继续调用下一个Interceptor的preHandle 方法,如果已经是最后一个Interceptor的时候就会是调用当前请求的Controller方法; 

    void postHandle(HttpServletRequest var1, HttpServletResponse var2, Object var3, ModelAndView var4) throws Exception;
//该方法将在请求处理之后,DispatcherServlet进行视图返回渲染之前进行调用,可以在这个方法中对Controller 处理之后的ModelAndView 对象进行操作。 

    void afterCompletion(HttpServletRequest var1, HttpServletResponse var2, Object var3, Exception var4) throws Exception;
//该方法也是需要当前对应的Interceptor的preHandle方法的返回值为true时才会执行,该方法将在整个请求结束之后,也就是在DispatcherServlet 渲染了对应的视图之后执行。用于进行资源清理。
}
package com.nowcoder.interceptor;

//PassportInterceptor.java

import com.nowcoder.dao.LoginTicketDAO;
import com.nowcoder.dao.UserDAO;
import com.nowcoder.model.HostHolder;
import com.nowcoder.model.LoginTicket;
import com.nowcoder.model.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Date;

@Component
public class PassportInterceptor implements HandlerInterceptor {

    @Autowired
    private LoginTicketDAO loginTicketDAO;

    @Autowired
    private UserDAO userDAO;

    @Autowired
    private HostHolder hostHolder;

    @Override
    public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
        String ticket=null;
        if(httpServletRequest.getCookies()!=null){//读取httpServletRequest的cookie
            for(Cookie cookie: httpServletRequest.getCookies()){//遍历所有cookie
                if( cookie.getName().equals("ticket")){//如果有cookie等于ticket
                    ticket=cookie.getValue();//取该cookie的value
                    break;
                }
            }
        }
        if(ticket!=null){
            LoginTicket loginTicket=loginTicketDAO.selectByTicket(ticket);//在数据库中查找该ticket
            if(loginTicket==null||loginTicket.getExpired().before(new Date())||loginTicket.getStatus()!=0){//判断loginTicket是否为空,是否过期,是否失效
                return true;
                //直接进入posthandle,继续判断是否有hosthandle
            }

            User user=userDAO.selectById(loginTicket.getUserId());//根据Loginticket中的userID属性查找对应的user
            hostHolder.setUser(user);//将该user存入hostHolder中
        }
        return true;
        //直接进入posthandle,继续判断是否有hosthandle
    }

    @Override
    public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
        if(modelAndView!=null&&hostHolder.getUser()!=null){//判断hostHolder是否为空,即判断是否有当前登录用户
            //modelAndView可以实现后端与前端的交互,addObject的元素可以直接被前端模板调用
            //html文件中可以直接使用这个$user
            modelAndView.addObject("user",hostHolder.getUser());
        }
    }

    @Override
    public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
        hostHolder.clear();//网页关闭,清理hostHolder占用的内存
    }
}

interception写到这里,它的功能已经实现但是还需要把它和本网站关联,使用configuration.ToutiaoWebConfiguration实现

package com.nowcoder.configuration;

import com.nowcoder.interceptor.LoginRequiredInterceptor;
import com.nowcoder.interceptor.PassportInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

@Component//用@Component对那些比较中立的类进行凝视,让spring可以自动装载这个类
public class ToutiaoWebConfiguration extends WebMvcConfigurerAdapter {
    @Autowired
    PassportInterceptor passportInterceptor;

    @Autowired
    LoginRequiredInterceptor loginRequiredInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(passportInterceptor);//将passportInterceptor注册,才能回调
        registry.addInterceptor(loginRequiredInterceptor).addPathPatterns("/setting/");//不是全局的,只是setting页面
        super.addInterceptors(registry);
    }
}

③LoginController

将ticket写入cookie,以注册为例,登录同理。

    @RequestMapping(path = {"/reg/"}, method = {RequestMethod.GET, RequestMethod.POST})
    //设置网页路径为/reg/
    @ResponseBody
    //@responseBody注解的作用是将controller的方法返回的对象通过适当的转换器转换为指定的格式之后,
    //写入到response对象的body区,通常用来返回JSON数据或者是XML
    public String reg(Model model , @RequestParam("username") String username,
                      @RequestParam("password") String password,
                      @RequestParam(value = "rember",defaultValue = "0") int remeberme,
                      HttpServletResponse response) {
//此处没有写前端,所以用户名,密码需要在url中填写
        try {
            Map<String,Object> map=userService.Register(username,password);
            //调用注册功能,得到注册返回的map
            if (map.containsKey("ticket")){
                //将ticket写入cookie
                Cookie cookie=new Cookie("ticket",map.get("ticket").toString());
                cookie.setPath("/");//设置cookie为全站有效的

                //设置remember me,即延长cookie的保存时间,,否则默认浏览器关闭删除cookie
                if(remeberme>0){
                    cookie.setMaxAge(3600*24*5);
                }

                response.addCookie(cookie);//注意设置完cookie的所有属性才能add到response,否则不会提交


                return ToutiaoUtil.getJSONString(0,"注册成功");//返回json格式的信息字符串
            }
            else{
                return ToutiaoUtil.getJSONString(1,map);//返回json格式的信息字符串
            }
        }catch (Exception e){
            logger.error("注册异常"+e.getMessage());
            return ToutiaoUtil.getJSONString(1,"注册异常");//返回json格式的信息字符串
        }
    }

猜你喜欢

转载自my.oschina.net/u/3786691/blog/1790742

相关文章