JavaWeb书城项目


一、前言

  • 本项目是我学习JavaWeb阶段,完成的实现了客户端、服务器、数据库三者交互的web项目。作为学习框架阶段对JavaWeb相关知识点的回顾,在此梳理。

  • 项目中运用到的知识点有:javase、servlet程序、jsp页面、JavaScript、jQuery框架、CSS样式、EL表达式、JSTL标签库、 Filter过滤器、jsp四大域对象、JDBC、MySql

  • 由于文章使用Markdown语法书写且文章较长,pc端观看效果最佳(其实只是有无目录区别)


二、项目主页及编译器界面展示

(1)项目主页
在这里插入图片描述

(2)编译器主页
在这里插入图片描述

本项目并未使用ssm或springboot等框架(以及maven项目管理),由手动实现web,service,dao三层架构

解释一下src目录和web目录下的包和子目录内容(从上到下):

  • src:
    filter-配置过滤器
    impl-dao层(持久层)接口及实现类
    pojo-bean目录
    service-service层(业务逻辑层)接口及实现类
    test-dao层和service层实现类的测试
    utils-JDBC和web层的工具类
    web-web层(表现层)的servlet程序
    jdbc.properties-数据库配置文件
  • web:
    pages-各jsp页面
    static-css样式,图片,jQuery框架源码
    WEB.INF-项目所依赖的jar包和xml配置文件
  • 右侧为本项目所用到的数据库book

三、项目实现

1.项目准备

(1)导入所需jar包和资源文件

commons-beanutils-1.8.0.jar
commons-dbutils-1.3.jar
commons-logging-1.1.1.jar
druid-1.1.9.jar
hamcrest-core-1.3.jar
junit-4.12.jar
hamcrest-core-1.3.jar
mysql-connector-java-8.0.27.jar
gson-2.2.4.jar
Java EE 6-Java EE 6
kaptcha-2.3.2.jar
taglibs-standard-impl-1.2.1.jar
taglibs-standard-spec-1.2.1.jar

(时间久远,可能有遗漏)
jdk用的是17.0.1,Tomcat版本为:Tomcat8.0.50

(2)创建所需数据库,数据表
在这里插入图片描述

  • t_book:图书信息表
  • t_order:订单信息表
  • t_order:订单项信息表
  • t_user:用户信息表

(3) 编写jdbc.properties配置文件

username=root
password=123456789(这里瞎写了一波)
url=jdbc:mysql://localhost:3306/book
driverClassName=com.mysql.cj.jdbc.Driver
initialSize=5
maxActive=10

2.项目工具类及配置文件编写

(1)编写工具类

  • JDBC工具类的编写
    此类主要负责:
    通过数据库连接池获取数据库连接
    关闭数据库连接(由于处理事务,顺便进行提交和回滚)
    用到了Apache组织下的dbutils工具类
package utils;

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.pool.DruidDataSourceFactory;

import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;

public class JdbcUtils {
    private static DruidDataSource dataSource;
    private static ThreadLocal<Connection> conns = new ThreadLocal<>();

    static {
        try {
            Properties properties = new Properties();
            InputStream inputStream = JdbcUtils.class.getClassLoader().getResourceAsStream("jdbc.properties");
            properties.load(inputStream);
            dataSource = (DruidDataSource) DruidDataSourceFactory.createDataSource(properties);

        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    /**
     * 获取数据库连接池中的连接
     *
     * @return 如果返回null, 说明获取连接失败<br />有值就是获取连接成功
     */
    public static Connection getConnection() {
        Connection conn = conns.get();
        if (conn == null) {
            try {
                conn = dataSource.getConnection(); //从数据库连接池中获取连接
                conns.set(conn); //保存到ThreadLocal对象中,供后面jdbc操作使用
                conn.setAutoCommit(false); //设置为手动管理事物
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        return conn;
    }

    /**
     * 提交事物,并关闭数据库连接
     */
    public static void commitAndClose() {
        Connection connection = conns.get();
        if (connection != null) { //若不为null,则说明之前使用过连接,操作数据库
            try {
                connection.commit(); //提交事物
            } catch (SQLException e) {
                e.printStackTrace();
            } finally {
                try {
                    connection.close(); //关闭连接
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
        /*Tomcat服务器使用了线程池技术,所以必须执行删除操作*/
        conns.remove();
    }

    /**
     * 回滚事物,并关闭数据库连接
     */
    public static void rollbackAndClose() {
        Connection connection = conns.get();
        if (connection != null) { //若不为null,则说明之前使用过连接,操作数据库
            try {
                connection.rollback(); //回滚事物
            } catch (SQLException e) {
                e.printStackTrace();
            } finally {
                try {
                    connection.close(); //关闭连接
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
        /*Tomcat服务器使用了线程池技术,所以必须执行删除操作*/
        conns.remove();
    }
}
  • web层工具类的编写(1)
    此页面主要负责:
    将服务器发送来的get请求转为post请求
    获取jsp页面请求的servlet类和方法(action)
    通过反射的方式调用指定servlet类中的指定方法(invoke)
package web;
/*所有Servlet程序的根父类,实现了接收客户端请求,并通过反射的方式调用子类中的方法*/
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Method;

public class BaseServlet extends HttpServlet {

    /**
     * 接收客户端发送来的POST请求,通过请求的hidden属性调用对应的方法
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        /*根据客户端隐藏属性,判断是哪个客户端发送来的请求*/
        String action = req.getParameter("action");
        try {
            /*通过反射调用客户端请求的方法,*/
            Method declaredMethod = this.getClass().getDeclaredMethod(action,HttpServletRequest.class,HttpServletResponse.class);
            declaredMethod.invoke(this,req,resp);
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(e); //将异常抛给Filter过滤器
        }
    }

    /**
     * 调用doPost方法,使两种请求干同一件事
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req,resp);
    }
}
  • web工具类的编写(2)
    此类主要负责:
    获取web层传来的 封装为Map对象的客户端请求,将其封装为Bean对象再传回web层
    获取web层传来的 获取数据类型为String的id号,将其转化为int型再传回web层
    (其实也可以写在web层的servlet程序中,但为了方便每个servlet程序调用,将这俩个方法封装为工具类)
package utils;

import org.apache.commons.beanutils.BeanUtils;
import java.util.Map;

public class WebUtils {
    /**
     * 获取请求的参数,封装为Bean对象
     * 传入map到对应的JavaBean属性中
     * @param value
     * @param bean  传入的参数为T类型,返回值的类型就为T,所以接收时,不需要类型转换
     */
    public static <T> T copyParameterToBean(Map value, T bean) {
        try {
            BeanUtils.populate(bean, value);
        } catch (Exception e) {
            /*e.printStackTrace();*/
        }
        return bean;
    }

    /**
     * 将获取的id改为int型
     * @param id
     * @return 转换成功返回id,不成功返回defaultValue
     */
    public static int parseInt(String id, int defaultValue) {
        try {
            return Integer.parseInt(id);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return defaultValue;
    }
}
  • DAO层工具类编写
    此类主要负责:
    调用JDBCUtils中的获取和关闭连接操作以及dbutils数据库连接池中的方发
    用其封装成基本的更改和查询操作
package impl;

import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.apache.commons.dbutils.handlers.ScalarHandler;
import utils.JdbcUtils;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;

public abstract class BaseDao {

    //使用DbUtils操作数据库
    private QueryRunner queryRunner = new QueryRunner();

    /**
     * update() 方法用来执行:Insert\Update\Delete语句
     *
     * @return 如果返回-1,说明执行失败<br/>返回其他表示影响的行数
     */
    public int update(String sql, Object... args) {
        Connection connection = JdbcUtils.getConnection();
        try {
            return queryRunner.update(connection, sql, args);
        } catch (SQLException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }

    /**
     * 查询返回一个javaBean的sql语句
     *
     * @param type 返回的对象类型
     * @param sql  执行的sql语句
     * @param args sql对应的参数值
     * @param <T>  返回的类型的泛型
     * @return
     */
    public <T> T queryForOne(Class<T> type, String sql, Object... args) {
        Connection con = JdbcUtils.getConnection();
        try {
            return queryRunner.query(con, sql, new BeanHandler<T>(type), args);
        } catch (SQLException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }

    /**
     * 查询返回多个javaBean的sql语句
     *
     * @param type 返回的对象类型
     * @param sql  执行的sql语句
     * @param args sql对应的参数值
     * @param <T>  返回的类型的泛型
     * @return
     */
    public <T> List<T> queryForList(Class<T> type, String sql, Object... args) {
        Connection con = JdbcUtils.getConnection();
        try {
            return queryRunner.query(con, sql, new BeanListHandler<T>(type), args);
        } catch (SQLException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }

    /**
     * 执行返回一行一列的sql语句
     * @param sql   执行的sql语句
     * @param args  sql对应的参数值
     * @return
     */
    public Object queryForSingleValue(String sql, Object... args){

        Connection conn = JdbcUtils.getConnection();

        try {
            return queryRunner.query(conn, sql, new ScalarHandler(), args);
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }
}

(2)编写配置文件

xml配置文件的编写
注:(这个步骤按顺序来说应该在配置servlet程序,filter过滤器,google验证码,异常跳转时编写,但由于工具类中需要用到配置的地址,所以我将它放在这里)
此步骤指名了各servlet程序的类名及路径
指名了filter过滤器所拦截的页面地址
导入了google验证码
设置了异常跳转路径

<?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_4_0.xsd"
         version="4.0">
    
    <servlet>
        <servlet-name>RegisterServlet</servlet-name>
        <servlet-class>web.older.RegisterServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>RegisterServlet</servlet-name>
        <url-pattern>/registerServlet</url-pattern>
    </servlet-mapping>
    <servlet>
        <servlet-name>LoginServlet</servlet-name>
        <servlet-class>web.older.LoginServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>LoginServlet</servlet-name>
        <url-pattern>/loginServlet</url-pattern>
    </servlet-mapping>

    <servlet>
        <servlet-name>UserServlet</servlet-name>
        <servlet-class>web.UserServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>UserServlet</servlet-name>
        <url-pattern>/userServlet</url-pattern>
    </servlet-mapping>

    <servlet>
        <servlet-name>BookServlet</servlet-name>
        <servlet-class>web.BookServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>BookServlet</servlet-name>
        <url-pattern>/manager/bookServlet</url-pattern>
    </servlet-mapping>

    <servlet>
        <servlet-name>ClientBookServlet</servlet-name>
        <servlet-class>web.ClientBookServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>ClientBookServlet</servlet-name>
        <url-pattern>/client/bookServlet</url-pattern>
    </servlet-mapping>

    <servlet>
        <servlet-name>KaptchaServlet</servlet-name>
        <servlet-class>com.google.code.kaptcha.servlet.KaptchaServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>KaptchaServlet</servlet-name>
        <url-pattern>/kaptcha.jpg</url-pattern>
    </servlet-mapping>

    <servlet>
        <servlet-name>CartServlet</servlet-name>
        <servlet-class>web.CartServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>CartServlet</servlet-name>
        <url-pattern>/cartServlet</url-pattern>
    </servlet-mapping>

    <servlet>
        <servlet-name>OrderServlet</servlet-name>
        <servlet-class>web.OrderServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>OrderServlet</servlet-name>
        <url-pattern>/orderServlet</url-pattern>
    </servlet-mapping>

    <!--配置过滤器-->
    <filter>
        <filter-name>ManagerFilter</filter-name>
        <filter-class>filter.ManagerFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>ManagerFilter</filter-name>
        <url-pattern>/pages/manager/*</url-pattern>
        <url-pattern>/manager/bookServlet</url-pattern>
    </filter-mapping>

    <filter>
        <filter-name>TransactionsFilter</filter-name>
        <filter-class>filter.TransactionFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>TransactionsFilter</filter-name>
        <!--/*表示包含当前工程下的所有项目-->
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <!--配置服务器出错后,自动跳转的页面-->
    <error-page>
        <!--错误类型-->
        <error-code>500</error-code>
        <!--要跳转去的页面路径-->
        <location>/pages/error/error500.jsp</location>
    </error-page>

    <error-page>
        <error-code>404</error-code>
        <location>/pages/error/error404.jsp</location>
    </error-page>

</web-app>

(3)编写jsp页面公共部分

  • 所有页面的head文件
    此文件主要负责:
    帮助每个页面获取动态的URL地址
    为每个页面添加静态包含base标签,css样式,jquery文件
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%--动态获取地址URL--%>
<%
    String basePath =
    request.getScheme()
    +"://"
    +request.getServerName()
    +":"
    +request.getServerPort()
    +request.getContextPath()
    +"/";

    pageContext.setAttribute("basePath",basePath);
%>
<base href="<%=basePath%>">

<link type="text/css" rel="stylesheet" href="static/css/style.css" >
<script type="text/javascript" src="static/script/jquery-1.7.2.js"></script>
  • 所有页面的foot文件
    此页面主要负责:
    引入底部标签
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<div id="bottom">
		<span>
			OdinPeng.BookStore &copy;2021
		</span>
</div>
  • 当登录成功后,各页面显示的信息栏
    通过session域获取客户端保存的登录名
    给各按钮指定跳转页面
<%@ page contentType="text/html;charset=UTF-8" language="java" %>

<div>
    <span>欢迎<span class="um_span">${sessionScope.user.username}</span>光临Google书城</span>
    <a href="../order/order.jsp">我的订单</a>
    <a href="userServlet?action=logout">注销</a>&nbsp;&nbsp;
    <a href="index.jsp">返回</a>
</div>
  • 进入管理员页面,显示的信息栏
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<div>
    <a href="manager/bookServlet?action=page">图书管理</a>
    <a href="pages/manager/order_manager.jsp">订单管理</a>
    <a href="index.jsp">返回商城</a>
</div>

3.各页面实现及动态展示

(1)登录页面功能实现

  • 页面展示
    首先使用错误测试,再使用正确密码测试
    在这里插入图片描述

  • 分析:
    登录功能实现的要素:用户名要存在,密码要正确
    所以用户在输入username和password 提交之后,我们得将请求发送给服务器。由服务器接收请求数据并访问本地数据库对应的用户信息表,对其信息进行查询。并将查询结果原路返回,直到servlet层进行判断。若是从数据库中查询到的用户信息与请求信息相符,则登录成功。若不相符,则保留用户名,删除密码,让用户重新输入。
    首先创建User的bean类
    这个阶段的实现需要JavaWeb三层架构,dao层用来封装基本的JDBC查询程序,service用来封装web层所需要的查询逻辑方法。web层则用来接收客户端发送的数据请求,并调用service层方法来进行判断用户信息是否吻合。
    我将由从下到上的顺序(dao->service->web)为大家介绍此功能的实现:

  • 定义User的Bean类
    注:(为了节省空间且bean目录中代码大同小异,文章中只展现User的bean文件)

package pojo;

public class User {
    private Integer id;
    private String username;
    private String password;
    private String email;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                ", email='" + email + '\'' +
                '}';
    }

    public User() {
    }

    public User(Integer id, String username, String password, String email) {
        this.id = id;
        this.username = username;
        this.password = password;
        this.email = email;
    }
}
  • dao层
    (1)定义userDao接口:
    规定需要定义的方法:queryUserByUsername
    (queryUserByUsername和savaUser方法注册界面需要使用)
package impl;
/*关于t_user表的CRUD*/
import pojo.User;

public interface UserDao {
    
    /**
     * 根据用户名查询用户信息
     * @param username 用户名
     * @return 如果返回null,说明没有这个用户。反之亦然
     */
    public User queryUserByUsername(String username);

    /**
     * 根据 用户名和密码查询用户信息
     * @param username
     * @param password
     * @return 如果返回null,说明用户名或密码错误,反之亦然
     */
    public User queryUserByUsernameAndPassword(String username,String password);

    /**
     * 保存用户信息
     * @param user
     * @return 返回-1表示操作失败,其他是sql语句影响的行数
     */
    public int saveUser(User user);
}

(2)UserDao接口实现类:
调用BaseDao定义的方法,实现UserDao接口

package impl;
/*DAO实现*/
/*持久层:实现与数据库交互*/
/*t_user表的CRUD的操作实现*/
import pojo.User;

public class UserDaoImpl extends BaseDao implements UserDao {
    /**
     * 根据用户名查询用户信息
     * @param username 用户名
     * @return 如果返回null,说明没有这个用户。反之亦然
     */
    @Override
    public User queryUserByUsername(String username) {
        String sql = "select `id`,`username`,`password`,`email` from t_user where username = ?";
        return queryForOne(User.class, sql, username);
    }

    /**
     * 根据 用户名和密码查询用户信息
     * @param username
     * @param password
     * @return 如果返回null,说明用户名或密码错误,反之亦然
     */
    @Override
    public User queryUserByUsernameAndPassword(String username, String password) {
        String sql = "select `id`,`username`,`password`,`email` from t_user where username = ? and password = ?";
        return queryForOne(User.class, sql, username,password);
    }

    /**
     * 保存用户信息
     * @param user
     * @return 返回-1表示操作失败,其他是sql语句影响的行数
     */
    @Override
    public int saveUser(User user) {
        String sql = "insert into t_user(`username`,`password`,`email`) values(?,?,?)";
        return update(sql, user.getUsername(),user.getPassword(),user.getEmail());
    }
}
  • service层
    (1)定义UserService接口
package service;

import pojo.User;

public interface UserService {

    /**
     * 注册用户
     * @param user
     */
    public void registerUser(User user);

    /**
     * 登录
     * @param user
     * @return
     */
    public User login(User user);

    /**
     * 检查用户名是否可用
     * @param username
     * @return 返回true:用户名已存在    返回false:用户名不存在
     */
    public boolean existsUsername(String username);
}

(2)UserService接口实现类

package service;
/*Service实现*/
/*业务层:处理业务逻辑,调用持久层*/
import impl.UserDao;
import impl.UserDaoImpl;
import pojo.User;

public class UserServiceImpl implements UserService{
    private UserDao userDao=new UserDaoImpl();

    /**
     * 注册用户
     * @param user
     */
    @Override
    public void registerUser(User user) {
        userDao.saveUser(user);
    }

    /**
     * 登录
     * @param user
     * @return 返回toString: 表中有username和password,登录成功    返回null:表中没有username和password,登录失败
     */
    @Override
    public User login(User user) {
        return userDao.queryUserByUsernameAndPassword(user.getUsername(), user.getPassword());
    }

    /**
     * 检查用户名是否可用
     * @param username
     * @return 返回true:用户名已存在    返回false:用户名不存在
     */
    @Override
    public boolean existsUsername(String username) {
        if (userDao.queryUserByUsername(username)==null){
            return false;
        }else{
            return true;
        }
    }
}
  • Web层
    实现UserServlet程序,接收客户端发送的数据请求,并调用service层方法来进行判断用户信息是否符合。并响应给客户端。
    注:
package web;
/*User模块,处理用户的登录与注册*/
import pojo.User;
import service.UserService;
import service.UserServiceImpl;
import utils.WebUtils;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;


import static com.google.code.kaptcha.Constants.KAPTCHA_SESSION_KEY;

public class UserServlet extends BaseServlet {

    private UserService userService = new UserServiceImpl();
    /**
     * 客户端登录功能
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    protected void login(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String username = req.getParameter("username");
        String password = req.getParameter("password");

        /*判断用户名对应的密码是否正确*/
        User login = userService.login(new User(null, username, password, null));
        if (login != null) {
            System.out.println("登录成功!");
            /*用户登录后,将user对象保存到Session域中*/
            req.getSession().setAttribute("user",login);
            req.getRequestDispatcher("/pages/user/login_success.jsp").forward(req, resp);
        } else {
            System.out.println("用户名或密码不正确,登录失败!");
            /*将错误信息保存到表request域中,保存用户名*/
            req.setAttribute("msg","用户名或密码不正确!");
            req.setAttribute("username",username);
            /*再次跳转到登录界面*/
            req.getRequestDispatcher("/pages/user/login.jsp").forward(req, resp);
        }
    }   
}
  • 登录JSP页面
<form action="userServlet" method="post">
<input type="hidden" name="action" value="login"/>

注:由上文的baseServlet类和xml配置文件得知:
这两行代码指定了此页面内容提交后将客户端发送的请求数据发送到指定的Servlet程序里,将hidden类型input标签的action属性值发送给BaseServlet,利用反射,调用action属性值指名的servlet程序方法

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Google会员登录页面</title>

    <%--导入静态包含base标签,css样式,jquery文件--%>
    <%@include file="/pages/common/head.jsp"%>

</head>
<body>
		<div id="login_header">
			<img class="logo_img" alt="" src="static/img/google.gif" height="88" width="365" style="margin-left: -120px">
		</div>
		
			<div class="login_banner">
			
				<div id="l_content">
					<span class="login_word">欢迎登录</span>
				</div>
				
				<div id="content">
					<div class="login_form">
						<div class="login_box">
							<div class="tit">
								<h1>Google会员</h1>
								<a href="pages/user/regist.jsp">立即注册</a>
							</div>
							<div class="msg_cont">
								<b></b>
								<span class="errorMsg">
									<%--<%=request.getAttribute("msg")==null?"请输入用户名和密码" :request.getAttribute("msg")%>--%>
									<%--EL表达式写法:--%>
									${empty requestScope.msg ? "请输入用户名和密码" : requestScope.msg}
								</span>
							</div>
							<div class="form">
								<form action="userServlet" method="post">
									<input type="hidden" name="action" value="login"/>
									<label>用户名称:</label>
									<input class="itxt" type="text" placeholder="请输入用户名" autocomplete="off" tabindex="1" name="username"
									<%--value="<%=request.getAttribute("username")==null?"":request.getAttribute("username")%>"--%>
									value="${requestScope.username}"
									/>
									<br />
									<br />
									<label>用户密码:</label>
									<input class="itxt" type="password" placeholder="请输入密码" autocomplete="off" tabindex="1" name="password" />
									<br />
									<br />
									<input type="submit" value="登录" id="sub_btn" />
								</form>
							</div>
							
						</div>
					</div>
				</div>
			</div>
		<%--导入底部标签--%>
		<%@include file="/pages/common/footer.jsp"%>
</body>
</html>

(2)注销功能实现

  • 页面展示:
    在这里插入图片描述

  • 分析:
    右上角显示【用户名】【注销】和【登录】【注册】取决于用户是否登录。jsp页面会获取session域中用户名信息。若为空,则未登录,反之亦然。点击注销按钮,则将session域中数据全部清空,右上角则显示【登录】【注册】。

  • web层:

/**
     * 注销,(销毁session对象)
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    protected void logout(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        /*立即销毁session对象*/
        req.getSession().invalidate();
        /*重定向到主页*/
        resp.sendRedirect(req.getContextPath());
    }
  • jsp页面:
<div id="header">
			<img class="logo_img" alt="" src="static/img/google.gif" height="88" width="365">
			<span class="wel_word">BOOK STORE</span>
			<div>
				<%--如果未登录,sessionScope.user内容为空,则显示登录,注册按钮--%>
				<c:if test="${ empty sessionScope.user}">
					<a href="pages/user/login.jsp">[登录]</a> |
					<a href="pages/user/regist.jsp">[注册]</a> &nbsp;&nbsp;
				</c:if>

				<c:if test="${ not empty sessionScope.user}">
					欢迎<span class="um_span">${sessionScope.user.username}</span>
					<%--<a href="../order/order.jsp">[订单]</a>--%>
					<a href="userServlet?action=logout">[注销]</a>
				</c:if>

				<a href="pages/cart/cart.jsp">[购物车]</a>
				<a href="pages/manager/manager.jsp">[后台管理]</a>
			</div>
	</div>

(3)注册页面功能实现

  • 页面展示:
    首先使用已存在用户名测试,再使用错误验证码测试,再使用不存在且正确验证码测试
    在这里插入图片描述

  • 分析:
    实现登录页面要素为:用户名,密码,邮箱格式符合要求
    用户名可用,密码和确认密码相同,验证码输入正确
    若用户输入的内容格式正确,验证码也正确,则会将用户的用户名发送给servlet程序,web层将用户名传入dao层。dao层程序访问本地数据库查询是否有与传入用户名相符的用户名,将结果原路返回传入servlet程序。判断与客户端的请求参数是否相符,若不相符则说明用户名可用。若相符则说明用户名已存在,刷新页面,删除用户原有数据。
    javaweb三层架构与登录页面相似,代码见上述

  • web层:

/**
     * 客户端注册功能
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    protected void regist(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        /*获取请求参数*/
        String username = req.getParameter("username");
        String password = req.getParameter("password");
        String email = req.getParameter("email");
        String code = req.getParameter("code"); //验证码

        /*获取Session域中验证码*/
        String sessionCode =(String) req.getSession().getAttribute(KAPTCHA_SESSION_KEY);
        /*立刻删除Session域中的验证码*/
        req.getSession().removeAttribute(KAPTCHA_SESSION_KEY);

        /*传入客户端请求的参数*/
        User user = WebUtils.copyParameterToBean(req.getParameterMap(),new User());

        /*判断验证码是否正确(忽略大小写)*/
        boolean b = sessionCode.equalsIgnoreCase(code);
        if (b) {
            /*此时验证码输入正确*/
            /*检查用户名是否可用*/
            boolean b1 = userService.existsUsername(username);
            if (b1) {
                /*b1==true:用户名已存在, 不可用,跳回注册页面 regist.jsp*/
                System.out.println("用户名已存在!");
                /*将错误信息保存到request域中,保存用户名和邮箱*/
                req.setAttribute("msg","用户名已存在!");

                req.getRequestDispatcher("/pages/user/regist.jsp").forward(req, resp);
            } else {
                /*b1==false: 用户名不存在,可用, 将用户信息保存到数据库*/
                System.out.println("注册成功!");
                userService.registerUser(new User(null, username, password, email));
                /*跳转到注册成功的页面: /pages/user/regist_success.jsp*/
                req.getRequestDispatcher("/pages/user/regist_success.jsp").forward(req,resp);
            }

        }else {
            /*此时验证码输入错误,跳回注册页面 regist.jsp*/
            System.out.println("验证码错误!");
            /*将错误信息保存到request域中,保存用户名和邮箱*/
            req.setAttribute("msg","验证码错误!");
            req.setAttribute("username",username);
            req.setAttribute("email",email);

            req.getRequestDispatcher("/pages/user/regist.jsp").forward(req, resp);
        }
    }
  • jsp页面如下:
<input class="itxt" type="text" style="width: 150px;" name="code" id="code"/>
                                    <%--获取获取谷歌验证码--%>
<img alt="" id="code_img" src="kaptcha.jpg" style="float: right; margin-right: 40px; width: 80px; height: 40px">

注:这两行代码为验证码的获取与输入。验证码的获取由
google提供的 kaptcha-2.3.2.jar 包实现,在jsp页面的头文件中,利用javaScript绑定了切换验证码的
单机事件,用户点击图片即可切换。

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Google会员注册页面</title>

	<%--导入静态包含base标签,css样式,jquery文件--%>
	<%@include file="/pages/common/head.jsp"%>

	<script type="text/javascript">
		// 页面加载完成之后
		$(function () {

            $("#code_img").click(function () {
                this.src="${basePath}kaptcha.jpg?d="+new Date();
            });
			// 给注册绑定单击事件
			$("#sub_btn").click(function () {
				// 验证用户名:必须由字母,数字下划线组成,并且长度为5到12位
				//1 获取用户名输入框里的内容
				var usernameText = $("#username").val();
				//2 创建正则表达式对象
				var usernamePatt = /^\w{5,12}$/;
				//3 使用test方法验证
				if (!usernamePatt.test(usernameText)) {
					//4 提示用户结果
					$("span.errorMsg").text("用户名不合法!");

					return false;
				}

				// 验证密码:必须由字母,数字下划线组成,并且长度为5到12位
				//1 获取用户名输入框里的内容
				var passwordText = $("#password").val();
				//2 创建正则表达式对象
				var passwordPatt = /^\w{5,12}$/;
				//3 使用test方法验证
				if (!passwordPatt.test(passwordText)) {
					//4 提示用户结果
					$("span.errorMsg").text("密码不合法!");

					return false;
				}

				// 验证确认密码:和密码相同
				//1 获取确认密码内容
				var repwdText = $("#repwd").val();
				//2 和密码相比较
				if (repwdText != passwordText) {
					//3 提示用户
					$("span.errorMsg").text("确认密码和密码不一致!");

					return false;
				}

				// 邮箱验证:xxxxx@xxx.com
				//1 获取邮箱里的内容
				var emailText = $("#email").val();
				//2 创建正则表达式对象
				var emailPatt = /^[a-z\d]+(\.[a-z\d]+)*@([\da-z](-[\da-z])?)+(\.{1,2}[a-z]+)+$/;
				//3 使用test方法验证是否合法
				if (!emailPatt.test(emailText)) {
					//4 提示用户
					$("span.errorMsg").text("邮箱格式不合法!");

					return false;
				}

				// 验证码:现在只需要验证用户已输入。因为还没讲到服务器。验证码生成。
				var codeText = $("#code").val();

				//去掉验证码前后空格
				// alert("去空格前:["+codeText+"]")
				codeText = $.trim(codeText);
				// alert("去空格后:["+codeText+"]")

				if (codeText == null || codeText == "") {
					//4 提示用户
					$("span.errorMsg").text("验证码不能为空!");

					return false;
				}

				// 去掉错误信息
				$("span.errorMsg").text("");

			});

		});


	</script>
<style type="text/css">
	.login_form{
		height:420px;
		margin-top: 25px;
	}
	
</style>
</head>
<body>
		<div id="login_header">
			<img class="logo_img" alt="" src="static/img/google.gif" height="88" width="365" style="margin-left: -120px" >
		</div>
		
			<div class="login_banner">
			
				<div id="l_content">
					<span class="login_word">欢迎注册</span>
				</div>
				
				<div id="content">
					<div class="login_form">
						<div class="login_box">
							<div class="tit">
								<h1>注册Google会员</h1>
								<span class="errorMsg">
									<%--<%=request.getAttribute("msg")==null?"":request.getAttribute("msg")%>--%>
									<%--EL表达式写法:--%>
									${requestScope.msg}
								</span>
							</div>
							<div class="form">

								<form action="userServlet" method="post">
									<input type="hidden" name="action" value="regist"/>
									<label>用户名称:</label>
									<input class="itxt" type="text" placeholder="请输入用户名" autocomplete="off" tabindex="1" name="username" id="username"
									<%--value="<%=request.getAttribute("username")==null?"":request.getAttribute("username")%>"--%>
										   value="${requestScope.username}"
									/>
									<br />
									<br />
									<label>用户密码:</label>
									<input class="itxt" type="password" placeholder="请输入密码" autocomplete="off" tabindex="1" name="password" id="password" />
									<br />
									<br />
									<label>确认密码:</label>
									<input class="itxt" type="password" placeholder="确认密码" autocomplete="off" tabindex="1" name="repwd" id="repwd" />
									<br />
									<br />
									<label>电子邮件:</label>
									<input class="itxt" type="text" placeholder="请输入邮箱地址" autocomplete="off" tabindex="1" name="email" id="email"
									<%--value="<%=request.getAttribute("email")==null?"":request.getAttribute("email")%>"--%>
										   value="${requestScope.email}"
									/>
									<br />
									<br />
									<label>验证码:</label>
									<input class="itxt" type="text" style="width: 150px;" name="code" id="code"/>
                                    <%--获取获取谷歌验证码--%>
									<img alt="" id="code_img" src="kaptcha.jpg" style="float: right; margin-right: 40px; width: 80px; height: 40px">
									<br />
									<br />
									<input type="submit" value="注册" id="sub_btn" />
									
								</form>
							</div>
							
						</div>
					</div>
				</div>
			</div>
		<%--导入底部标签--%>
		<%@include file="/pages/common/footer.jsp"%>
</body>
</html>

(4)主页面价格筛选实现

  • 页面展示:
    在这里插入图片描述

  • 分析:
    实现价格筛选页面,需要将所输入的价格区间中的商品重新存入一个新的集合中。刷新页面后,将集合中的商品信息、总数目、分页情况重定向在主页面。并且刷新分页行的信息,将页数及价格区间中的总商品数展示在分页条上。

  • web层:

/**
     * 价格区间搜索,根据价格分页
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    protected void pageByPrice(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        /*获取请求的pageNo和pageSize,min,max*/
        int pageNo = WebUtils.parseInt(req.getParameter("pageNo"), 1);
        int pageSize= WebUtils.parseInt(req.getParameter("pageSize"), Page.PAGE_SIZE); //客户端没传入则用常量
        int min = WebUtils.parseInt(req.getParameter("min"), 0);
        int max = WebUtils.parseInt(req.getParameter("max"), Integer.MAX_VALUE);
        /*调用BookService.pageByPrice获取page对象*/
        Page<Book> page = bookService.pageByPrice(pageNo, pageSize,min,max);

        /*设置Url*/
        StringBuilder sb = new StringBuilder("client/bookServlet?action=pageByPrice");
        if (req.getParameter("min")!=null){
            sb.append("&min=").append(req.getParameter("min"));
        }
        if (req.getParameter("max")!=null){
            sb.append("&max=").append(req.getParameter("max"));
        }
        page.setUrl(sb.toString());

        /*将page保存到request域中*/
        req.setAttribute("page",page);
        /*请求转发到/pages/manager/book_manager.jsp*/
        req.getRequestDispatcher("/pages/client/index.jsp").forward(req,resp);
    }
  • jsp页面:
<div class="book_cond">
				<form action="client/bookServlet" method="get" style="margin-left: -43px">
					<input type="hidden" name="action" value="pageByPrice">
					价格:<input id="min" type="text" name="min" value="${param.min}"> 元 -
						<input id="max" type="text" name="max" value="${param.max}"><input type="submit" value="查询" />
				</form>
</div>

(5)分页实现

  • 页面展示:
    在这里插入图片描述

  • 分析:
    分页操作的要点在于将指定页码的数据展现在界面上
    点击页数按钮时,会出现几种特殊情况。
    例如,当前页数在前三页或最后三页,则不会改变显示的所有页数,只会移动当前页数所选框;
    在首页和末页时,首页选项和末页选项分别消失;
    输出总页数和总记录数;
    在输入框输入页面跳转时,跳转后选择框指定到所选页面;
    若跳转页数小等于0或大于最大页数,则会分别跳转到首页和末页。

  • jsp页面:

<%--分页操作--%>
		<div id="page_nav">
			<%--大于首页,才显示--%>
			<c:if test="${requestScope.page.pageNo > 1}">
				<a href="${ requestScope.page.url }&pageNo=1">首页</a>
				<a href="${ requestScope.page.url }&pageNo=${requestScope.page.pageNo-1}">上一页</a>
			</c:if>
			<%--页码输出的开始--%>
			<c:choose>
				<%--情况1:如果总页码小于等于5的情况,页码的范围是:1-总页码--%>
				<c:when test="${ requestScope.page.pageTotal <= 5 }">
					<c:set var="begin" value="1"/>
					<c:set var="end" value="${requestScope.page.pageTotal}"/>
				</c:when>
				<%--情况2:总页码大于5的情况--%>
				<c:when test="${requestScope.page.pageTotal > 5}">
					<c:choose>
						<%--小情况1:当前页码为前面3个:1,2,3的情况,页码范围是:1-5.--%>
						<c:when test="${requestScope.page.pageNo <= 3}">
							<c:set var="begin" value="1"/>
							<c:set var="end" value="5"/>
						</c:when>
						<%--小情况2:当前页码为最后3个,8,9,10,页码范围是:总页码减4 - 总页码--%>
						<c:when test="${requestScope.page.pageNo > requestScope.page.pageTotal-3}">
							<c:set var="begin" value="${requestScope.page.pageTotal-4}"/>
							<c:set var="end" value="${requestScope.page.pageTotal}"/>
						</c:when>
						<%--小情况3:4,5,6,7,页码范围是:当前页码减2 - 当前页码加2--%>
						<c:otherwise>
							<c:set var="begin" value="${requestScope.page.pageNo-2}"/>
							<c:set var="end" value="${requestScope.page.pageNo+2}"/>
						</c:otherwise>
					</c:choose>
				</c:when>
			</c:choose>

			<c:forEach begin="${begin}" end="${end}" var="i">
				<c:if test="${i == requestScope.page.pageNo}">
					【${i}】
				</c:if>
				<c:if test="${i != requestScope.page.pageNo}">
					<a href="${ requestScope.page.url }&pageNo=${i}">${i}</a>
				</c:if>
			</c:forEach>
			<%--页码输出的结束--%>

			<%-- 如果已经 是最后一页,则不显示下一页,末页 --%>
			<c:if test="${requestScope.page.pageNo < requestScope.page.pageTotal}">
				<a href="${ requestScope.page.url }&pageNo=${requestScope.page.pageNo+1}">下一页</a>
				<a href="${ requestScope.page.url }&pageNo=${requestScope.page.pageTotal}">末页</a>
			</c:if>

			共${ requestScope.page.pageTotal }页,${ requestScope.page.pageTotalCount }条记录
			到第<input value="${param.pageNo}" name="pn" id="pn_input"/><input id="searchPageBtn" type="button" value="确定">

			<script type="text/javascript">

				$(function () {
					// 跳到指定的页码
					$("#searchPageBtn").click(function () {

						var pageNo = $("#pn_input").val();

						<%--var pageTotal = ${requestScope.page.pageTotal};--%>
						<%--alert(pageTotal);--%>

						// javaScript语言中提供了一个location地址栏对象
						// 它有一个属性叫href.它可以获取浏览器地址栏中的地址
						// href属性可读,可写
						location.href = "${pageScope.basePath}${ requestScope.page.url }&pageNo=" + pageNo;
					});
				});

			</script>
		</div>
  • 页面展示书籍的jsp页面代码:
<%--遍历页面商品--%>
			<c:forEach items="${requestScope.page.items}" var="book">
				<div class="b_list">
					<div class="img_div">
						<img class="book_img" alt="" src="${book.imgPath}" />
					</div>
					<div class="book_info">
						<div class="book_name">
							<span class="sp1">书名:</span>
							<span class="sp2">${book.name}</span>
						</div>
						<div class="book_author">
							<span class="sp1">作者:</span>
							<span class="sp2">${book.author}</span>
						</div>
						<div class="book_price">
							<span class="sp1">价格:</span>
							<span class="sp2">${book.price}</span>
						</div>
						<div class="book_sales">
							<span class="sp1">销量:</span>
							<span class="sp2">${book.sales}</span>
						</div>
						<div class="book_amount">
							<span class="sp1">库存:</span>
							<span class="sp2">${book.stock}</span>
						</div>
						<div class="book_add">
							<button bookId="${book.id}" class="addToCart">加入购物车</button>
						</div>
					</div>
				</div>
			</c:forEach>

(6)购物车实现

1.主页添加商品到购物车实现
  • 页面展示:
    在这里插入图片描述

  • 分析:
    购物车界面有两种显示方式:显示购物车为空或显示添加的商品
    判断条件为session域中关于购物车的信息是否为空
    当session域中有购物车的商品,则会改为显示商品的信息
    当点击加入购物车按钮时,会将session域中最后添加的数据显示在主页面,形成添加图书的反馈

  • 编写购物车商品项类:

package pojo;

import java.math.BigDecimal;
import java.util.Objects;
/*购物车商品项*/

public class CartItem {
    private Integer id;
    private String name;
    private Integer count; //商品数量
    private BigDecimal price; //商品单价
    private BigDecimal totalPrice; //商品总价

    public CartItem(Integer id, String name, Integer count, BigDecimal price, BigDecimal totalPrice) {
        this.id = id;
        this.name = name;
        this.count = count;
        this.price = price;
        this.totalPrice = totalPrice;
    }

    public CartItem() {
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getCount() {
        return count;
    }

    public void setCount(Integer count) {
        this.count = count;
    }

    public BigDecimal getPrice() {
        return price;
    }

    public void setPrice(BigDecimal price) {
        this.price = price;
    }

    public BigDecimal getTotalPrice() {
        return totalPrice;
    }

    public void setTotalPrice(BigDecimal totalPrice) {
        this.totalPrice = totalPrice;
    }

    @Override
    public String toString() {
        return "CartItem{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", count=" + count +
                ", price=" + price +
                ", totalPrice=" + totalPrice +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        CartItem cartItem = (CartItem) o;
        return Objects.equals(id, cartItem.id) && Objects.equals(name, cartItem.name) && Objects.equals(count, cartItem.count) && Objects.equals(price, cartItem.price) && Objects.equals(totalPrice, cartItem.totalPrice);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id, name, count, price, totalPrice);
    }
}
  • 编写购物车类:
package pojo;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.*;

/*购物车类*/
public class Cart {
    /*items  key:商品编号, value:商品信息 */
    private Map<Integer,CartItem> items=new LinkedHashMap<Integer,CartItem>();

    public Cart(Map<Integer,CartItem> items) {
        this.items = items;
    }

    public Cart() {}

    /**
     * 添加商品项
     * @param cartItem
     */
    public void addItem(CartItem cartItem){
        /*cartItem.getId():获取传入商品项的id     items.get(id):通过id找集合中对应的商品项*/
        CartItem item = items.get(cartItem.getId());
        if (item==null){
            /*如果购物车中不含此商品项,则直接向items(商品项)集合中添加商品*/
            items.put(cartItem.getId(),cartItem);
        }else{
            /*如果购物车中已包含此商品项,则商品数量和商品价格更新*/
            item.setCount(cartItem.getCount()+1); //数量+1
            item.setTotalPrice(item.getPrice().multiply(new BigDecimal(item.getCount()))); //商品单价x商品总数
        }
    }

    /**
     * 删除商品项
     * @param id
     */
    public void deleteItem(Integer id){
        items.remove(id);
    }

    /**
     * 清空购物车
     */
    public void clear(){
        items.clear();
    }

    /**
     * 修改商品数量,并修改累计价格
     * @param id
     * @param count
     */
    public void updateCount(Integer id, Integer count){
        /*获取集合中id对应的商品项*/
        CartItem cartItem = items.get(id);
        if (cartItem!=null){
            cartItem.setCount(count);
            cartItem.setTotalPrice(cartItem.getPrice().multiply(new BigDecimal(cartItem.getCount())));
        }
    }

    /**
     * 获取商品总数量
     * @return
     */
    public Integer getTotalCount() {
        Integer totalCount=0;
        /*遍历集合*/
        for(Map.Entry<Integer,CartItem> entry: items.entrySet()){
           totalCount += entry.getValue().getCount(); //获取每个商品项的数量 并累加
        }
        return totalCount;
    }

    /**
     * 获取商品总价格
     * @return
     */
    public BigDecimal getTotalPrice() {
        BigDecimal totalPrice=new BigDecimal(0);
        for (Map.Entry<Integer,CartItem> entry : items.entrySet()){
            totalPrice=totalPrice.add(entry.getValue().getTotalPrice());
        }
        return totalPrice;
    }


    public Map<Integer,CartItem> getItems() {
        return items;
    }

    public void setItems(Map<Integer,CartItem> items) {
        this.items = items;
    }

    @Override
    public String toString() {
        return "Cart{" +
                "totalCount=" + getTotalCount() +
                ", totalPrice=" + getTotalPrice() +
                ", items=" + items +
                '}';
    }

}
  • 在web层编写购物车添加商品的方法:
private BookService bookService=new BookServiceImpl();

    /**
     * 添加商品
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    protected void addItem(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        /*获取请求参数的id*/
        int id = WebUtils.parseInt(req.getParameter("id"), 0);
        /*调用bookService.queryBookById查询图书信息*/
        Book book = bookService.queryBookById(id);
        /*将图书信息,转化为CartItem对象*/
        CartItem cartItem=new CartItem(book.getId(),book.getName(),1,book.getPrice(),book.getPrice());

        /*调用Cart.addItem添加图书信息, 保存到session域中*/
        Cart cart =(Cart)req.getSession().getAttribute("cart");
        /*若此时首次添加商品,session中无cart对象,则得到的cart对象为空,需要创建cart对象*/
        if (cart==null){
            cart = new Cart();
            req.getSession().setAttribute("cart",cart);
        }
        cart.addItem(cartItem);

        /*将最后一个添加的商品名称保存到Session域中*/
        req.getSession().setAttribute("lastItem",cartItem.getName());

        /*获取请求头Referer的值*/
        String referer = req.getHeader("Referer");
        /*重定向到index.jsp指定page的页面*/
        resp.sendRedirect(referer);
    }
  • 主页显示添加信息的jsp代码:
<div style="text-align: center">

				<%--购物车为空时--%>
				<c:if test="${empty sessionScope.cart.items}">
					<span style="margin-right: 30px">请添加商品</span>
					<div style="margin-right: 30px">
						<span style="color: red" >当前购物车为空</span>
					</div>
				</c:if>

				<%--购物车非空时--%>
				<c:if test="${not empty sessionScope.cart.items}">
					<span>您的购物车中有${sessionScope.cart.totalCount}件商品</span>
					<div>
						您刚刚将<span style="color: red">${sessionScope.lastItem}</span>加入到了购物车中
					</div>
				</c:if>

			</div>
  • 主页的添加商品按钮jsp代码:
<div class="book_add">
		<button bookId="${book.id}" class="addToCart">加入购物车</button>
</div>

2.购物车页面功能实现
  • 页面展示:
    在这里插入图片描述
  • 分析:
    购物车页面的删除按钮绑定了单机事件,点击确认后,则会调用cartServlet程序中的deleteItem方法将session域中购物车界面指定id的书籍删除,并且会根据session域中图书信息重新计算商品数量和总价(修改和清空同理)
    清空购物车同样绑定了单机事件,调用cartServlet程序中的clear方法将sessoin域中购物车界面的所有图书信息删除
    修改数量输入框绑定了焦点事件,修改过后点击空白处,则会将当前商品数、当前商品金额修改。并修改总数,总金额
  • servlet程序:
package web;
/*购物车的Servlet类*/
import pojo.Book;
import pojo.Cart;
import pojo.CartItem;
import service.BookService;
import service.BookServiceImpl;
import utils.WebUtils;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class CartServlet extends BaseServlet{
    private BookService bookService=new BookServiceImpl();

    /**
     * 添加商品
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    protected void addItem(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        /*获取请求参数的id*/
        int id = WebUtils.parseInt(req.getParameter("id"), 0);
        /*调用bookService.queryBookById查询图书信息*/
        Book book = bookService.queryBookById(id);
        /*将图书信息,转化为CartItem对象*/
        CartItem cartItem=new CartItem(book.getId(),book.getName(),1,book.getPrice(),book.getPrice());

        /*调用Cart.addItem添加图书信息, 保存到session域中*/
        Cart cart =(Cart)req.getSession().getAttribute("cart");
        /*若此时首次添加商品,session中无cart对象,则得到的cart对象为空,需要创建cart对象*/
        if (cart==null){
            cart = new Cart();
            req.getSession().setAttribute("cart",cart);
        }
        cart.addItem(cartItem);

        /*将最后一个添加的商品名称保存到Session域中*/
        req.getSession().setAttribute("lastItem",cartItem.getName());

        /*获取请求头Referer的值*/
        String referer = req.getHeader("Referer");
        /*重定向到index.jsp指定page的页面*/
        resp.sendRedirect(referer);
    }

    /**
     * 删除商品项
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    protected void deleteItem(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        /*获取请求参数id*/
        int id = WebUtils.parseInt(req.getParameter("id"), 0);
        /*从Session域获取cart对象*/
        Cart cart = (Cart)req.getSession().getAttribute("cart");
        if (cart!=null){
            /*调用cart.deleteItem删除指定商品*/
            cart.deleteItem(id);
        }
        /*重定向到购物车页面,cart.jsp*/
        resp.sendRedirect(req.getHeader("Referer"));
    }

    /**
     * 清空购物车
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    protected void clear(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        /*从Session域中获取Cart对象*/
        Cart cart =(Cart) req.getSession().getAttribute("cart");
        if (cart!=null){
            /*调用cart.clear()方法清空购物车*/
            cart.clear();
        }
        /*重定向到购物车页面,cart.jsp*/
        resp.sendRedirect(req.getHeader("Referer"));
    }

    /**
     * 修改商品数量,(同时修改了商品总价格)
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    protected void updateCount(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        /*获取请求参数id的值*/
        int id = WebUtils.parseInt(req.getParameter("id"), 0);
        int count = WebUtils.parseInt(req.getParameter("count"), 1);
        /*从Session域中获取Cart对象*/
        Cart cart = (Cart)req.getSession().getAttribute("cart");
        if (cart!=null){
            /*调用cart.updateCount方法修改商品数量*/
            cart.updateCount(id,count);
        }
        /*重定向到购物车页面*/
        resp.sendRedirect(req.getHeader("Referer"));
    }

}
  • jsp页面:
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>购物车</title>

	<%--导入静态包含base标签,css样式,jquery文件--%>
	<%@include file="/pages/common/head.jsp"%>

	<script type="text/javascript">
		$(function () {
			/*给删除按钮绑定单机事件*/
			$(".deleteItem").click(function () {
				return confirm("你确定要删除【"+$(this).parent().parent().find("td:first").text()+"】吗?");
			});
		});

		$(function () {
			/*给清空购物车按钮绑定单机事件*/
			$("#clearCart").click(function () {
				return confirm("你确定要清空购物车吗?");
			});
		});

		$(function () {
			/*给修改商品数量绑定焦点事件*/
			$(".updateCount").change(function () {
				var name = $(this).parent().parent().find("td:first").text();
				var count =this.value;
				var id=$(this).attr("bookId");
				if (confirm("你确定要修改商品【"+name+"】的值为:"+count+"吗?")){
				/*修改数据*/
					location.href="cartServlet?action=updateCount&count="+count+"&id="+id;
				}else{
				/*返回为原来数据*/
					this.value=this.defaultValue;
				}
			});
		});

	</script>

</head>
<body>
	
	<div id="header">
			<img class="logo_img" alt="" src="static/img/google.gif" height="88" width="345">
			<span class="wel_word">购物车</span>

			<%--导入成功后导航栏--%>
			<%@ include file="/pages/common/login_success_menu.jsp"%>

	</div>
	
	<div id="main" style="background: ghostwhite">
	
		<table>
			<tr>
				<td>商品名称</td>
				<td>数量</td>
				<td>单价</td>
				<td>金额</td>
				<td>操作</td>
			</tr>

			<%--购物车为空--%>
			<c:if test="${empty sessionScope.cart.items}">
				<tr>
					<td colspan="5" >
						<a href="index.jsp" style="color: red"> 您的购物车为空!快去逛逛!</a>
					</td>
				</tr>
			</c:if>

			<%--购物车非空--%>
			<c:if test="${not empty sessionScope.cart.items}">
				<%--遍历购物车中的内容--%>
				<c:forEach items="${sessionScope.cart.items}" var="entry">
					<tr>
						<td>${entry.value.name}</td>
						<td>
							<input type="text" style="width: 25px" class="updateCount"
							bookId="${entry.value.id}"	   value="${entry.value.count}"/>
						</td>
						<td>${entry.value.price}</td>
						<td>${entry.value.totalPrice}</td>
						<td><a class="deleteItem" href="cartServlet?action=deleteItem&id=${entry.value.id}">删除</a></td>
					</tr>
				</c:forEach>
			</c:if>


		</table>
		<%--购物车非空时才输出此内容--%>
		<c:if test="${not empty sessionScope.cart.items}">
			<div class="cart_info">
				<span class="cart_span">购物车中共有<span class="b_count">${sessionScope.cart.totalCount}</span>件商品</span>
				<span class="cart_span">总金额<span class="b_price">${sessionScope.cart.totalPrice}</span></span>
				<span class="cart_span"><a id="clearCart" href="cartServlet?action=clear">清空购物车</a></span>
				<span class="cart_span"><a href="orderServlet?action=createOrder">去结账</a></span>
			</div>
		</c:if>

	</div>

	<%--导入底部标签--%>
	<%@include file="/pages/common/footer.jsp"%>
</body>
</html>

3.结账并生成订单功能实现
  • 功能展示:
    在这里插入图片描述

  • 点击去结账按钮则会生成订单,订单中会包含购物车中所有图书的信息。
    在t_order数据库中生成订单号,下单时间,本单总价,缺货数量,下单用户的id

    t_order数据库:
    在这里插入图片描述
    dao程序:

public class OrderDaoImpl extends BaseDao implements OrderDao{
    /**
     * 保存订单
     * @param order
     * @return
     */
    @Override
    public int saveOrder(Order order) {
        String sql="insert into t_order (order_id,create_time,price,status,user_id) values (?,?,?,?,?)";
        return update(sql,order.getOrderId(),order.getCreateTime(),
                order.getPrice(),order.getStatus(),order.getUserId());
    }
}
  • 在t_order_item数据库中会生成被购买的图书编号,名称,数量,单价,总价,对应的订单号

t_order_item数据库:在这里插入图片描述
dao程序:

public class OrderItemDaoImpl extends BaseDao implements OrderItemDao{
    /**
     * 保存订单项
     * @param orderItem
     * @return
     */
    @Override
    public int saveOrderItem(OrderItem orderItem) {
        String sql="insert into t_order_item (name,count,price,total_price,order_id) values (?,?,?,?,?)";
        return update(sql,orderItem.getName(),orderItem.getCount(),orderItem.getPrice(),
                orderItem.getTotalPrice(),orderItem.getOrderId());
    }
}
  • 创建订单并保存订单项的service实现类:
public class OrderServiceImpl implements  OrderService{
    private OrderDao orderDao = new OrderDaoImpl();
    private OrderItemDao orderItemDao = new OrderItemDaoImpl();
    private BookDao bookDao=new BookDaoImpl();

    /**
     * 创建订单(保存订单和订单项)
     * @param cart
     * @param userId
     * @return
     */
    @Override
    public String createOrder(Cart cart, Integer userId) {
        /*生成订单号,唯一: 时间戳+用户id*/
        String orderId=System.currentTimeMillis()+""+userId;
        /*创建一个订单对象*/
        Order order = new Order(orderId, new Date(), cart.getTotalPrice(), 0, userId);
        /*保存订单*/
        orderDao.saveOrder(order);

        /*遍历购物车中的商品项cart转化为订单项order保存到数据库*/
        for (Map.Entry<Integer, CartItem> entry:cart.getItems().entrySet()){
            /*获取每个购物车中的商品项*/
            CartItem cartItem = entry.getValue();
            /*转化为每一个订单项*/
            OrderItem orderItem = new OrderItem(null, cartItem.getName(), cartItem.getCount(),
                    cartItem.getPrice(), cartItem.getTotalPrice(), orderId);
            /*保存订单项*/
            orderItemDao.saveOrderItem(orderItem);

            /*保存订单时,要更改图书的销量和库存*/
            /*获取对应图书对象*/
            Book book = bookDao.queryBookById(cartItem.getId());
            /*更改销量*/
            book.setSales(book.getSales()+cartItem.getCount());
            /*更改库存*/
            book.setStock(book.getStock()- cartItem.getCount());
            /*保存数据到t_book数据库*/
            bookDao.updateBook(book);
        }
        /*结账后,清空购物车*/
        cart.clear();
        return orderId;
    }
}
  • 创建订单保存订单项的Servlet程序:
public class OrderServlet extends BaseServlet{
    private OrderService orderService = new OrderServiceImpl();
    /**
     * 创建订单,并生成订单项
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    protected void createOrder(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        /*在Session域中获取cart对象*/
        Cart cart = (Cart)req.getSession().getAttribute("cart");

        /*获取userId*/
        User loginUser=(User)req.getSession().getAttribute("user");
        /*如果用户未登录,loginUser为空,跳转到登录页面*/
        if (loginUser==null){
            req.getRequestDispatcher("/pages/user/login.jsp").forward(req,resp);
        }else{
            /*若用户已经登录,可获取到User对象,通过user对象获取用户id*/
            Integer userId = loginUser.getId();
            /*调用orderService.createOrder(Cart cart,Integer userId) 保存订单和订单项, 得到订单号*/
            String orderId = orderService.createOrder(cart, userId);
            /*将订单号保存到session域中(需要在checkout.jsp页面输出)*/
            req.getSession().setAttribute("orderId",orderId);
            /*重定向到/pages/cart/checkout.jsp*/
            resp.sendRedirect(req.getContextPath()+"/pages/cart/checkout.jsp");
        }
    }
}
  • 创建订单后的jsp页面:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>结算页面</title>

	<%--导入静态包含base标签,css样式,jquery文件--%>
	<%@include file="/pages/common/head.jsp"%>

	<style type="text/css">
	h1 {
		text-align: center;
		margin-top: 200px;
	}
</style>
</head>
<body>
	
	<div id="header">
			<img class="logo_img" alt="" src="static/img/google.gif" height="88" width="365">
			<span class="wel_word">结算</span>
		<%--导入成功后导航栏--%>
		<div>
			<a href="manager/bookServlet?action=page">图书管理</a>
			<a href="pages/manager/order_manager.jsp">订单管理</a>
			<a href="pages/cart/cart.jsp">返回</a>
		</div>
	</div>
	
	<div id="main">
		
		<h1>你的订单已结算,订单号为: ${sessionScope.orderId}</h1>
		
	
	</div>

	<%--导入底部标签--%>
	<%@include file="/pages/common/footer.jsp"%>
</body>
</html>

(7)后台管理实现

1.过滤未登录用户
  • 未登录被过滤展示:
    在这里插入图片描述

  • 已登录展示:
    在这里插入图片描述

  • 分析:
    由于订单管理页面不允许游客进入,则在此页面设置了filter过滤器来过滤未登录的用户
    在web.xml配置文件中配置过滤器的名称和过滤器拦截的页面url,当进入过滤页面时,过滤器则会执行doFilter方法里的业务逻辑
    本项目设置两个过滤器,分别为:transactionsFilter和ManagerFilter,分别用来处理事物和过滤未登录用户
    transactionsFilter过滤器判断事物进行时是否出现异常,若出现异常,则事物回滚,未出现则过滤器放行
    ManagerFilter过滤器判断session域中user对象是否为空,若为空则说明未登陆,跳转到登录界面。若不为空则过滤器放行

  • web.xml配置过滤器:

<filter>
        <filter-name>ManagerFilter</filter-name>
        <filter-class>filter.ManagerFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>ManagerFilter</filter-name>
        <url-pattern>/pages/manager/*</url-pattern>
        <url-pattern>/manager/bookServlet</url-pattern>
    </filter-mapping>

    <filter>
        <filter-name>TransactionsFilter</filter-name>
        <filter-class>filter.TransactionFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>TransactionsFilter</filter-name>
        <!--/*表示包含当前工程下的所有项目-->
        <url-pattern>/*</url-pattern>
    </filter-mapping>
  • transactionsFilter过滤器:
public class TransactionFilter implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        try {
            filterChain.doFilter(servletRequest,servletResponse);
            JdbcUtils.commitAndClose();
        } catch (Exception e) {
            JdbcUtils.rollbackAndClose();
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }
}
  • ManagerFilter过滤器:
public class ManagerFilter implements Filter {
     /**
     * 对后台管理系统进行过滤
     * @param servletRequest
     * @param servletResponse
     * @param filterChain
     * @throws IOException
     * @throws ServletException
     */
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest req=(HttpServletRequest) servletRequest;
        Object user = req.getSession().getAttribute("user");
        if (user==null){
            /*用户未登陆,跳转到登录页面*/
            req.getRequestDispatcher("/pages/user/login.jsp").forward(servletRequest,servletResponse);
        }else{
            filterChain.doFilter(servletRequest,servletResponse);
        }
    }
}

2.图书管理页面实现
  • 页面展示:
    在这里插入图片描述

  • 分析:
    图书管理界面中罗列了主界面中所展示的所有图书信息
    图书管理界面有对图书的增删改操作,操作都会影响到管理界面和主界面中图书信息

  • jsp页面代码:

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>图书管理</title>
<%--图书管理界面--%>
	<%--导入静态包含base标签,css样式,jquery文件--%>
	<%@include file="/pages/common/head.jsp"%>

    <script type="text/javascript">
        $(function () {
            /*给删除的a标签绑定单机事件,用于提示是否删除*/
            $("a.deleteClass").click(function () {
                /*confirm为确认提示操作
                * 两个按钮分别返回true和false
                * 返回true执行事件,返回false终止事件执行
                * */
                return confirm("你确定要删除【"+$(this).parent().parent().find("td:first").text()+"】?");
            });
        });
    </script>

</head>
<body>
	
	<div id="header">
			<img class="logo_img" alt="" src="static/img/google.gif" height="88" width="365" >
			<span class="wel_word">图书管理系统</span>

		<%@include file="../common/manager_menu.jsp"%>
	</div>
	
	<div id="main" style="background: ghostwhite">

		<table>
			<tr>
				<td>名称</td>
				<td>价格</td>
				<td>作者</td>
				<td>销量</td>
				<td>库存</td>
				<td colspan="2">操作</td>
			</tr>

			<%--遍历request域中保存的book信息--%>
			<c:forEach items="${requestScope.page.items}" var="book">
				<tr>
					<td>${book.name}</td>
					<td>${book.price}</td>
					<td>${book.author}</td>
					<td>${book.sales}</td>
					<td>${book.stock}</td>
					<%--请求manager/bookServlet页面,使用其getBook方法 并获取id 指名为update操作--%>
					<td><a href="manager/bookServlet?action=getBook&id=${book.id}&
					pageNo=${requestScope.page.pageNo}">修改</a></td>
					<%--请求manager/bookServlet页面,使用其delete方法 并获取id--%>
					<td><a class="deleteClass" href="manager/bookServlet?action=delete&id=${book.id}&
					pageNo=${requestScope.page.pageNo}">删除</a></td>
				</tr>
			</c:forEach>
			
			<tr>
				<td></td>
				<td></td>
				<td></td>
				<td></td>
				<td></td>
				<td></td>
				<td><a href="pages/manager/book_edit.jsp?pageNo=${requestScope.page.pageTotal}">添加图书</a></td> <%--指名为add操作--%>
			</tr>
		</table>

		    <div id="page_nav">
			<%--分页操作--%>
			<c:if test="${requestScope.page.pageNo >1}">
				<a href="manager/bookServlet?action=page&pageNo=1">首页</a>
				<a href="manager/bookServlet?action=page&pageNo=${requestScope.page.pageNo-1}">上一页</a>
			</c:if>
			<%--页码输出--%>
			<c:choose>
				<%--情况一:总页码数<=5,显示范围:1-总页码 --%>
				<c:when test="${requestScope.page.pageTotal<=5}">
					<c:set var="begin" value="1"/>
					<c:set var="end" value="${requestScope.page.pageTotal}"/>
				</c:when>

				<%--情况二:总页码>5--%>
				<c:when test="${requestScope.page.pageTotal>5}">
					<c:choose>
						<%--2.1: 当前页码为前三个,页码范围1-5 --%>
						<c:when test="${requestScope.page.pageNo<=3}">
							<c:set var="begin" value="1"/>
							<c:set var="end" value="5"/>
				        </c:when>

						<%--2.2: 当前页面为后三个,页码范围6-10--%>
						<c:when test="${requestScope.page.pageNo> requestScope.page.pageTotal-3}">
							<c:set var="begin" value="${requestScope.page.pageTotal-4}"/>
							<c:set var="end" value="${requestScope.page.pageTotal}"/>
						</c:when>

						<%--2.3: 页面为4,5,6,7, 页码范围:当前页码-2 —— 当前页码+2  --%>
						<c:otherwise>
							<c:set var="begin" value="${requestScope.page.pageNo-2}"/>
							<c:set var="end" value="${requestScope.page.pageNo+2}"/>
						</c:otherwise>

					</c:choose>
				</c:when>
			</c:choose>
			<%--输出分页值--%>
			<c:forEach begin="${begin}" end="${end}" var="i">
				<%--每个显示数字都能够点击,并且跳转到manager/bookServlet--%>
				<%--为当前页码时,不可点击,直接显示【i】--%>
				<c:if test="${requestScope.page.pageNo==i}">
					【${i}】
				</c:if>
				<%--不为当前页码时,可以点击,并跳转到指定索引页码--%>
				<c:if test="${requestScope.page.pageNo!=i}">
					<a href="manager/bookServlet?action=page&pageNo=${i}">${i}</a>
				</c:if>
			</c:forEach>

			<c:if test="${requestScope.page.pageNo< requestScope.page.pageTotal}">
				<a href="manager/bookServlet?action=page&pageNo=${requestScope.page.pageNo+1}">下一页</a>
				<a href="manager/bookServlet?action=page&pageNo=${requestScope.page.pageTotal}">末页</a>
			</c:if>

			共${requestScope.page.pageTotal}页,${requestScope.page.pageTotalCount}条记录 到第<input value="${param.pageNo}" name="pn" id="pn_input"/><input id="searchPageBtn" type="button" value="确定">
			<script>
				$(function () {
					/*跳到指定的页码*/
					$("#searchPageBtn").click(function () {
						var pageNo= $("#pn_input").val();

						var pageTotal=${requestScope.page.pageTotal};
						location.href="${pageScope.basePath}manager/bookServlet?action=page&pageNo="+pageNo;
					});
				})
			</script>
		</div>

	</div>

	<%--导入底部标签--%>
	<%@include file="/pages/common/footer.jsp"%>
</body>
</html>

3.删除图书功能实现
  • 功能展示:
    在这里插入图片描述

  • 分析:
    删除按钮绑定了单机事件,点金确认后调用bookServlet程序的delete方法进行删除

  • bookServlet程序的delete方法:

/**
     * 实现在订单管理系统中对书籍的删除操作
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    protected void delete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        /*获取请求id*/
        String id = req.getParameter("id");
        int i = WebUtils.parseInt(id,0);
        /*调用BookService.deleteBookById删除书籍*/
        bookService.deleteBookById(i);
        /*重定向请求转发到manager/bookServlet?action=list
         调用list方法展示/pages/manager/book_manager.jsp*/
        resp.sendRedirect(req.getContextPath() + "/manager/bookServlet?action=page&pageNo="+req.getParameter("pageNo"));
    }

4.修改图书和添加图书功能实现
  • 功能展示:

(修改图书)
在这里插入图片描述
(添加图书)
在这里插入图片描述

  • 分析:
    点击修改按钮后,跳转到编辑图书界面,并调用bookServlet程序中的getBook方法。通过图书的id,查询出该图书的所有信息
    将查询出的信息保存在request域中,跳转到book_edit(编辑图书)界面后,再从request域中取出对应key(字段)的值,显示在修改栏中
    由于添加图书和修改图书用的是同一个界面,所以需要通过book_edit页面的request域是否有图书对象来判断是添加还是修改操作
    若存在图书对象,则为修改操作,调用bookServlet程序中的add方法
    若不存在图书对象,则为添加操作,调用bookServlet程序中的update方法

  • bookServlet程序中的add和update方法

(add)


protected void add(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        int pageNo = WebUtils.parseInt(req.getParameter("pageNo"), 0);
        pageNo+=1;
        /*获取请求的参数,封装为Bean对象*/
        Book book = WebUtils.copyParameterToBean(req.getParameterMap(), new Book());
        /*调用BookService.addBook添加书籍*/
        bookService.addBook(book);
        /*用此方式用户按下F5功能键会发起最后一次请求(add方法),会导致表单重复提交
        req.getRequestDispatcher("manager/bookServlet?action=list").forward(req,resp);*/

        /*使用表单重定向
        请求转发到
        manager/bookServlet?action=list :再次调用list方法,将数据存储到request域中
        并跳转到/pages/manager/book_manager.jsp显示图书管理界面*/
        resp.sendRedirect(req.getContextPath() + "/manager/bookServlet?action=page&pageNo=" + pageNo);
    }

(update)

protected void update(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        /*获取请求的参数,封装为Bean对象*/
        Book book = WebUtils.copyParameterToBean(req.getParameterMap(), new Book());
        /*调用BookService.updateBook修改图书*/
        bookService.updateBook(book);
        /*请求重定向转发到manager/bookServlet?action=list
        调用list方法展示/pages/manager/book_manager.jsp*/
        resp.sendRedirect(req.getContextPath() + "/manager/bookServlet?action=page&pageNo="+req.getParameter("pageNo"));
    }

(8)界面错误跳转实现

  • 功能展示:
    在这里插入图片描述

  • 分析:
    通过web.xml配置文件配置错误跳转,当页面出现404错误或500错误时,不显示报错。
    而是显示我们自己书写的错误页面。

  • web.xml配置文件:

<!--配置服务器出错后,自动跳转的页面-->
    <error-page>
        <!--错误类型-->
        <error-code>500</error-code>
        <!--要跳转去的页面路径-->
        <location>/pages/error/error500.jsp</location>
    </error-page>

    <error-page>
        <error-code>404</error-code>
        <location>/pages/error/error404.jsp</location>
    </error-page>
  • 404错误jsp页面:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
    <%@include file="/pages/common/head.jsp"%>
</head>
<body>
<h1 style="font-size: 100px; color: #666666">页面出现异常,请返回</h1>
<a href="index.jsp">返回首页</a>
</body>
</html>
  • 500错误jsp页面:(与404设置的相同内容)
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
    <%@include file="/pages/common/head.jsp"%>
</head>
<body>
<h1 style="font-size: 100px; color: #666666">页面出现异常,请返回</h1>
<a href="index.jsp">返回首页</a>
</body>
</html>

四、总结

  • 在我看来,JavaWeb阶段在Java基础和java应用开发之间起到了很好的过渡作用。JavaWeb阶段“新事物”很多,这个书城项目能够很好的把它们融汇在一起。很多功能实现起来原理相同,所以项目完成后对web阶段知识点的掌握会有较大的提升。

  • 由于项目完成的时间比较久远,文章中可能会有遗漏或Exception的地方,欢迎大家指出。 关于JavaWeb书城项目的拙作就到这里,谢谢观看。

Logo

本社区面向用户介绍CSDN开发云部门内部产品使用和产品迭代功能,产品功能迭代和产品建议更透明和便捷

更多推荐