一、需求设计
本文设计的的应用模式如下图所示(注册过程大同小异,此处略过):
当以游客身份访问时:
当登陆过后,再次在当前浏览器中访问时:
⚠️注意:本文的设定是不关闭当前浏览器,再次访问该网站则自动登陆,若关闭了当前浏览器,重新开启新的浏览器进行访问,那么需要重新登陆(此处一旦登陆,某段时间内可记住密码)。当然,很多网站关闭浏览器退出后,再开启新浏览器进行访问仍能保持登陆状态,这在功能上也就是使用Cookie和Session的区别,后文会提到!
本文介绍第一个功能:登陆功能!
二、具体实现
(1)项目框架
网站设计的一个经典模式就是MVC模式:1).M,Model,表示模型层,用很多属性来描述一个类,这个类就是模型,比如,一个简单的用户模型就是用户名和密码,于是我们可以用一个UserBean类来表述这个用户模型,同时还包括持久层的对数据库的操作;2).V,views,表示视图层,即给用户展示的界面,在JavaEE中,jsp文件完成V的作用;3)C,controller,控制器,控制页面处理和跳转的主逻辑。
本文使用的JavaEE是jsp+servlet+javabean,并没有使用更高级的框架。为了充分结合MVC的设计模式,我们将Intellij idea中的工程目录建成如下图结构(此处参考基于Servlet+JSP+JavaBean开发模式的用户登录注册,该文中对给基于MVC模式的jsp+servlet+javabean网站设计介绍得非常详细,也是本文一个主要参考来源):
从以上结构,我们可以进行一下分类(这个分类并不一定准确,但一定程度上可以帮助我们理解程序):
总的来说,就是domain建立User的用户模型,同时Dao层实现对用户模型的数据库操作;controller实现后台逻辑,比如登陆成功跳转首页、登陆失败跳转失败页面,而其他如service等是登陆等逻辑过程的具体实现,是支持controller实现的必要部分;最后jsp文件作为展示页面。具体在Intellij idea中创建一个JavaEE项目的方法如下:
1>.如果你是学生,那么使用你的学生邮箱很容易就能在Jetbrains上注册一个学生账号,那么无论是Intellij idea还是PyCharm这样的神器,你都可以免费使用专业版了!打开Intellij idea后,创建一个新的工程,然后
选择“Java Enterprise”中的“Web Application(3.1)”,然后选择工程的路径和工程名字,我把这个工程叫做“MyLab”。
新建后的工程结构如下图所示,那么现在就是要按照前文中的项目结构进行修改。
主要修改的地方是1)src文件夹中按照上文中的目录结构进行修改(其实并非每个项目都要这么麻烦的建立各个package,只是为了更好地凸显出MVC模式的层次结构以及更好的管理项目,所以才这么干。实际情况应该具体情况具体分析!)2)在web文件夹下的WEB-INFO文件夹下新建“classes”和“lib”文件夹,这是java编译后文件的目录以及程序中使用的包的目录,当然,在未设置之前,这两个文件夹是啥功能也没有的,于是,还要做以下设置:打开“File”,选择“Project Structure”,选择“modules”,先选择“Paths”项,选中“Use modules compile output path”,然后将“Output path”和“Test output path”的路径都选择刚新建的“classes”目录,这样一来,编译后的.class文件都将生成在“classes”文件夹中
这当然没完,还需要设置程序使用包的路径“lib”,如下图选择好JAR包路径!
实际上,上述两部添加“classes”和“lib”并不一定是必须的,但是,为了接下来的方便,我们按照这个框架来写,是一个非常明智的选择!
接下来就是要配置Tomcat服务器,首先新建一个Tomcat本地服务器,点击“Run”选项,选择“Edit Configurations”,按照下图所示,新建一个服务器。
然后选择上图的“Configure..”,配置你本地的Tomcat路径,即可(这个服务器名字自取)!
最后,我们要为这个项目配置一个访问路径,仍在“ Configurations”中,选择“Deployment”,选中项目的“war exploded”(这就是整个项目的部署文件),更改“Application context”
到此为止,网站初期的工作就全部完成了!接下来可以开始编写咱们的登陆网站!
(2)功能实现
接下来,本文将分功能来介绍具体实现。
1>.登陆功能(从登陆,熟悉JavaEE的MVC)
该功能主要流程是:游客在界面上写入用户名和密码,点击登录按钮,后台读取数据库进行用户登录验证,验证成功则进入到登录界面。首先我们可以先看看界面是什么样子的。
登陆前:
图1 登陆前界面
登陆前的index.jsp视图:
<%@ page import="domain.UserBean,web_listener.PeopleListner" %><%--
Created by IntelliJ IDEA.
User: lps
Date: 2017/3/3
Time: 下午3:53
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>首页</title>
</head>
<body>
<h1 align="center">3033工作室</h1>
<hr>
当前在线人数:<%= PeopleListner.getActiveSessions()%>
<br>
<%
//通过session判断当前是否已经是登陆状态
UserBean user;
user = (UserBean) request.getSession().getAttribute("user");
if (user == null)
{
%>
<a href="login.jsp">请登陆</a>
<%
}
else
{
%>
欢迎${user.username}
<a href="${pageContext.request.contextPath }/LogoutServlet" >注销</a>
<%
}
%>
<br>
<a href="usercansee.jsp">游客请先登录再查看</a>
</body>
</html>
登陆界面(这是在bootstrap上下载的源码):
图2 登陆界面
登陆界面login.sp视图:
<%--
Created by IntelliJ IDEA.
User: lps
Date: 2017/3/1
Time: 下午10:10
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Bootstrap Login Form Template</title>
<!-- CSS -->
<link rel="stylesheet" href="http://fonts.googleapis.com/css?family=Roboto:400,100,300,500">
<link rel="stylesheet" href="assets/bootstrap/css/bootstrap.min.css">
<link rel="stylesheet" href="assets/font-awesome/css/font-awesome.min.css">
<link rel="stylesheet" href="assets/css/form-elements.css">
<link rel="stylesheet" href="assets/css/style.css">
<!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
<script src="https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js"></script>
<![endif]-->
<!-- Favicon and touch icons -->
<link rel="shortcut icon" href="assets/ico/favicon.png">
<link rel="apple-touch-icon-precomposed" sizes="144x144" href="assets/ico/apple-touch-icon-144-precomposed.png">
<link rel="apple-touch-icon-precomposed" sizes="114x114" href="assets/ico/apple-touch-icon-114-precomposed.png">
<link rel="apple-touch-icon-precomposed" sizes="72x72" href="assets/ico/apple-touch-icon-72-precomposed.png">
<link rel="apple-touch-icon-precomposed" href="assets/ico/apple-touch-icon-57-precomposed.png">
</head>
<body>
<% //通过cookie找到最近时间内登陆的用户,实现保存密码
String username = "";
String password = "";
//获取当前站点的所有Cookie
Cookie[] cookies = request.getCookies();
for (int i = 0; i < cookies.length; i++) {//对cookies中的数据进行遍历,找到用户名、密码的数据
if ("username".equals(cookies[i].getName())) {
username = cookies[i].getValue();
} else if ("password".equals(cookies[i].getName())) {
password = cookies[i].getValue();
}
}
%>
<!-- Top content -->
<div class="top-content">
<div class="inner-bg">
<div class="container">
<div class="row">
<div class="col-sm-8 col-sm-offset-2 text">
<h1><strong>Bootstrap</strong> Login Form</h1>
<div class="description">
<p>
This is a free responsive login form made with Bootstrap.
Download it on <a href="http://azmind.com"><strong>AZMIND</strong></a>, customize and use it as you like!
</p>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-6 col-sm-offset-3 form-box">
<div class="form-top">
<div class="form-top-left">
<h3>Login to our site</h3>
<p>Enter your username and password to log on:</p>
</div>
<div class="form-top-right">
<i class="fa fa-key"></i>
</div>
</div>
<div class="form-bottom">
<form role="form" action="mylogin" method="post" class="login-form">
<div class="form-group">
<label class="sr-only" for="form-username">Username</label>
<input type="text" name="username" placeholder="Username..." class="form-username form-control" id="form-username" value=<%=username%>>
</div>
<div class="form-group">
<label class="sr-only" for="form-password">Password</label>
<input type="password" name="password" placeholder="Password..." class="form-password form-control" id="form-password" value=<%=password%>>
</div>
<button type="submit" class="btn">Sign in!</button>
</form>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-6 col-sm-offset-3 social-login">
<h3>...or login with:</h3>
<div class="social-login-buttons">
<a class="btn btn-link-1 btn-link-1-facebook" href="#">
<i class="fa fa-facebook"></i> Facebook
</a>
<a class="btn btn-link-1 btn-link-1-twitter" href="#">
<i class="fa fa-twitter"></i> Twitter
</a>
<a class="btn btn-link-1 btn-link-1-google-plus" href="#">
<i class="fa fa-google-plus"></i> Google Plus
</a>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Javascript -->
<script src="assets/js/jquery-1.11.1.min.js"></script>
<script src="assets/bootstrap/js/bootstrap.min.js"></script>
<script src="assets/js/jquery.backstretch.min.js"></script>
<script src="assets/js/scripts.js"></script>
<!--[if lt IE 10]>
<script src="assets/js/placeholder.js"></script>
<![endif]-->
</body>
</html>
该部分还有一些css,js的引用这里不作阐述,文后会给出源码。本文不对前端过多介绍。
登陆后:
图3 登陆后界面
该部分与登陆前的index.jsp一致,只是利用了session对当前用户是否处于登陆状态进行了判断,最后显示不同的信息(游客显示“请登陆”按钮,用户显示“欢迎***”,隐藏“请登陆”按钮)。
接下来,再来看看登陆功能是怎么实现的:
首先,我们在图1登陆前界面中点击请登陆按钮,界面即跳转到登陆界面图2。现在要实现的登陆功能即输入用户名和密码,后台在数据库中查找匹配,匹配成功则跳转到登陆后的页面图3,否则提示密码错误,并且一定时间后跳转回登陆界面图2。
我们尽量遵循MVC模式来讲解配置过程:
(1)M:我们来配置用户的模型,首先在mysql数据库中查看用户的字段信息:
根据该用户表的字段信息,我们在domain这个package中定义UserBean.java来表示用户的模型。
package domain;
/**
* Created by lps on 2017/3/2.
*/
/**
* 用户类
* Created by lps on 2017/3/1.
*/
public class UserBean {
private String username;
private String password;
public UserBean()
{
}
public UserBean(String username, String password) {
this.username = username;
this.password = password;
}
public String getUsername() {
return this.username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return this.password;
}
public void setPassword(String password) {
this.password = password;
}
}
那么,对于持久层,我们应该围绕用户模型对数据库进行操作,实现如下:
首先,需要配置JDBC的JAR包,由于上文已经将lib配置成JAR包访问路径,那么只需要将mysql-connector-java-5.1.40-bin.jar包复制到lib下即可;
配置完成后,我们在utils的package中定义一个数据库工具类,得到一个可用的数据库连接对象connection:
package utils;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
/**
* Created by lps on 2017/3/3.
*/
public class DataBaseUtil {
public DataBaseUtil() {
}
/**
* 开启数据库,得到数据库对象
* @return 数据库对象
*/
public static Connection getConnection() {
Connection connection = null;
try {
Class.forName("com.mysql.jdbc.Driver");
String sqle = "jdbc:mysql://localhost:3306/user_manage";
connection = DriverManager.getConnection(sqle, "root", "683683");
return connection;
} catch (ClassNotFoundException var2) {
var2.printStackTrace();
} catch (SQLException var3) {
var3.printStackTrace();
}
return connection;
}
/**
* 关闭数据库
* @param connection 数据库对象
*/
public static void closeConnection(Connection connection) {
if(connection != null) {
try {
connection.close();
} catch (SQLException var2) {
var2.printStackTrace();
}
}
}
接着,我们在dao的package中定义dao层的接口类型(我们需要验证用户信息,即查看某用户名和某密码对在数据库中是否存在)
package dao;
import domain.UserBean;
/**
* Created by lps on 2017/3/2.
*/
public interface UserDao {
//用户登陆验证
public boolean getUser(UserBean user);
//某用户名是否已存在
public boolean isUserExist(String username);
//将用户存入数据库
public void saveUser(UserBean user);
}
然后实现
在dao_impl的package中实现UserDao接口
package dao_impl;
import dao.UserDao;
import domain.UserBean;
import utils.DataBaseUtil;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* Created by lps on 2017/3/2.
*/
public class UserDaoImpl implements UserDao{
/**
* 登陆验证
* @param user
* @return true 验证成功 false 验证失败
*/
public boolean getUser(UserBean user) {
String username = user.getUsername();
String password = user.getPassword();
Connection conn = DataBaseUtil.getConnection();
String sql = "select password from user_info WHERE username=?";
try{
PreparedStatement ps = conn.prepareStatement(sql);
ps.setString(1, username);
ResultSet resultSet = ps.executeQuery();
//没有该用户,返回false
if (!resultSet.next()) {
return false;
}
//开始验证用户密码
String real_password = resultSet.getString("password");
//密码验证失败
if (!password.equals(real_password))
{
return false;
}
}catch(SQLException e) {
System.out.println(e.toString());
e.printStackTrace();
}finally {
DataBaseUtil.closeConnection(conn);
}
return true;
}
/**
* 用户名是否已存在
* @param username
* @return
*/
public boolean isUserExist(String username)
{
//连接数据库
Connection connection = DataBaseUtil.getConnection();
String sql = "select * from user_info where username=?";
//访问数据库
try
{
PreparedStatement ps = connection.prepareStatement(sql);
ps.setString(1,username);
//查询是否有该用户
ResultSet rs = ps.executeQuery();
if(!rs.next())
{
return false;
}
}catch(SQLException e) {//捕捉异常
System.out.println(e.toString());
e.printStackTrace();
}finally {//关闭数据库
DataBaseUtil.closeConnection(connection);
}
return true;
}
/**
* 新注册用户,存入数据库
* @param user
*/
public void saveUser(UserBean user)
{
//得到数据库
Connection connection = DataBaseUtil.getConnection();
String sql = "insert into user_info values(?,?)";
try{
PreparedStatement ps = connection.prepareStatement(sql);
ps.setString(1,user.getUsername());
ps.setString(2,user.getPassword());
//更新数据库
ps.executeUpdate();
}catch (SQLException e){
e.printStackTrace();
}finally {
DataBaseUtil.closeConnection(connection);
}
}
}
到此,用户模型以及dao层对用户模型进行操作的实现就结束了!这也可以看作是M的实现!
(2)V部分实际上就是上文中提到的jsp页面;
(3)C:主逻辑是在此实现
首先,拿到前端的数据后,我们用一个servlet服务来统一管理,在web_controller中定义一个登陆的控制器LoginServlet
package web_controller;
import domain.UserBean;
import service.BusinessService;
import service_impl.BusinessServiceImpl;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* Created by lps on 2017/3/3.
*/
public class LoginServlet extends HttpServlet{
public void doPost(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException
{
String username = request.getParameter("username");
String password = request.getParameter("password");
UserBean user = new UserBean();
user.setUsername(username);
user.setPassword(password);
BusinessService service = new BusinessServiceImpl();
if(service.login(user)) {
//存入cookie:name,password
Cookie nameCookie = new Cookie("username", username);
//设置Cookie的有效期:3天
nameCookie.setMaxAge(60*60*24*3);
Cookie pwdCookie = new Cookie("password", password);
pwdCookie.setMaxAge(60*60*24*3);
response.addCookie(nameCookie);
response.addCookie(pwdCookie);
//设置页面显示信息
request.setAttribute("info", "登陆成功!欢迎您的访问!");
//用户信息存入session,供当前访问使用
request.getSession().setAttribute("user",user);
//请求转发(带信息,而重定向是丢失信息的)
request.getRequestDispatcher("index.jsp").forward(request, response);
} else {//登陆失败
request.setAttribute("info", "登陆失败!请确认后再尝试!");
request.getRequestDispatcher("message.jsp").forward(request, response);
}
}
}
为了方便管理,我们使用一个service服务接口来对前端数据、底层数据库访问以及逻辑操作进行封装,
在service的package中定义服务接口BusinessService
package service;
import domain.UserBean;
/**
* Created by lps on 2017/3/3.
*/
public interface BusinessService {
public boolean login(UserBean user);
}
在service_ipml的package中实现BusinessService接口
package service_impl;
import dao.UserDao;
import dao_impl.UserDaoImpl;
import domain.UserBean;
import service.BusinessService;
/**
* Created by lps on 2017/3/3.
*/
public class BusinessServiceImpl implements BusinessService{
private UserDao dao = new UserDaoImpl();
@Override
public boolean login(UserBean user) {
return dao.getUser(user);
}
}
以上就是所有登陆相关的Java代码。
为了使得Servlet生效(实际上就是点击按钮后,能运行servlet中的doPost方法),我们还需要配置web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<servlet>
<servlet-name>LoginServlet</servlet-name>
<servlet-class>web_controller.LoginServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>LoginServlet</servlet-name>
<url-pattern>/mylogin</url-pattern>
</servlet-mapping>
</web-app>
配置方法:如上所示,<servlet>和<servlet-mapping>中的<servlet-name>必须对应,<servlet-class>即servlet的java路径,记得带上包名,<url-pattern>对应的是前端的点击事件映射,也就是前端的点击事件就是通过这个映射来找到对应的servlet(表单中的action项)。
至此,登陆部分的实现已经完成了!
我们来结合程序回顾一下整个登陆过程的流程:
输入用户名、密码,点击登陆按钮 ----> 表单事件action:mylogin找到servlet配置的<url-pattern>:/mylogin, 后台LoginServlet的doPost方法被触发 ----> 调用BusinessService方法进行用户验证 ----> dao层操作数据库进行用户验证 ----> LoginServlet对结果进行处理(如页面跳转,session保存等)。
下文将介绍----注销以及注销过程中用到的一些JavaEE概念以及功能。