JavaEE 使用Servlet实现简单登录页面


平常我们浏览网页时,有很多情况需要先登录,今天我们就来编写一个简单的登录页面。

我们使用Tomcat服务器,用eclipse进行开发。整个登录流程很简单:
- 用户在登录页面(login.jsp)输入用户名和密码,点击登录;
- 服务器收到用户请求后,搜索数据库,如果用户名和密码匹配,则跳转到欢迎页面(welcome.jsp);否则在登录页面显示相应的错误信息。

效果

我们先来看下最终的效果:

1. 数据库:在数据库中创建一张user表,包括用户ID、用户名、登录密码、在线状态和上次登录时间这5个属性,如下图:

Javaee下登录 javaee登录界面代码_java ee


2. 登录页面:一个简单的form表单,包括用户名和密码,如下图:

Javaee下登录 javaee登录界面代码_java ee_02


如果登录时发生错误,则登录页面会显示错误信息,下图显示了密码错误的情况:

Javaee下登录 javaee登录界面代码_servlet_03


3. 欢迎页面:显示用户名和上次登录时间等,如下图:

Javaee下登录 javaee登录界面代码_数据库_04


如果在登录时,使用的用户名已经在线,则会提示如下信息:

Javaee下登录 javaee登录界面代码_数据库_05

思路

我们采用MVC架构,用JavaBean作为模型,jsp页面作为视图,Servlet作为控制器。用户和jsp页面交互,Servlet处理来自用户的请求和向用户发送响应,而真正的数据库操作则交由JavaBean来完成。

模型:JavaBean

由于要操作数据库,我们就先定义一个DBManager类来管理数据库连接(实际中建议使用DBCP):

// DBManager.java

public class DBManager {
    private Properties prop;
    private static DBManager instance;

    public static DBManager getInstance() {
        if(instance==null) {
            synchronized(DBManager.class) {
                if(instance==null)
                    instance=new DBManager();
            }
        }
        return instance;
    }

    private DBManager() {
        prop=new Properties();
        prop.put("driver", "com.mysql.jdbc.Driver");
        prop.put("url", "jdbc:mysql://localhost:3306/XXX");
        prop.put("user", "XXX");
        prop.put("password", "******");
        prop.put("useUnicode", "true");
        prop.put("characterEncoding", "UTF-8");
        prop.put("useSSL", "true");

        try {
            Class.forName(prop.getProperty("driver"));
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    public Connection connect() {
        Connection conn=null;
        String url=prop.getProperty("url");
        try {
            conn=DriverManager.getConnection(url, prop);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return conn;
    }

    public void disconnect(Connection conn) {
        try {
            if(conn!=null)
                conn.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

}

DBManager类负责连接数据库,构造器方法中对数据库进行了相应配置,数据库我们使用MySQL。connect方法返回一个数据库连接,disconnect方法关闭一个数据库连接。

接下来,我们还需要提供一个LoginHelper类来帮助我们对数据库进行操作:

// LoginHelper.java

public class LoginHelper {
    public enum LoginState {
        LOG_IN_SUCCESS,     // 登录成功
        PASSWORD_ERROR,     // 密码错误
        USER_NOT_EXIST,     // 用户不存在
        USER_IS_EMPTY,      // 用户名为空
        PASSWORD_IS_EMPTY,  // 密码为空
    }
    private static SimpleDateFormat sdf=
            new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    public static LoginState login(HttpSession session, 
            String name, String password) throws SQLException {
        if(name==null || name.equals(""))
            return LoginState.USER_IS_EMPTY;
        if(password==null || password.equals(""))
            return LoginState.PASSWORD_IS_EMPTY;

        DBManager manager=DBManager.getInstance();
        Connection conn=manager.connect();

        try{
            Statement state=conn.createStatement();
            ResultSet result=state.executeQuery(
                    "select password, online, date "+
                            "from user where name='"+name+"';");

            if(result.next()) {
                if(result.getString(1).equals(password)) {
                    session.setAttribute("name", name);
                    session.setAttribute("online", result.getBoolean(2));
                    session.setAttribute("date", result.getString(3));
                    state.execute("update user set online=true, date='"+
                            sdf.format(new Date())+"' where name='"+name+"';");
                    return LoginState.LOG_IN_SUCCESS;
                }
                else
                    return LoginState.PASSWORD_ERROR;
            }
            else
                return LoginState.USER_NOT_EXIST;
        } finally {
            manager.disconnect(conn);
        }
    }

}

LoginHelper类的静态方法login将负责检验用户名和密码。如果用户名和密码匹配,则在session范围内设置nameonlinedate三个属性,这三个属性将会显示在欢迎页面,然后返回LOG_IN_SUCCESS标识。如果发生了错误,例如密码错误或用户不存在等,则返回相应的错误标识。

控制器:Servlet

在项目中新建一个Servlet,起名为LoginServlet,让它重写父类的service方法,并使用@WebServlet注解对它进行配置:

// LoginServlet.java

@WebServlet(name="LoginServlet", urlPatterns="/login")
public class LoginServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        request.setCharacterEncoding("GBK");
        String error=null;
        RequestDispatcher dispatcher;

        HttpSession session=request.getSession(true);
        String user=request.getParameter("user");
        String password=request.getParameter("password");
        try {
            switch(LoginHelper.login(session, user, password)) {
            case LOG_IN_SUCCESS:
                dispatcher=request.getRequestDispatcher("/welcome.jsp");
                dispatcher.forward(request, response);
                break;
            case PASSWORD_ERROR:
                error="密码错误!";
                break;
            case USER_NOT_EXIST:
                error="用户不存在!";
                break;
            case USER_IS_EMPTY:
                error="用户名不能为空!";
                break;
            case PASSWORD_IS_EMPTY:
                error="密码不能为空!";
                break;
            default: break;
            }
        } catch (SQLException e) {
            error=e.getMessage();
        } finally {
            if(error!=null) {
                request.setAttribute("error", error);
                dispatcher=request.getRequestDispatcher("/login.jsp");
                dispatcher.forward(request, response);
            }
        }
    }

}

可以看到,service方法中主要就是对LoginHelper.login方法的返回值进行处理。如果是LOG_IN_SUCCESS的情况,则将用户请求转发到welcome.jsp页面;其它情况则将用户请求转发到login.jsp页面,并显示错误信息。

写到这,我们的工作已经差不多完成了,接来下只需要提供两个简单的jsp页面即可。

视图:jsp页面

就像我们上面所说的那样,用户是和jsp页面进行交互的。由于这只是一个简单的登录页面,我们就将jsp页面写简单点。首先是登录页面:

<!-- login.jsp -->

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<%@ page language="java" contentType="text/html; charset=GBK"
    pageEncoding="UTF-8"%>
<meta http-equiv="Content-Type" content="text/html; charset=GBK">
<title>登录页</title>
</head>
<body>
    <span style="color:red;font-weight:bold">
    <%
        if(request.getAttribute("error")!=null)
            out.println(request.getAttribute("error")+"<br>");
    %>
    </span>
    <form id="login" method="post" action="login">
        用户名:<input type="text" name="user"><br>
        密  码:<input type="text" name="password"><br>
        <input type="submit" value="登录">
    </form>
</body>
</html>

login.jsp主要分为两部分,其一是判断是否有错误信息(由LoginServlet转发而来),其二是一个简单的form表单。用户点击登录后,用户请求将会发送到/login,即LoginServlet(由@WebServlet注解中的urlPatterns设置),LoginServlet作为控制器,将调用模型(LoginHelper)对数据库进行操作,再根据结果来显示相应的试图(login.jsp或welcome.jsp)。

下面是欢迎页面:

<!-- welcome.jsp -->

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<%@ page language="java" contentType="text/html; charset=GBK"
    pageEncoding="UTF-8"%>
<meta http-equiv="Content-Type" content="text/html; charset=GBK">
<title>欢迎页</title>
</head>
<body>
    <%
        String name=(String) session.getAttribute("name");
        boolean online=(Boolean) session.getAttribute("online");
        String date=(String) session.getAttribute("date");

        out.print("亲爱的"+name+",欢迎您!");

        if(online)
            out.println("您的账号于"+date+"在别处登录,如果不是您所为,建议您修改密码。");
        else
            out.println("上次登录时间为"+date+"。");
    %>
</body>
</html>

welcome.jsp页面会根据online的值来决定显示什么内容。如果当前只有一人在线,则简单显示上次登录时间;否则提醒用户账号不安全。

小结

借助这个简单的登录页面,我们展示了如何使用MVC架构来编写web应用。当然,这只是一个“玩具式”的程序,在此基础上,我们还可以设置cookie记忆用户名、使用filter对用户请求进行过滤、注册listener对session的整个生命周期进行监听(注意到我们并没有提供用户下线的功能)等等。

此项目已托管到github上:https:///jzyhywxz/WebDemo.git