JavaWeb书城

JavaWeb 书城项目 表单验证的实现 表单验证主要使用 jQuery 实现,IDE 为 IDEA, 导入项目 新建一个模块 新建模块 把原有的文件导入

本文包含相关资料包-----> 点击直达获取<-------

JavaWeb 书城项目

表单验证的实现

表单验证主要使用 jQuery 实现,IDE 为 IDEA。

导入项目

新建一个模块

新建模块

把原有的文件导入, 原有文件链接 提取码:nefu

导入文件 接下来我们要修改 login.html 以及 regist.html [

login 与 regist jquery.js 放入 static/script 文件夹下 [

导入 jquery.js

regist 部分

我们要验证表单内容,主要有以下几个部分

注册表单

  • 验证用户名:必须由字母,数字下划线组成,并且长度为 5 到 12 位
  • 验证密码:必须由字母,数字下划线组成,并且长度为 5 到 12 位
  • 验证确认密码:和密码相同
  • 邮箱验证; xxxxx@xxx.com
  • 验证码:现在只需要验证用户已输入

具体大致流程如下:

  1. $(#id).val() 获得表单项的值
  2. / / 创建正则项表达式
  3. 使用 test 方法测试
  4. $("span.errorMsg").text("提示信息") 提示用户

以用户名为例,具体代码如下

// 验证用户名:必须由字母,数字下划线组成,并且长度为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; // 让其不跳转 }

需要注意的是,全部验证完之后,不应该出现错误信息,所以使用 $("span.errorMsg").text("") 将其清空。 regist.html 全部代码如下

```

尚硅谷会员注册页面
尚硅谷书城.Copyright ©2015

```

演示结果

结果


login 部分

login 部分也是一样的思路,因为表单标签没有提供 id ,我们要为其添加一个 id 标签,即修改原始代码如下

<input id="username" type="text" placeholder="请输入用户名" autocomplete="off" tabindex="1" name="username" /> <input id="password" type="password" placeholder="请输入密码" autocomplete="off" tabindex="1" name="password" />

后面就和注册一样的思路,利用 JQuery 验证表单即可。 login.html 全部代码如下

```

尚硅谷会员登录页面
尚硅谷书城.Copyright ©2015

```

演示结果

结果

用户注册和登录

之前已经做好前端页面,现在要通过 servlet 程序以及 JDBC 具体实现用户注册和登录

JavaEE 项目的三层架构

三层架构

为什么要分层呢?通过一层完成所有事情不行吗?

分层的目的是为了解耦。解耦就是为了降低代码的耦合度。方便项目后期的维护和升级。 我们知道有些项目代码量是巨大的,如果放在一层后期维护和升级会很麻烦,如果分出不同的层,每层都有不同负责的人员,那么维护和升级会变得轻松很多。 需求分析 需求一:用户注册 1)访问注册页面 2)填写注册信息,提交给服务器 3)服务器应该保存用户 4)当用户已经存在—-提示用户注册 失败,用户名已存在 5)当用户不存在—–注册成功 需求二:用户登录 1)访问登陆页面 2)填写用户名密码后提交 3)服务器判断用户是否存在 4)如果登陆失败 —>>>> 返回用户名或者密码错误信息 5)如果登录成功 —>>>> 返回登陆成功 信息

需要的接口和类

web 层 com.atguigu.web/servlet/controller service 层 com.atguigu.service Service 接口包 com.atguigu.service.impl Service 接口实现类 dao 持久层 com.atguigu.dao Dao 接口包 com.atguigu.dao.impl Dao 接口实现类 实体 bean 对象 com.atguigu.pojo/entity/domain/bean JavaBean 类 测试包 com.atguigu.test/junit 工具类 com.atguigu.utils

搭建环境

完成类编写后的目录结构如下 [

目录结构

创建数据库和表

这里我使用的是 MySql + Navicat ,新建一个 book 数据库,并新建一个 t_user 表。

t_user 表

通过建立 Unique 类型索引,可以使该字段唯一。 建立 Unique 索引 插入一条数据 插入数据

当然也可以直接使用如下 Sql 语句创建

drop database if exists book; create database book; use book; create table t_user( id int primary key auto_increment, username varchar(20) not null unique, password varchar(32) not null, email varchar(200) ); insert into t_user(username,password,email) values('admin','admin','admin@atguigu.com'); select * from t_user;

编写数据库对应的 JavaBean 对象

``` package com.atguigu.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;
}

} ```

编写工具类 JdbcUtils

JdbcUtils 工具类主要用来 建立数据库连接 释放数据库连接

导入 jar 包

数据库和连接池需要如下 jar

druid-1.1.9.jar mysql-connector-java-5.1.7-bin.jar

以下是测试需要:

hamcrest-core-1.3.jar junit-4.12.jar

编写 jdbc.properties 配置文件

放在 src 文件夹下

放入 src 文件夹下

内容根据自己情况修改 username 改为你的用户名 password 改为你的密码 initialSize 为初始连接池大小 maxActive 为最大可用连接数

username=root password=123456 url=jdbc:mysql://localhost:3306/book?useUnicode=true&characterEncoding=utf8 driverClassName=com.mysql.jdbc.Driver initialSize=5 maxActive=10

编写 JdbcUtils 工具类

``` package com.atguigu.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;

static {
    try {
        Properties properties = new Properties();
        // 读取jdbc.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 = null;

    try {
        conn = dataSource.getConnection();
    } catch (Exception e) {
        e.printStackTrace();
    }
    return conn;
}

/**
 * 关闭连接,放回数据库连接池
 * @param conn
 */
public static void close(Connection conn){
    if(conn != null)
    {
        try {
            conn.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

} ```

JdbcUtils 测试

我们在 test 包下创建 JdbcUtilsTest 测试类

``` package com.atguigu.test;

import com.atguigu.utils.JdbcUtils; import org.junit.Test;

import java.sql.Connection;

public class JdbcUtilsTest {

@Test
public void testJdbcUtils(){
    for(int i=0; i<100; ++i)
    {
        Connection con = JdbcUtils.getConnection();
        System.out.println(con);
        JdbcUtils.close(con);
    }
}

} ```

测试结果

编写 BaseDao

BaseDao 类用来封装数据库的更新,查询操作(包括查询一行,查询多行,查询一个值)

导入 DBUtils 的 jar 包

commons-dbutils-1.3.jar

编写 BaseDao

``` package com.atguigu.dao.impl;

import com.atguigu.utils.JdbcUtils; 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 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 throwables) {
        throwables.printStackTrace();
    } finally {
        JdbcUtils.close(connection);
    }
    return -1;
}

/**
 * 查询返回一个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 throwables) {
        throwables.printStackTrace();
    } finally {
        JdbcUtils.close(con);
    }
    return null;
}

/**
 * 查询返回多个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 throwables) {
        throwables.printStackTrace();
    } finally {
        JdbcUtils.close(con);
    }
    return null;
}

/**
 * 执行返回一行一列的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();
    } finally {
        JdbcUtils.close(conn);
    }
    return null;
}

} ```

编写 UserDao 和测试

UserDao 也是属于 Dao 层,相比于 BaseDao 更加抽象,用来通过 用户名查询是否有这个用户,用户名和密码查询,保存用户信息

UserDao 接口

``` package com.atguigu.dao;

import com.atguigu.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表示错误,其它表示影响的行数
 */
public int saveUser(User user);

} ```

UserDaoImpl 实现类

`` UserDaoImpl 实现类继承 BaseDao 并实现`UserDao package com.atguigu.dao.impl;

import com.atguigu.dao.UserDao; import com.atguigu.pojo.User;

public class UserDaoImpl extends BaseDao implements UserDao {

@Override
public User queryUserByUsername(String username) {
    String sql = "select id,username,password,email from t_user where username = ?";
    return queryForOne(User.class, sql, username);
}

@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);
}

@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());
}

} ```

UserDao 测试

test 下创建 UserDaoTest 测试类

``` package com.atguigu.test;

import com.atguigu.dao.UserDao; import com.atguigu.dao.impl.UserDaoImpl; import com.atguigu.pojo.User; import org.junit.Test;

import static org.junit.Assert.*;

public class UserDaoTest {

UserDao userDao = new UserDaoImpl();

@Test
public void queryUserByUsername() {
    if ( userDao.queryUserByUsername("admin1234") == null ) {
        System.out.println("用户名可用!");
    } else{
        System.out.println("用户名已存在!");
    }
}

@Test
public void queryUserByUsernameAndPassword() {
    if ( userDao.queryUserByUsernameAndPassword("admin", "admin1234") == null ) {
        System.out.println("用户名或密码错误,登录失败!");
    } else{
        System.out.println("登录成功!");
    }
}

@Test
public void saveUser() {
    System.out.println( userDao.saveUser(new User(null, "wzg169", "123456", "wzg169 @qq.com")));
}

} ```

编写 UserService 和 测试

UserService 更加抽象化,具体完成注册,登录,查询用户名是否存在操作,为 Servlet 程序提供服务。

UserService 接口

``` package com.atguigu.service;

import com.atguigu.pojo.User;

public interface UserService { /* * 注册用户 * @param user / public void registUser(User user);

/**
 * 登录
 * @param user
 * @return 返回null是登录失败,返回有值是登录成功
 */
public User login(User user);

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

} ```

UserServiceImpl 实现类

UserServiceImpl 实现 UseService ,底层实际是调用 UserDao 来进行操作

``` package com.atguigu.service.impl;

import com.atguigu.dao.UserDao; import com.atguigu.dao.impl.UserDaoImpl; import com.atguigu.pojo.User; import com.atguigu.service.UserService;

public class UserServiceImpl implements UserService {

private UserDao userDao = new UserDaoImpl();

@Override
public void registUser(User user) {
    userDao.saveUser(user);
}

@Override
public User login(User user) {
    return userDao.queryUserByUsernameAndPassword(user.getUsername(), user.getPassword());
}

@Override
public boolean existsUsername(String username) {
    if (userDao.queryUserByUsername(username) == null){
        return false;
    }

    return true;
}

} ```

UserService 测试

test 下创建 UserServiceTest 测试类

``` package com.atguigu.test;

import com.atguigu.pojo.User; import com.atguigu.service.UserService; import com.atguigu.service.impl.UserServiceImpl; import org.junit.Test;

import static org.junit.Assert.*;

public class UserServiceTest {

UserService userService = new UserServiceImpl();

@Test
public void registUser() {
    userService.registUser(new User(null, "bbj168", "666666", "bbj168@qq.com"));
    userService.registUser(new User(null, "abc168", "666666", "abc 168@qq.com"));
}

@Test
public void login() {
    System.out.println( userService.login(new User(null, "wzg168", "123456", null)));
}

@Test
public void existsUsername() {
    if (userService.existsUsername("wzg1688")){
        System.out.println("用户名已存在!");
    } else{
        System.out.println("用户名可用!");
    }
}

} ```

编写 Web 层

实现用户注册的功能

图解用户注册 用户注册流程

修改 regist.html 和 regist_success.html 页面

  1. 添加 base 标签 通过写 base 标签我们可用固定相对路径跳转的结果,这样可以让我们在写相对路径时更清晰,一般推荐这么做。

base 标签 2. 修改 base 标签对相对路径的影响 我们在添加完 base 标签之后需要对已有的相对路径进行修改,我们可以先重新部署服务器,之后看哪些资源失败了,来看需要修改哪个的相对路径。 3. 修改注册表单的提交地址和请求方式

修改注册表单

##### 编写 RegistServlet 程序

``` package com.atguigu.web;

import com.atguigu.pojo.User; import com.atguigu.service.UserService; import com.atguigu.service.impl.UserServiceImpl;

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

public class RegistServlet extends HttpServlet {

private UserService userService = new UserServiceImpl();

protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException{
    // 1. 获取请求的参数
    String username = req.getParameter("username");
    String password = req.getParameter("password");
    String email = req.getParameter("email");
    String code = req.getParameter("code");

// System.out.println(code); // 2. 验证验证码是否正确 === 写死,要求验证码为:abcde if ("abcde".equalsIgnoreCase(code)){ // 正确 // 3. 检查用户名是否可用 if (userService.existsUsername(username)){ System.out.println("用户名[" + username + "]已存在!"); // 跳回注册页面 req.getRequestDispatcher("/pages/user/regist.html").forward(req, resp); } else{ // 可用,调用Service保存到数据库 userService.registUser(new User(null, username, password, email)); // 跳到注册成功页面 req.getRequestDispatcher("/pages/user/regist_success.html").forward(req, resp); } } else{ System.out.println("验证码[" + code + "]错误"); req.getRequestDispatcher("/pages/user/regist.html").forward(req, resp); }

}

} ```

配置 Servlet 映射

web.xml 中添加如下语句

<servlet> <servlet-name>RegistServlet</servlet-name> <servlet-class>com.atguigu.web.RegistServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>RegistServlet</servlet-name> <url-pattern>/registServlet</url-pattern> </servlet-mapping>

实现用户登录的功能

图解用户登录

用户登录的流程

修改 login.html 页面和 login_success.html 页面

  1. 添加 base 标签
  2. 修改 base 标签对相对路径的影响
  3. 修改注册表单的提交地址和请求方式

修改登录表单

##### 编写 LoginServlet 程序

``` package com.atguigu.web;

import com.atguigu.pojo.User; import com.atguigu.service.UserService; import com.atguigu.service.impl.UserServiceImpl;

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

public class LoginServlet extends HttpServlet {

private UserService userService = new UserServiceImpl();

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

    // 1. 获取请求的参数
    String username = req.getParameter("username");
    String password = req.getParameter("password");
    // 2. userService.login()登录处理业务
    User loginUser = userService.login(new User( null, username, password, null));
    // 如果等于null,说明登录失败!
    if (loginUser == null) {
        // 跳回登录页面
        req.getRequestDispatcher("/pages/user/login.html").forward(req, resp);
    } else {
        // 登录 成功
        // 跳到成功页面login_success.html
        req.getRequestDispatcher("/pages/user/login_success.html").forward(req, resp);
    }
}

} ```

配置 Servlet 映射

web.xml 中添加如下语句

<servlet> <servlet-name>LoginServlet</servlet-name> <servlet-class>com.atguigu.web.LoginServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>LoginServlet</servlet-name> <url-pattern>/loginServlet</url-pattern> </servlet-mapping>

表单回显

之前已经基本做好用户的注册和登录功能,但用户注册和登录还缺少错误信息的提示,比如用户名已存在会在页面显示用户名已存在。这次我们就来完成表单回显的功能。

修改所有.html 为.jsp

只有把静态页面改为动态页面,才能完成表单回显的功能,因此第一步我们先将所有 .html 页面 改为 .jsp 页面。 将 .html 页面改为 .jsp 页面只需要如下两个步骤。

  1. 在头部添加如下语句

<%@ page contentType="text/html;charset=UTF-8" language="java" %>

.html

后缀改为

.jsp

后缀,

比如,user 目录下修改完为如下

HTML 改为 JSP

抽取 JSP 公共内容

我们可以将 jsp 页面中的公共内容提取出来,这样以后在维护或者修改的时候,如果需要对公共部分进行修改,那么只需要修改一份代码即可。 我们先在 pages 目录下创建 common 文件夹,用于存放公共部分代码

登录成功菜单部分

登录成功菜单

我们在登录成功之后的页面,都会有如上图所示,位于页面右上角的菜单部分,我们可以将其提取出来,放入 common 文件夹下的 login_success_menu.jsp 中。

``` <%@ page contentType="text/html;charset=UTF-8" language="java" %>

欢迎 韩总 光临尚硅谷书城 我的订单 注销 返回

```

之后在公共位置改为如下代码

<%-- 静态包含登录成功之后的菜单 --%> <%@ include file="/pages/common/login_success_menu.jsp"%>

头部信息

我们会在 jsp 页面头部写 base 标签,导入样式及 jQuery 的包,因此也可以提取出如下公共部分放入 common 文件夹下 head.jsp 中。

``` --%> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <% String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + request.getContextPath() + "/"; %>

<base href="<%=basePath%>">

```

需要注意的是,我们自己每次试验都是通过 localhost 访问服务器的,但是正常情况下,是用户使用客户端访问服务器的 ip ,并且服务器的 ip 是可能会动态变化的,所以我们写 base 标签的时候,必须动态获取服务器的 ip 地址。

``` <%@ page contentType="text/html;charset=UTF-8" language="java" %> <% String basePath = request.getScheme() //协议名称 + "://" + request.getServerName() //服务器ip + ":" + request.getServerPort() //服务器端口 + request.getContextPath() //工程路径 + "/"; %>

<base href="<%=basePath%>">

```

之后在公共位置改为如下代码

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

脚部信息

脚部信息 我们在所有页面下面都会加上如上图所示的脚部信息,因此我们将其提取出来放入 common 文件夹下的 footer.jsp 中。

``` <%@ page contentType="text/html;charset=UTF-8" language="java" %>

尚硅谷书城.Copyright ©2015

```

之后在公共位置改为如下代码

<%-- include包含脚部信息 --%> <%@ include file="/pages/common/footer.jsp"%>

后台管理菜单

后台管理菜单 后台管理页面都会有如上图所示的菜单项,我们也可以将其提取出来放入 common 文件夹下的 manager_menu.jsp 中。

``` <%@ page contentType="text/html;charset=UTF-8" language="java" %>

```

之后在公共位置改为如下代码

<%-- 静态包含manager管理模块的菜单 --%> <%@ include file="/pages/common/manager_menu.jsp"%>

表单提交失败的错误回显

具体实现思路

  1. Servlet 程序中将错误回显信息放入 request 域中
  2. jsp 页面中输出回显信息

修改 Servlet 程序

修改 LoginServlet RegistServlet 程序如下

修改 LoginServlet 程序

修改 RegistServlet 程序

修改 JSP 页面

修改 login.jsp regist.jsp 如下

修改 login,jsp

修改 regist,jsp

代码优化

前三部分我们已经完成了书城项目的登录与注册功能,第四部分我们对之前的代码进行优化。

合并 Servlet

在实际的开发中,一个模块,一般只使用一个 Servlet 程序,用户的注册与登录都属于用户模块,因此只需要一个 Servlet 程序即可,所以我们将 LoginServlet RegistSerlet 程序合并为一个 UserServlet 程序。 那么一个请求过来,如何知道他是注册还是登录呢?

这时我们就要用到表单项的隐藏域来解决。

登录表单隐藏域

注册表单隐藏域 这样,我们在用 Servlet 程序接收请求时,就能根据 name value 项来判断其是注册还是登录。

反射优化

用户模块不止有登录和注册功能,还会有注销,修改密码等其它功能,如果这时都放在一个 Servlet 里,那么代码必定有很多 if...else... ,这样代码就会显得很冗杂。我们可以使用反射优化 if...else...

反射优化 这样两行代码就能代替繁琐的 if...else,,,

抽取 BaseServlet 程序

抽取 BaseServlet 因为我们不止一个模块,除了用户模块,还会有图书模块等,这样其反射优化都是一样的,那么我们可以抽取出父类 BaseServlet 程序,那么其它模块就可以继承这个模块,达到复用代码的目的。 BaseServlet 程序:

``` package com.atguigu.web;

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 abstract class BaseServlet extends HttpServlet {

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    String action = req.getParameter("action");

    try {
        // 获取action业务鉴别字符串,获得相应的业务 方法反射对象
        Method method = this.getClass().getDeclaredMethod(action, HttpServletRequest.class, HttpServletResponse.class);
        // 调用目标业务 方法
        method.invoke(this, req, resp);
    } catch (Exception e) {
        e.printStackTrace();
    }

}

} ```

数据的封装和抽取 BeanUtils 的使用

我们一般会将数据封装进 JavaBean 里,但如果通过调用 Set 方法会使代码很冗余,这时我们可以使用第三方工具类 BeanUtils ,它可以用于把 Map 中的值注入到 JavaBean 中。

  1. 导入需要的 jar 包: commons-beanutils-1.8.0.jar commons-logging-1.1.1.jar
  2. 编写 WebUtils 工具类使用 WebUtils 工具类:

``` package com.atguigu.utils;

import org.apache.commons.beanutils.BeanUtils;

import java.lang.reflect.InvocationTargetException; import java.util.Map;

public class WebUtils { / * 把Map中的值注入到JavaBean中 * @param value * @param bean * @param * @return */ public static T copyParamToBean(Map value , T bean ){ try { System.out.println("注入之前:" + bean); / * 把所有请求的参数都注入到bean对象中 */ BeanUtils.populate(bean, value); System.out.println("注入之后:" + bean); } catch (Exception e) { e.printStackTrace(); } return bean; } } ```

  1. 在程序中加入如下代码即可调用方法将请求参数注入到

JavaBean

调用方法

使用 EL 表达式修改表单回显

我们可以使用 EL 表达式来修改表单回显,这样能使代码更简洁。

修改 login,jsp

修改 regist.jsp

图书模块

前四个阶段我们完成了用户的注册与登录功能,并对代码进行了优化,第五阶段我们完成书城项目的图书模块,属于后台管理中的图书管理功能,主要包括图书的添加,删除,修改以及显示。

数据库表

首先编写图书模块的数据库表,使用如下 Sql 语句创建 t_book 表,并插入初始化测试数据。

`` create table t_book( id int(11) primary key auto_increment, ## 主键 name varchar(50) not null, ## 书名 author varchar(50) not null, ## 作者 price decimal(11,2) not null, ## 价格 sales int(11) not null, ## 销量 stock int(11) not null, ## 库存 img_path` varchar(200) not null ## 书的图片路径 );

插入初始化测试数据

insert into t_book( id , name , author , price , sales , stock , img_path ) values(null , 'java从入门到放弃' , '国哥' , 80 , 9999 , 9 , 'static/img/default.jpg');

insert into t_book( id , name , author , price , sales , stock , img_path ) values(null , '数据结构与算法' , '严敏君' , 78.5 , 6 , 13 , 'static/img/default.jpg');

insert into t_book( id , name , author , price , sales , stock , img_path ) values(null , '怎样拐跑别人的媳妇' , '龙伍' , 68, 99999 , 52 , 'static/img/default.jpg');

insert into t_book( id , name , author , price , sales , stock , img_path ) values(null , '木虚肉盖饭' , '小胖' , 16, 1000 , 50 , 'static/img/default.jpg');

insert into t_book( id , name , author , price , sales , stock , img_path ) values(null , 'C++编程思想' , '刚哥' , 45.5 , 14 , 95 , 'static/img/default.jpg');

insert into t_book( id , name , author , price , sales , stock , img_path ) values(null , '蛋炒饭' , '周星星' , 9.9, 12 , 53 , 'static/img/default.jpg');

insert into t_book( id , name , author , price , sales , stock , img_path ) values(null , '赌神' , '龙伍' , 66.5, 125 , 535 , 'static/img/default.jpg');

insert into t_book( id , name , author , price , sales , stock , img_path ) values(null , 'Java编程思想' , '阳哥' , 99.5 , 47 , 36 , 'static/img/default.jpg');

insert into t_book( id , name , author , price , sales , stock , img_path ) values(null , 'JavaScript从入门到精通' , '婷姐' , 9.9 , 85 , 95 , 'static/img/default.jpg');

insert into t_book( id , name , author , price , sales , stock , img_path ) values(null , 'cocos2d-x游戏编程入门' , '国哥' , 49, 52 , 62 , 'static/img/default.jpg');

insert into t_book( id , name , author , price , sales , stock , img_path ) values(null , 'C语言程序设计' , '谭浩强' , 28 , 52 , 74 , 'static/img/default.jpg');

insert into t_book( id , name , author , price , sales , stock , img_path ) values(null , 'Lua语言程序设计' , '雷丰阳' , 51.5 , 48 , 82 , 'static/img/default.jpg');

insert into t_book( id , name , author , price , sales , stock , img_path ) values(null , '西游记' , '罗贯中' , 12, 19 , 9999 , 'static/img/default.jpg');

insert into t_book( id , name , author , price , sales , stock , img_path ) values(null , '水浒传' , '华仔' , 33.05 , 22 , 88 , 'static/img/default.jpg');

insert into t_book( id , name , author , price , sales , stock , img_path ) values(null , '操作系统原理' , '刘优' , 133.05 , 122 , 188 , 'static/img/default.jpg');

insert into t_book( id , name , author , price , sales , stock , img_path ) values(null , '数据结构 java版' , '封大神' , 173.15 , 21 , 81 , 'static/img/default.jpg');

insert into t_book( id , name , author , price , sales , stock , img_path ) values(null , 'UNIX高级环境编程' , '乐天' , 99.15 , 210 , 810 , 'static/img/default.jpg');

insert into t_book( id , name , author , price , sales , stock , img_path ) values(null , 'javaScript高级编程' , '国哥' , 69.15 , 210 , 810 , 'static/img/default.jpg');

insert into t_book( id , name , author , price , sales , stock , img_path ) values(null , '大话设计模式' , '国哥' , 89.15 , 20 , 10 , 'static/img/default.jpg');

insert into t_book( id , name , author , price , sales , stock , img_path ) values(null , '人月神话' , '刚哥' , 88.15 , 20 , 80 , 'static/img/default.jpg'); ```

编写图书模块的 JavaBean

我们在 pojo 目录下创建 Book 类,它的属性如下,并设置 get , set ,有参和无参构造方法。

private Integer id; private String name; private String author; private BigDecimal price; private Integer sales; private Integer stock; private String imgPath = "static/img/default.jpg";

需要注意的是,对于图片路径 imgPath ,我们初始化了一个默认图片路径,对于之后修改,如果传入图片路径为 null 或空串,我们就不对 imgPath 进行修改,因此我们需要对默认构造方法以及 setImgPath 方法做出如下修改。

// 要求给定的图书封面图片路径不能为空 if (imgPath != null && !"".equals(imgPath)) { this.imgPath = imgPath; }

编写图书模块的 Dao 和测试 Dao

Dao 接口

``` package com.atguigu.dao;

import com.atguigu.pojo.Book;

import java.util.List;

public interface BookDao {

public int addBook(Book book);

public int deleteBookById(Integer id);

public int updateBook(Book book);

public Book queryBookById(Integer id);

public List<Book> queryBooks();

} ```

BookDaoImpl 实现类

``` package com.atguigu.dao.impl;

import com.atguigu.dao.BookDao; import com.atguigu.pojo.Book;

import java.util.List;

public class BookDaoImpl extends BaseDao implements BookDao {

@Override
public int addBook(Book book) {
    String sql = "insert into t_book(`name`,`author`,`price`,`sales`,`stock`,`img_path`) values(?,?,?,?,?,?)";
    return update(sql,book.getName(),book.getAuthor(),book.getPrice(),book.getSales(),book.getStock(),book.getImgPath());
}

@Override
public int deleteBookById(Integer id) {
    String sql = "delete from t_book where id = ?";
    return update(sql, id);
}

@Override
public int updateBook(Book book) {
    String sql = "update t_book set `name`=?,`author`=?,`price`=?,`sales`=?,`stock`=?,`img_path`=? where id=?";
    return update(sql,book.getName(),book.getAuthor(),book.getPrice(),book.getSales(),book.getStock(),book.getImgPath(),book.getId());
}

@Override
public Book queryBookById(Integer id) {
    String sql = "select `id`,`name`,`author`,`price`,`sales`,`stock`,`img_path` imgPath from t_book where id = ?";
    return queryForOne(Book.class, sql, id);
}

@Override
public List<Book> queryBooks() {
    String sql = "select `id`,`name`,`author`,`price`,`sales`,`stock`,`img_path` imgPath from t_book";
    return queryForList(Book.class, sql);
}

} ```

BookDao 的测试: 我们在 test 目录下创建 BookDaoTest 类进行测试。

``` package com.atguigu.test;

import com.atguigu.dao.BookDao; import com.atguigu.dao.impl.BookDaoImpl; import com.atguigu.pojo.Book; import org.junit.Test;

import java.math.BigDecimal;

import static org.junit.Assert.*;

public class BookDaoTest {

private BookDao bookDao = new BookDaoImpl();

@Test
public void addBook() {
    bookDao.addBook(new Book(null, "博文为太帅了", "博文", new BigDecimal(199999), 1100000, 0, null));
}

@Test
public void deleteBookById() {
    bookDao.deleteBookById(21);
}

@Test
public void updateBook() {
    bookDao.updateBook(new Book(22, "大家都很帅", "佳庆",  new BigDecimal(199999), 1100000, 0, null));
}

@Test
public void queryBookById() {
    System.out.println( bookDao.queryBookById(21) );
}

@Test
public void queryBooks() {
    for (Book queryBook : bookDao.queryBooks()){
        System.out.println(queryBook);
    }
}

} ```

编写图书模块的 Service 和测试 Service

BookService 接口

``` import com.atguigu.pojo.Book;

import java.util.List;

public interface BookService {

public void addBook(Book book);

public void deleteBookById(Integer id);

public void updateBook(Book book);

public Book queryBookById(Integer id);

public List<Book> queryBooks();

} ```

BookServiceImpl 实现类

``` package com.atguigu.service.impl;

import com.atguigu.dao.BookDao; import com.atguigu.dao.impl.BookDaoImpl; import com.atguigu.pojo.Book; import com.atguigu.service.BookService;

import java.util.List;

public class BookServiceImpl implements BookService { private BookDao bookDao = new BookDaoImpl(); @Override public void addBook(Book book) { bookDao.addBook(book); }

@Override
public void deleteBookById(Integer id) {
    bookDao.deleteBookById(id);
}

@Override
public void updateBook(Book book) {
    bookDao.updateBook(book);
}

@Override
public Book queryBookById(Integer id) {
    return bookDao.queryBookById(id);
}

@Override
public List<Book> queryBooks() {
    return bookDao.queryBooks();
}

} ```

BookService 的测试: 在 test 目录下创建 BookServiceImplTest 测试类

``` package com.atguigu.test;

import com.atguigu.pojo.Book; import com.atguigu.service.BookService; import com.atguigu.service.impl.BookServiceImpl; import org.junit.Test;

import java.math.BigDecimal;

import static org.junit.Assert.*;

public class BookServiceImplTest {

private BookService bookService = new BookServiceImpl();
@Test
public void addBook() {
    bookService.addBook(new Book(null, "博文真是太帅了", "博文", new BigDecimal(1555555), 200000, 0, null));
}

@Test
public void deleteBookById() {
    bookService.deleteBookById(22);
}

@Test
public void updateBook() {
    bookService.updateBook(new Book(21, "博文帅惨了", "博文", new BigDecimal(21313131), 100005, 0, null));
}

@Test
public void queryBookById() {
    System.out.println(bookService.queryBookById(21));
}

@Test
public void queryBooks() {
    for(Book bookquery : bookService.queryBooks())
        System.out.println(bookquery);
}

} ```

编写图书模块的 Web 层,页面联调测试

图书列表功能的实现

  1. 图解列表功能流程

列表功能流程 2. BookServlet 程序中添加 list 方法 在 web 目录下创建 BookServlet 类,并让其继承 BaseServlet ,并实现 list 方法,需要注意的是,我们直接访问是通过 get 访问的,所以我们要实现 doGet 方法,让其调用 doPost 方法。

``` public class BookServlet extends BaseServlet{

private BookService bookService = new BookServiceImpl();

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    doPost(req, resp);
}

protected void list(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    //1 通过BookService查询全部图书
    List<Book> books = bookService.queryBooks();
    //2 把全部图书保存到Request域中
    req.setAttribute("books", books);
    //3 请求转发到/pages/manager/book_manager.jsp页面
    req.getRequestDispatcher("/pages/manager/book_manager.jsp").forward(req, resp);
}

} ```

然后在 web.xml 中添加映射

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

我们将其 url 放在 /manager 下是因为,图书管理属于后台管理,放在 /manager 下用于区别其是后台功能。

前后台区分

\3. 修改图书管理请求地址 我们在 manager_menu.jsp 中修改图书管理请求地址 修改图书管理请求地址 \4. 修改 pages/manager/book_manager.jsp 页面的数据遍历输出 导入如下 jar

taglibs-standard-impl-1.2.1.jar taglibs-standard-spec-1.2.1.jar

修改 book_manager.jsp ,利用 JSTL 标签库遍历输出图书信息。

利用 JSTL 标签库遍历输出图书信息

添加图书功能的实现

  1. 添加图书流程细节

添加图书流程 2. 问题说明 如果像之前一样,通过 Servlet 的程序转发请求,那么前后算是同一个请求,而当用户提交完请求,浏览器会记录下最后一次请求的全部信息。当用户按下功能键 F5 ,就会发起浏览器记录的最后一次请求。那么就会重复添加图书项,所以这里要使用重定向,这样前后就是两次请求了,就算按 F5 也是展示图书列表。 3. BookServlet 程序中添加 add 方法

protected void add(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //1 获取请求的参数==封装为Book对象 Book book = WebUtils.copyParamToBean(req.getParameterMap(), new Book()); //2 调用BookService.addBook()保存图书 bookService.addBook(book); //3 跳到图书列表页面 resp.sendRedirect(req.getContextPath() + "/manager/bookServlet?action=list"); }

  1. 修改

book_edit.jsp

页面

修改 book_edit.jsp

这里如果使用

post

方法提交会中文乱码,可以通过修改

post

的编码解决。

删除图书功能的实现

  1. 图解删除流程

删除流程 2. BookServlet 中添加 delete 方法 删除功能需要 id 项,我们通过 getParameter 方法获得的 id 是字符串类型,需要将其转换为 Integer 型。所以我们给 WebUtils 工具类添加转换 Interger 类型。

/** * 将字符串转换成为 int 类型的数据 * @param strInt * @param defaultValue * @return */ public static int parseInt(String strInt,int defaultValue) { try { return Integer.parseInt(strInt); } catch (Exception e) { e.printStackTrace(); } return defaultValue; }

然后在 BookServlet 中添加 delete 方法

protected void delete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 1、获取请求的参数 id,图书编程 int id = WebUtils.parseInt(req.getParameter("id"), 0); // 2、调用 bookService.deleteBookById();删除图书 bookService.deleteBookById(id); // 3、重定向回图书列表管理页面 // /book/manager/bookServlet?action=list resp.sendRedirect(req.getContextPath() + "/manager/bookServlet?action=list"); }

  1. 修改删除的连接地址

修改删除的连接地址 2. 给删除添加确认提示操作 删除属于危险项,所以我们需要添加确认操作,给删除的 a 标签绑定单击事件。

```

```

修改图书功能的实现

  1. 图解修改图书细节

修改图书 2. 更新修改的请求地址

更新修改的请求地址 3. BookServlet 程序中添加 getBook 方法

protected void getBook(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //1 获取请求的参数图书编号 int id = WebUtils.parseInt(req.getParameter("id"), 0); //2 调用 bookService.queryBookById 查询图书 Book book = bookService.queryBookById(id); //3 保存到图书到 Request 域中 req.setAttribute("book", book) ; //4 请求转发到。pages/manager/book_edit.jsp 页面 req.getRequestDispatcher("/pages/manager/book_edit.jsp").forward(req,resp); }

book_edit.jsp

页面中显示修改的数据

显示修改的数据 2. 在 BookServlet 程序中添加 update 方法

protected void update(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 1、获取请求的参数==封装成为 Book 对象 Book book = WebUtils.copyParamToBean(req.getParameterMap(),new Book()); // 2、调用 BookService.updateBook( book );修改图书 bookService.updateBook(book); // 3、重定向回图书列表管理页面 // 地址:/工程名/manager/bookServlet?action=list resp.sendRedirect(req.getContextPath() + "/manager/bookServlet?action=list"); }

  1. 解决

book_edit.jsp

页面,既要实现添加,又要实现修改操作。

实现添加与修改

图书分页

前五个部分我们已经完成了用户的注册与登录模块,以及后台的图书管理,第六部分我们完成图书的分页部分,分页的原因就是一页显示全部信息太繁杂了,所以需要需要分页来解决这个问题。

分页模块的分析

分页模块的分析

分页模型 Page 的抽取

由分页的视图分析出分页的对象模型 Page 类有如下属性

``` /* * Page是分页的模型对象 * @param 是具体的模块的javaBean类 / public class Page {

public static final Integer PAGE_SIZE = 4;

// 当前页码
private Integer pageNo;
// 总页码
private Integer pageTotal;
// 当前页显示数量
private Integer pageSize = PAGE_SIZE;
// 总记录数
private Integer pageTotalCount;
// 当前页数据
private List<Book> items;

```

分页的初步实现

BookDao 代码

``` @Override public Integer queryForPageTotalCount() { String sql = "select count(*) from t_book"; Number count = (Number) queryForSingleValue(sql); return count.intValue(); }

@Override public List queryForPageItems(int begin, int pageSize) { String sql = "select id , name , author , price , sales , stock , img_path imgPath" + " from t_book limit ?,?"; return queryForList(Book.class, sql, begin, pageSize); } ```

BookService 代码

``` @Override public Page page(int pageNo, int pageSize) { Page page = new Page (); // 设置当前页码 page.setPageNo(pageNo); // 设置每页显示的数量 page.setPageSize(pageSize); // 求总记录数 Integer pageTotalCount = bookDao.queryForPageTotalCount(); // 设置总记录数 page.setPageTotalCount(pageTotalCount); // 求总页码 Integer pageTotal = pageTotalCount / pageSize; if (pageTotalCount % pageSize > 0){ pageTotal+=1; } // 设置总页码 page.setPageTotal(pageTotal); // 求当前页数据的开始索引 int begin = (page.getPageNo() - 1) * pageSize; // 求当前页数据 List items = bookDao.queryForPageItems(begin, pageSize); // 设置当前页数据 page.setItems(items);

return page;

} ```

BookServlet 程序的代码

protected void page(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //1 获取请求的参数 pageNo 和 pageSize int pageNo = WebUtils.parseInt(req.getParameter("pageNo"), 1); int pageSize = WebUtils.parseInt(req.getParameter("pageSize"), Page.PAGE_SIZE); //2 调用BookService.page(pageNo, pageSize): Page对象 Page<Book> page = bookService.page(pageNo, pageSize); //3 保存 Page 对象到 Request 域中 req.setAttribute("page", page); //4 请求转发到pages/manager/book_manager.jsp页面 req.getRequestDispatcher("/pages/manager/book_manager.jsp").forward(req,resp); }

manager_menu.jsp 中图书管理请求地址的修改

修改图书管理请求地址 book_manager.jsp 修改 book_manager.jsp 修改(一) book_manager.jsp 修改(二)

首页,上一页,下一页,末页实现

修改 book_manager.jsp

修改 book_manager.jsp

实现跳到指定页数

修改 book_manager.jsp ,通过绑定单击事件实现跳到指定页数

修改 book_manager.jsp

Page 对象的修改,完成数据边界的有效检查,使其不会跳到没有的页数

public void setPageNo(Integer pageNo) { /* 数据边界的有效检查 */ if (pageNo < 1) { pageNo = 1; } if (pageNo > pageTotal) { pageNo = pageTotal; } this.pageNo = pageNo; }

与之对应,要修改 BookService page 方法,因为设置当前页码时,需要 pageTotal 来进行数据边界的有效检查,所以设置当前页码要放在设置总页码之后。

修改 BookService 中 page 方法

页码的显示

一般来说,显示页码的时候,不仅会显示当前页的页码,还会显示前几页的页码,以及后几页的页码,然后点击这些页码就可以跳转到指定页。 这里实现一次显示 5 个页码,下面分情况讨论:

情况1: 如果总页码小于等于5,页码的范围是:1~总页码 1页 1 2页 1,2 3页 1,2,3 4页 1,2,3,4 5页 1,2,3,4,5 情况2: 总页码大于5的情况。假设一共10页 小情况1:当前页码为前面2个,页码的范围是:1~5 [1],2,3,4,5 1,[2],3,4,5 小情况2:当前页码为最后2个,页码的范围是:总页码-4~总页码 6,7,8,[9],10 6,7,8,9,[10] 小情况3:其它情况,页码的范围是:当前页码-2~当前页码+2 2,3,[4],5,6 3,4,[5],6,7

按照上面的情况修改 book_manager.jsp

<c:choose> <%--情况1:如果总页码小于等于5--%> <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:当前页码为前面2个--%> <c:when test="${requestScope.page.pageNo < 3}"> <c:set var="begin" value="1"/> <c:set var="end" value="5"/> </c:when> <%--小情况2:当前页码为最后2个--%> <c:when test="${requestScope.page.pageNo > requestScope.page.pageTotal-2}"> <c:set var="begin" value="${requestScope.page.pageTotal-4}"/> <c:set var="end" value="${requestScope.page.pageTotal}"/> </c:when> <%--小情况3:其他情况--%> <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="manager/bookServlet?action=page&pageNo=${i}">${i}</a> </c:if> </c:forEach>

修改分页后,增加,删除,修改图书信息的回显页面

以修改图书为例 1.在修改的请求地址上追加当前页码参数

修改 book_manager,jsp

2.在 book_edit.jsp 页面中使用隐藏域记录下 pageNo 参数 使用隐藏域记录下 pageNo 参数

3.在服务器重定向时,获取当前页码追加上进行跳转

追加当前页码进行跳转

首页 index.jsp 的跳转

因为首页也需要分页,所以我们访问首页的时候需要让其通过 ClientBookServlet 程序让其跳转到 web目录/pages/client目录的/index.jsp

首页 index.jsp 的跳转

创建 ClientBookServlet 程序

``` package com.atguigu.web;

import com.atguigu.pojo.Book; import com.atguigu.pojo.Page; import com.atguigu.service.BookService; import com.atguigu.service.impl.BookServiceImpl; import com.atguigu.utils.WebUtils;

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

public class ClientBookServlet extends BaseServlet{

private BookService bookService = new BookServiceImpl();

protected void page(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    //System.out.println("经过了前台程序");
    //1 获取请求的参数 pageNo 和 pageSize
    int pageNo = WebUtils.parseInt(req.getParameter("pageNo"), 1);
    int pageSize = WebUtils.parseInt(req.getParameter("pageSize"), Page.PAGE_SIZE);
    //2 调用BookService.page(pageNo, pageSize): Page对象
    Page<Book> page = bookService.page(pageNo, pageSize);
    page.setUrl("client/bookServlet?action=page");
    //3 保存 Page 对象到 Request 域中
    req.setAttribute("page", page);
    //4 请求转发到pages/client/index.jsp页面
    req.getRequestDispatcher("/pages/client/index.jsp").forward(req,resp);
}

} ```

配置 web.xml ,增加 ClientBookServlet 的映射

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

创建 client/index.jsp

``` <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ page contentType="text/html;charset=UTF-8" language="java" %>

书城首页
价格: 元 -
您的购物车中有3件商品
您刚刚将 时间简史 加入到了购物车中
书名: ${book.name}
作者: ${book.author}
价格: ¥${book.price}
销量: ${book.sales}
库存: ${book.stock}
尚硅谷书城.Copyright ©2015

```

修改 web/index.jsp 为请求转发到 Servlet

<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%--只负责请求转发--%> <jsp:forward page="/client/bookServlet?action=page"></jsp:forward>

分页条的抽取

前台页面也需要分页条,因为对于分页条而言,只是请求的 url 不同,我们可以给 page 添加 url 属性,再把分页条抽取出来,就可以简单的调用分页条。

  1. page 对象中添加 url 属性

page 对象中添加 url 属性 2. 在 Servlet 程序中的 page 分页方法中设置 url 的分页请求地址

设置 url 的分页请求地址 3. 修改分页条中请求地址为 url 变量输出,并抽取一个单独的 page_nav.jsp 页面

``` <%@ page contentType="text/html;charset=UTF-8" language="java" %>

```

4.静态包含

page_nav.jsp

静态包含 page_nav.jsp

首页价格搜索

首页价格搜索

BookDao 程序添加如下方法

``` @Override public Integer queryForPageTotalCountByPrice(int min, int max) { String sql = "select count(*) from t_book where price between ? and ?"; Number count = (Number) queryForSingleValue(sql, min, max); return count.intValue(); }

@Override public List queryForPageItemsByPrice(int begin, int pageSize, int min, int max) { String sql = "select id , name , author , price , sales , stock , img_path imgPath" + " from t_book where price between ? and ? limit ?,?"; return queryForList(Book.class, sql, min, max, begin, pageSize); } ```

BookService 程序添加如下方法

``` @Override public Page pageByPrice(int pageNo, int pageSize, int min, int max) { Page page = new Page (); // 设置每页显示的数量 page.setPageSize(pageSize); // 求总记录数 Integer pageTotalCount = bookDao.queryForPageTotalCountByPrice(min,max); // 设置总记录数 page.setPageTotalCount(pageTotalCount); // 求总页码 Integer pageTotal = pageTotalCount / pageSize; if (pageTotalCount % pageSize > 0){ pageTotal+=1; } // 设置总页码 page.setPageTotal(pageTotal); // 设置当前页码 page.setPageNo(pageNo); // 求当前页数据的开始索引 int begin = (page.getPageNo() - 1) * pageSize; // 求当前页数据 List items = bookDao.queryForPageItemsByPrice(begin, pageSize, min, max); // 设置当前页数据 page.setItems(items);

return page;

} ```

ClientBookServlet 程序添加如下方法,需要注意的是设置 url 需要加上 min max 参数,这样之后点击下一页之类的,才是按价格查询的。

protected void pageByPrice(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //System.out.println("经过了前台程序"); //1 获取请求的参数 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); //2 调用BookService.pageByPrice(pageNo, pageSize, min, max): Page对象 Page<Book> page = bookService.pageByPrice(pageNo, pageSize, min, max); 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()); //3 保存 Page 对象到 Request 域中 req.setAttribute("page", page); //4 请求转发到pages/client/index.jsp页面 req.getRequestDispatcher("/pages/client/index.jsp").forward(req,resp); }

用户功能完善

之前我们做出了用户功能的注册与登录功能,这次我们将用户功能完善,包括用户登录显示用户名,注销用户,以及验证码的使用

登录显示用户名

一般来说,我们登录之后会显示用户名,我们之前是写死的,这次改成动态的。

  1. UserServlet 程序中保存用户登录的信息。因为我们登录之后的所有网页都是要显示用户名的,所以不能保存到 request 域,而是要保存到 session 域。
  2. 修改

login_success_menu.jsp

,,因为我们之前把登录成功之后的菜单信息提取出一个

jsp

文件,所以只需要修改这个公共的部分。

修改 login_success_menu.jsp 3. 修改首页

index.jsp

页面的菜单,使其在登录之后也能显示用户信息。

修改首页 index.jsp

登出注销用户

  1. UserServlet 程序中添加 logout 方法

/** * 注销 * @param req * @param resp * @throws ServletException * @throws IOException */ protected void logout(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 1. 销毁Session中用户登录的信息(或者销毁Session) req.getSession().invalidate(); // 2. 重定向到首页(或登录页面)。 resp.sendRedirect(req.getContextPath()); }

  1. 修改注销的菜单地址

修改注销菜单的地址

使用验证码的原因及原理

之前验证码一直是写死的,这次我们实现动态的验证码。使用验证码的原因之一是为了防止用户重复提交表单而产生错误。 表单重复提交有以下三种情况。

  1. 提交完表单。服务器使用请求转来进行页面跳转。这个时候,用户按下功能键 F5,就会发起最后一次的请求。造成表单重复提交问题。解决方法:这个可以使用重定向来进行跳转
  2. 用户正常提交服务器,但是由于网络延迟等原因,迟迟未收到服务器的响应,这个时候,用户以为提交失败,就会着急,然后多点了几次提交操作,也会造成表单重复提交。
  3. 用户正常提交服务器。服务器也没有延迟,但是提交完成后,用户回退浏览器。重新提交。也会造成表单重复提交。

最后两种情况无法通过重定向解决,所以我们可以使用验证码解决。

验证码解决表单重复提交的底层原理

谷歌 kaptcha 图片验证码的使用

我们使用现有的 jar 包来完成我们的验证码功能。

  1. 导入谷歌验证码的 jar

kaptcha-2.3.2.jar

  1. web.xml 中去配置用于生成验证码的 Servlet 程序,因为 kaptcha 提供的也是 Servlet 程序,所以要在 web.xml 中配置完成才能使用。

<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>

  1. 在表单中使用 img 标签去显示验证码图片并使用它

使用验证码图片 2. 在服务器获取谷歌生成的验证码和客户端发送过来的验证码比较使用

服务器比较验证码 3. 切换验证码,用户有时候可能看不清验证码,所以我们还要提供点击图片替换的功能。

// 给验证码图片绑定单击事件 $("#code_img").click(function () { // 在事件响应的function函数中有一个this对象,这个this对象,是当前正在响应事件的dom对象 // src 属性表示验证码img标签的图片路径。它可读可写 // 添加随机变量,防止调用缓存 this.src = "${basePath}kaptcha.jpg?d=" + new Date(); });

购物车模块

上一部分,我们对用户功能进行了完善,这一部分我们完成购物车模块,主要包括添加商品到购物车,删除商品,清空购物车。

购物车模块分析

购物车模块分析

我们使用 Session 版本实现购物车,这样就不需要 Dao 层和 Service 层了。

购物车模型编写

  1. 创建 CartItem 类定义购物车中的商品项,其有以下属性

private Integer id; //编号 private String name; //名称 private Integer count; //数量 private BigDecimal price; //单价 private BigDecimal totalprice; //总价

  1. 创建 Cart 类定义购物车

``` package com.atguigu.pojo;

import java.math.BigDecimal; import java.util.LinkedHashMap; import java.util.Map;

/* * 购物车对象 / public class Cart {

/**
 * key 是商品编号
 * value 是商品信息
 */
private Map<Integer, CartItem> items = new LinkedHashMap<Integer, CartItem>();

public void addItem(CartItem cartItem) {
    // 先查看购物车中是否已经添加过此商品,如果已添加,则数量累加,总金额更新,如果没有添加过,直接放到集合中即可
    CartItem item = items.get(cartItem.getId());

    if( item == null )
    {
        // 之前没添加过此商品
        items.put(cartItem.getId(), cartItem);
    } else {
        // 已经添加过的情况
        item.setCount( item.getCount() + 1 ); //数量增加
        item.setTotalprice( item.getPrice().multiply(new BigDecimal( item.getCount() ))); // 更新总金额
    }
}

/**
 * 删除商品项
 * @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) {
    // 先查看购物车是否有此商品。如果有,修改商品数量,更新总金额
    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;
}

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 +
            '}';
}

} ```

2.购物车的测试,创建测试类 CartTest

``` package com.atguigu.test;

import com.atguigu.pojo.Cart; import com.atguigu.pojo.CartItem; import org.junit.Test;

import java.math.BigDecimal;

import static org.junit.Assert.*;

public class CartTest {

@Test
public void addItem() {
    Cart cart = new Cart();
    cart.addItem(new CartItem(1, "java从入门到精通", 1, new BigDecimal(1000),new BigDecimal(1000)));
    cart.addItem(new CartItem(1, "java从入门到精通", 1, new BigDecimal(1000),new BigDecimal(1000)));
    cart.addItem(new CartItem(2, "数据结构与算法", 1, new BigDecimal(100),new BigDecimal(100)));
    System.out.println(cart);
}

@Test
public void deleteItem() {
    Cart cart = new Cart();
    cart.addItem(new CartItem(1, "java从入门到精通", 1, new BigDecimal(1000),new BigDecimal(1000)));
    cart.addItem(new CartItem(1, "java从入门到精通", 1, new BigDecimal(1000),new BigDecimal(1000)));
    cart.addItem(new CartItem(2, "数据结构与算法", 1, new BigDecimal(100),new BigDecimal(100)));
    cart.deleteItem(1);
    System.out.println(cart);
}

@Test
public void clear() {
    Cart cart = new Cart();
    cart.addItem(new CartItem(1, "java从入门到精通", 1, new BigDecimal(1000),new BigDecimal(1000)));
    cart.addItem(new CartItem(1, "java从入门到精通", 1, new BigDecimal(1000),new BigDecimal(1000)));
    cart.addItem(new CartItem(2, "数据结构与算法", 1, new BigDecimal(100),new BigDecimal(100)));
    cart.deleteItem(1);
    cart.clear();
    System.out.println(cart);
}

@Test
public void updateCount() {
    Cart cart = new Cart();
    cart.addItem(new CartItem(1, "java从入门到精通", 1, new BigDecimal(1000),new BigDecimal(1000)));
    cart.addItem(new CartItem(1, "java从入门到精通", 1, new BigDecimal(1000),new BigDecimal(1000)));
    cart.addItem(new CartItem(2, "数据结构与算法", 1, new BigDecimal(100),new BigDecimal(100)));
    cart.deleteItem(1);
    cart.clear();
    cart.addItem(new CartItem(1, "java从入门到精通", 1, new BigDecimal(1000),new BigDecimal(1000)));
    cart.updateCount(1, 10);
    System.out.println(cart);
}

} ```

加入购物车功能的实现

  1. CartServlet 程序中的代码

``` protected void addItem(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 获取请求的参数 商品编号 int id = WebUtils.parseInt(req.getParameter("id"), 0); // 调用 bookService.queryBookById(id):Book 得到图书的信息 Book book = bookService.queryBookById(id); // 把图书信息,转换为CartItem商品项 CartItem cartItem = new CartItem(book.getId(), book.getName(), 1, book.getPrice(), book.getPrice()); // 调用Cart.addItem(CartItem);添加商品项 Cart cart = (Cart) req.getSession().getAttribute("cart"); if (cart == null) { cart = new Cart(); req.getSession().setAttribute("cart", cart); } cart.addItem(cartItem);

System.out.println(cart);
System.out.println("请求头Referer的值:" + req.getHeader("Referer"));

// 重定向回原来商品所在的地址页面
resp.sendRedirect(req.getHeader("Referer"));

} ```

2.

index.jsp

页面

js

的代码

修改 index.jsp

3.图解说明,如何跳回添加商品的页面

跳回添加商品的页面

购物车的展示

修改 cart.jsp

修改 cart,jsp(一)

修改 cart.jsp(二)

删除购物车商品项

  1. CartServlet 增加 deleteItem 方法

``` protected void deleteItem(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 获取商品编号 int id = WebUtils.parseInt(req.getParameter("id"), 0); // 获取购物车对象 Cart cart = (Cart) req.getSession().getAttribute("cart");

if (cart != null) {
    // 删除购物车商品项
    cart.deleteItem(id);
    // 重定向回原来购物车展示页面
    resp.sendRedirect(req.getHeader("Referer"));
}

```

  1. 修改 cart.jsp ,添加删除的请求地址,并增加确认删除的提示。

修改 cart.jsp

清空购物车

CartServlet 增加 clear 方法

protected void clear(HttpServletRequest req, HttpServletResponse resp) throws ServletException,IOException{ // 1 获取购物车对象 Cart cart = (Cart) req.getSession().getAttribute("cart"); if (cart != null) { // 清空购物车 cart.clear(); // 重定向回原来购物车的展示页面 resp.sendRedirect(req.getHeader("Referer")); } }

cart.jsp 页面的内容,给购物车添加请求地址,和添加 id 属性,以及清空确认提示操作

修改 cart.jsp

修改购物车商品的数量

CartServlet 添加 updateCount 方法

``` protected void updateCount(HttpServletRequest req, HttpServletResponse resp) throws ServletException,IOException{ // 获取请求的参数 商品编号,商品数量 int id = WebUtils.parseInt(req.getParameter("id"), 0); int count = WebUtils.parseInt(req.getParameter("count"), 1); // 获取Cart购物车对象 Cart cart = (Cart) req.getSession().getAttribute("cart");

if (cart != null) {
    // 修改商品数量
    cart.updateCount(id, count);
    // 重定向回原来购物车展示页面
    resp.sendRedirect(req.getHeader("Referer"));
}

} ```

  1. 修改

cart.jsp

修改 cart.jsp(一)

修改 cart.jsp(二)

首页购物车数据回显

  1. 在添加商品到购物车的时候,保存最后一个添加的商品名称。

修改 CartServlet 的 addItem 方法 2. 在

index.jsp

页面中输出购物车信息

修改 index.jsp

订单模块

这一部分我们完成订单模块,主要包括生成订单,查询所有订单,发货,查看订单详情,查看我的订单,签收订单。

订单模块的分析

订单模块的分析

订单模块的实现

创建订单模块的数据库表

为订单创建一个 t_order 表,为订单项创建一个 t_order_item 表。

``` use book;

create table t_order( order_id varchar(50) primary key, create_time datetime, price decimal(11,2), status int, user_id int, foreign key( user_id ) references t_user( id ) );

create table t_order_item( id int primary key auto_increment, name varchar(100), count int, price decimal(11,2), total_price decimal(11,2), order_id varchar(50), foreign key( order_id ) references t_order( order_id ) ); ```

创建订单模块的数据模型

创建一个 Order 类和一个 OrderItem 类, Order 类属性如下:

private String orderId; private Date createTime; private BigDecimal price; // 0未发货,1已发货,2已签收 private Integer status = 0; private Integer userId;

OrderItem 类属性如下:

private Integer id; private String name; private Integer count; private BigDecimal price; private BigDecimal totalPrice; private String orderId;

并为其生成构造方法, Get 方法, Set 方法,以及 toString 方法。

编写订单模块的 Dao 程序和测试

OrderDao 接口

``` public interface OrderDao { public int saveOrder(Order order);

public List<Order> queryOrders();

public List<Order> queryOrdersByUserId(Integer id);

public int updateOrder(Order order);

} ```

OrderDaoImpl 实现类

``` 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());
}

/**
 * 查询全部订单
 * @return
 */
@Override
public List<Order> queryOrders() {
    String sql = "select `order_id` orderId,`create_time` createTime,`price`,`status`,`user_id` userId from t_order";
    return queryForList(Order.class, sql);
}

/**
 * 根据UserId查询订单
 * @param id
 * @return
 */
@Override
public List<Order> queryOrdersByUserId(Integer id) {
    String sql = "select `order_id` orderId,`create_time` createTime,`price`,`status`,`user_id` userId from t_order where user_id=?";
    return queryForList(Order.class, sql, id);
}

/**
 * 修改订单
 * @param order
 * @return
 */
@Override
public int updateOrder(Order order) {
    String sql = "update t_order set `create_time`=?,`price`=?,`status`=? where order_id=?";
    return update(sql, order.getCreateTime(), order.getPrice(), order.getStatus(), order.getOrderId());
}

} ```

测试

``` public class OrderDaoTest {

OrderDao orderDao = new OrderDaoImpl();
@Test
public void saveOrder() {
    orderDao.saveOrder(new Order("12345678",new Date(),new BigDecimal(100),0,2));
    orderDao.saveOrder(new Order("123", new Date(), new BigDecimal(1005), 0, 5));
    orderDao.saveOrder(new Order("456", new Date(), new BigDecimal(505), 1, 6));
    orderDao.saveOrder(new Order("789", new Date(), new BigDecimal(605), 2, 6));
}

@Test
public void queryOrders() {
    System.out.println(orderDao.queryOrders());
}

@Test
public void queryOrdersByUserId() {
    System.out.println(orderDao.queryOrdersByUserId(6));
}

@Test
public void updateOrder() {
    orderDao.updateOrder(new Order("456", new Date(), new BigDecimal(611), 2, 6));
}

} ```

OrderDaoItem 接口

``` public interface OrderItemDao { public int saveOrderItem(OrderItem orderItem);

public List<OrderItem> queryOrderItemsById(String id);

} ```

OrderDaoItemImpl 实现类

``` 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());
}

/**
 * 根据order_id查询订单项
 * @param id
 * @return
 */
@Override
public List<OrderItem> queryOrderItemsById(String id) {
    String sql = "select `id`,`name`,`count`,`price`,`total_price` totalPrice,`order_id` orderId from t_order_item where order_id=?";

    return queryForList(OrderItem.class, sql, id);
}

} ```

测试

``` public class OrderItemDaoTest {

OrderItemDao orderItemDao = new OrderItemDaoImpl();
@Test
public void saveOrderItem() {
    orderItemDao.saveOrderItem(new OrderItem(null,"java 从入门到精通", 1,new BigDecimal(100),new
            BigDecimal(100),"456"));
    orderItemDao.saveOrderItem(new OrderItem(null,"javaScript 从入门到精通", 2,new
            BigDecimal(100),new BigDecimal(200),"12345678"));
    orderItemDao.saveOrderItem(new OrderItem(null,"Netty 入门", 1,new BigDecimal(100),new
            BigDecimal(100),"456"));
}

@Test
public void queryOrderItemsById() {
    System.out.println(orderItemDao.queryOrderItemsById("12345678"));
}

} ```

编写订单模块的 Service 和测试

OrderService 接口

``` public interface OrderService {

/**
 * 创建订单
 * @param cart
 * @param userId
 * @return
 */
public String createOrder(Cart cart, Integer userId);

/**
 * 展示所有订单
 * @return
 */
public List<Order> showAllOrders();

/**
 * 发货
 * @param order
 */
public void sendOrder(Order order);

/**
 * 查询订单详情
 * @param id
 * @return
 */
public List<OrderItem> showOrderDetail(String id);

/**
 * 展示我的订单
 * @param id
 * @return
 */
public List<Order> showMyOrders(Integer id);

/**
 * 签收
 * @param order
 */
public void receiverOrder(Order order);

} ```

OrderServiceImpl 实现类

``` public class OrderServiceImpl implements OrderService { private OrderDao orderDao = new OrderDaoImpl(); private OrderItemDao orderItemDao = new OrderItemDaoImpl(); private BookDao bookDao = new BookDaoImpl();

@Override
public String createOrder(Cart cart, Integer userId) {
    // 订单号==唯一性
    String orderId = System.currentTimeMillis()+""+userId;
    // 创建一个订单对象
    Order order = new Order(orderId, new Date(), cart.getTotalPrice(), 0, userId);
    // 保存订单
    orderDao.saveOrder(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() );
        bookDao.updateBook(book);
    }
    // 清空购物车
    cart.clear();
    return orderId;
}

@Override
public List<Order> showAllOrders() {
    return orderDao.queryOrders();
}

@Override
public void sendOrder( Order order ) {
    order.setStatus(1);
    orderDao.updateOrder(order);
}

@Override
public List<OrderItem> showOrderDetail(String id) {
    return orderItemDao.queryOrderItemsById(id);
}

@Override
public List<Order> showMyOrders(Integer id) {
    return orderDao.queryOrdersByUserId(id);
}

@Override
public void receiverOrder(Order order) {
    order.setStatus(2);
    orderDao.updateOrder(order);
}

} ```

测试

``` public class OrderServiceImplTest {

private OrderService orderService = new OrderServiceImpl();

@Test
public void createOrder() {
    Cart cart = new Cart();
    cart.addItem(new CartItem(1, "java从入门到精通", 1, new BigDecimal(1000),new BigDecimal(1000)));
    cart.addItem(new CartItem(1, "java从入门到精通", 1, new BigDecimal(1000),new BigDecimal(1000)));
    cart.addItem(new CartItem(2, "数据结构与算法", 1, new BigDecimal(100),new BigDecimal(100)));
    System.out.println("订单号是:" + orderService.createOrder(cart, 2));
}

@Test
public void showAllOrders() {
    System.out.println(orderService.showAllOrders());
}

@Test
public void sendOrder() {
    orderService.sendOrder(new Order("16366346078552",new Date(), new BigDecimal(2100),0,2));
}

@Test
public void showOrderDetail() {
    System.out.println(orderService.showOrderDetail("16366346078552"));
}

@Test
public void showMyOrders() {
    System.out.println(orderService.showMyOrders(2));
}

@Test
public void receiverOrder() {
    orderService.receiverOrder(new Order("16366346078552",new Date(), new BigDecimal(2100),1,2));
}

} ```

编写订单模块的 Web 层和页面联调

生成订单
  1. OrderServlet 添加 createOrder 方法

``` /* * 生成订单 * @param req * @param resp * @throws ServletException * @throws IOException / protected void createOrder(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 先获取购物车对象 Cart cart = (Cart) req.getSession().getAttribute("cart"); // 获取Userid User loginUser = (User) req.getSession().getAttribute("user");

if (loginUser == null) {
    req.getRequestDispatcher("/pages/user/login.jsp").forward(req, resp);
    return ;
}

Integer userId = loginUser.getId();
String orderId = orderService.createOrder(cart, userId);

req.getSession().setAttribute("orderId",orderId);

resp.sendRedirect(req.getContextPath()+"/pages/cart/checkout.jsp");

} ```

  1. 修改 cart.jsp 页面,结账的请求地址

修改 cart.jsp 2. 修改 checkout.jsp 页面

修改 checkout.jsp

##### 展示全部订单 3. OrderServlet 添加 showAllOrders 方法

/** * 展示全部订单 * @param req * @param resp * @throws ServletException * @throws IOException */ protected void showAllOrders(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 1.通过orderService查询所有订单 List<Order> orders = orderService.showAllOrders(); // 2.把全部订单保存到Request域 req.setAttribute("orders", orders); // 3.请求转发到 /pages/manager/order_manager.jsp req.getRequestDispatcher("/pages/manager/order_manager.jsp"); }

  1. 修改 manager_menu.jsp 页面,订单管理请求地址

修改 manager_menu.jsp 2. 修改 order_manager.jsp 页面

修改 order_manager.jsp

##### 展示我的订单 3. OrderServlet 添加 showMyOrders 方法

``` /* * 展示我的订单 * @param req * @param resp * @throws ServletException * @throws IOException / protected void showMyOrders(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 获取Userid User loginUser = (User) req.getSession().getAttribute("user");

if (loginUser == null) {
    req.getRequestDispatcher("/pages/user/login.jsp").forward(req, resp);
    return ;
}

Integer userId = loginUser.getId();
List<Order> myOrders = orderService.showMyOrders(userId);

req.getSession().setAttribute("myOrders", myOrders);

resp.sendRedirect(req.getContextPath()+"/pages/order/order.jsp");

} ```

  1. 修改 login_success_menu.jsp 页面,我的订单请求地址

修改 login_menu.jsp 2. 修改 order.jsp 页面

修改 order.jsp

显示订单详情
  1. OrderServlet 增加 showOrderDetail 方法

/** * 显示订单详情 * @param req * @param resp * @throws ServletException * @throws IOException */ protected void showOrderDetail(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 1. 获取Orderid String orderId = req.getParameter("orderId"); if (orderId != null && orderId != "") { // 2. 查询订单项 List<OrderItem> orderItems = orderService.showOrderDetail(orderId); // 3. 把订单项保存到Request域 req.setAttribute("orderItems", orderItems); // 4. 请求转发到 /pages/order/order_detail.jsp req.getRequestDispatcher("/pages/order/order_detail.jsp").forward(req, resp); } }

  1. order 目录下新建 order_detail.jsp 页面

``` <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ page contentType="text/html;charset=UTF-8" language="java" %>

我的订单 <%-- 静态包含 base标签,css样式,jQuery文件 --%> <%@ include file="/pages/common/head.jsp"%>
商品名称 数量 单价 金额
${orderItem.name} ${orderItem.count} ${orderItem.price} ${orderItem.totalPrice}
<%-- include包含脚部信息 --%> <%@ include file="/pages/common/footer.jsp"%>

```

  1. 修改 order.jsp 页面,查看详情请求地址

修改 order.jsp 2. 修改 order_manager.jsp 页面,查看详情请求地址

修改 order_manager.jsp

##### 发货 3. OrderServlet 增加 sendOrder 方法

/** * 发货 * @param req * @param resp * @throws ServletException * @throws IOException */ protected void sendOrder(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 1. 获取Order Order order = WebUtils.copyParamToBean(req.getParameterMap(), new Order()); // 2. 调用orderService的sendOrder方法发货 orderService.sendOrder(order); // 3. 重定向回 /pages/manager/order_manager.jsp resp.sendRedirect(req.getContextPath() + "/pages/manager/order_manager.jsp"); }

  1. 修改

WebUtils

copyParamToBean

方法,因为

BeanUtils

无法直接将

String

转为

Date

,所以要先注册

修改 WebUtils 2. 修改

order_manager.jsp

页面, 点击发货请求地址

修改 order_manager.jsp

##### 签收 3. OrderServlet 增加 receiverOrder 方法

/** * 签收 * @param req * @param resp * @throws ServletException * @throws IOException */ protected void receiverOrder(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 1. 获取Order Order order = WebUtils.copyParamToBean(req.getParameterMap(), new Order()); // 2. 调用orderService的receiverOrder方法签收 orderService.receiverOrder(order); // 3. 重定向回 /orderServlet?action=showMyOrders resp.sendRedirect(req.getContextPath() + "/orderServlet?action=showMyOrders"); }

  1. 修改

order.jsp

页面,点击签收请求地址

修改 order.jsp

Filter 过滤器和 Aajx 请求

第十部分我们做一个结尾,主要包括使用 Filter 过滤器进行权限检查,使用 Filter 和 ThreadLocal 组合管理事务,将异常同一交给 Tomcat 并展示友好的错误页面,使用 Aajx 请求改进功能。

Filter 过滤器实现权限检查

我们要使用 Filter 过滤器拦截/pages/manager/所有内容,实现权限检查。

  1. Filter 的工作流程如下

Filter 工作流程图 2. Filter 过滤器的使用步骤:

``` 1、 编写一个类去实现 Filter 接口

2、 实现过滤方法 doFilter()

3、 到 web.xml 中去配置 Filter ```

  1. Filter 的生命周期

Filter 的生命周期包含几个方法 1、构造器方法 2、init 初始化方法 第 1,2 步,在 web 工程启动的时候执行(Filter 已经创建) 3、doFilter 过滤方法 第 3 步,每次拦截到请求,就会执行 4、destroy 销毁 第 4 步,停止 web 工程的时候,就会执行(停止 web 工程,也会销毁 Filter 过滤器)

  1. Filter 的拦截路径

``` --精确匹配 /target.jsp 以上配置的路径,表示请求地址必须为:http://ip:port/工程路径/target.jsp

--目录匹配 /admin/ 以上配置的路径,表示请求地址必须为:http://ip:port/工程路径/admin/

--后缀名匹配 .html 以上配置的路径,表示请求地址必须以.html 结尾才会拦截到 .do 以上配置的路径,表示请求地址必须以.do 结尾才会拦截到 *.action 以上配置的路径,表示请求地址必须以.action 结尾才会拦截 ```

  1. 创建 ManagerFilter 实现类

``` public class ManagerFilter implements Filter { @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest; Object user = httpServletRequest.getSession().getAttribute("user");

    if (user == null) {
        httpServletRequest.getRequestDispatcher("/pages/user/login.jsp").forward(servletRequest,servletResponse);
    } else {
        filterChain.doFilter(servletRequest, servletResponse);
    }
}

@Override
public void init(FilterConfig filterConfig) throws ServletException {

}

@Override
public void destroy() {

}

} ```

  1. 配置 web.xml 文件

<filter> <filter-name>ManagerFilter</filter-name> <filter-class>com.atguigu.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 和 ThreadLocal 组合管理事务

  1. ThreadLocal 的作用

``` ThreadLocal 的作用,它可以解决多线程的数据安全问题。

ThreadLocal 它可以给当前线程关联一个数据(可以是普通变量,可以是对象,也可以是数组,集合)

ThreadLocal 的特点: 1、ThreadLocal 可以为当前线程关联一个数据。(它可以像 Map 一样存取数据,key 为当前线程) 2、每一个 ThreadLocal 对象,只能为当前线程关联一个数据,如果要为当前线程关联多个数据,就需要使用 多个 ThreadLocal 对象实例。 3、每个 ThreadLocal 对象实例定义的时候,一般都是 static 类型 4、ThreadLocal 中保存数据,在线程销毁后。会由 JVM 虚拟自动释放 ```

  1. 使用

ThreadLocal

来确保所有

dao

操作都在同一个

Connection

连接对象中完成。

原理分析图

JdbcUtils

工具类的修改

``` public class JdbcUtils {

private static DruidDataSource dataSource;
private static ThreadLocal<Connection> conns = new ThreadLocal<Connection>();

static {
    try {
        Properties properties = new Properties();
        // 读取jdbc.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 (Exception e) {
            e.printStackTrace();
        }
    }
    return conn;
}

/**
 * 提交事务,并关闭释放连接
 */
public static void commitAndClose() {
    Connection connection = conns.get();
    if (connection != null) { // 如果不等于null,说明之前使用过连接,操作过数据库
        try {
            connection.commit(); // 提交事务
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        } finally {
            try {
                connection.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
    }
    // 一定要执行remove操作,否则就会出错(因为Tomcat服务器底层使用了线程池技术)
    conns.remove();
}

public static void rollbackAndClose(){
    Connection connection = conns.get();
    if (connection != null) { //如果不等于null,说明之前使用过连接,操作过数据库
        try {
            connection.rollback(); // 回滚事务
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        } finally {
            try {
                connection.close(); //关闭连接释放资源
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
    }
    // 一定要执行remove操作,否则就会出错(因为Tomcat底层使用了线程池技术)
    conns.remove();
}

// // /* // * 关闭连接,放回数据库连接池 // * @param conn // / // public static void close(Connection conn){ // if(conn != null) // { // try { // conn.close(); // } catch (Exception e) { // e.printStackTrace(); // } // } // } } ```

修改 BaseDao

``` 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);
    }
}

} ```

  1. 使用

Filter

过滤器统一给所有的

Service

方法都加上

try-catch

。来进行实现的管理。

原理分析图

Filter

类代码

``` 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(); } }

@Override
public void init(FilterConfig filterConfig) throws ServletException {

}

@Override
public void destroy() {

}

} ```

配置 web.xml

<filter> <filter-name>TransactionFilter</filter-name> <filter-class>com.atguigu.filter.TransactionFilter</filter-class> </filter> <filter-mapping> <filter-name>TransactionFilter</filter-name> <!-- /* 表示当前工程下所有请求 --> <url-pattern>/*</url-pattern> </filter-mapping>

一定要记得把 BaseServlet 中的异常往外抛给 Filter 过滤器

``` public abstract class BaseServlet extends HttpServlet {

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    doPost(req, resp);
}

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    // 解决post请求中文乱码问题
    // 一定要在获取请求参数之前调用才有效
    req.setCharacterEncoding("UTF-8");

    String action = req.getParameter("action");

    try {
        // 获取action业务鉴别字符串,获得相应的业务 方法反射对象
        Method method = this.getClass().getDeclaredMethod(action, HttpServletRequest.class, HttpServletResponse.class);
        // 调用目标业务 方法
        method.invoke(this, req, resp);
    } catch (Exception e) {
        e.printStackTrace();
        throw new RuntimeException(e); // 把异常抛给Filter过滤器
    }

}

} ```

Tomcat 管理异常

我们将所有异常都统一交给 Tomcat ,让 Tomcat 展示友好的错误信息页面,这样用户就不用面对一大堆问题代码了。

  1. web.xml 中我们可以通过错误页面配置来进行管理

```

500 /pages/error/error500.jsp

404 /pages/error/error404.jsp ```

  1. 编写错误页面,我们就简单做一下

error500.jsp

``` <%@ page contentType="text/html;charset=UTF-8" language="java" %>
服务器出错啦 <%-- 静态包含 base标签,css样式,jQuery文件 --%> <%@ include file="/pages/common/head.jsp"%> 服务器出错啦,程序员小哥正在加紧抢修。
返回首页

```

error404.jsp

``` <%@ page contentType="text/html;charset=UTF-8" language="java" %>

访问资源不存在 <%-- 静态包含 base标签,css样式,jQuery文件 --%> <%@ include file="/pages/common/head.jsp"%> 您访问的资源不存在,或已被删除
返回首页

```

  1. TransactionFilter

要把异常抛给

Tomcat

修改 TransactionFilter

使用 AJAX 验证用户名是否可用

  1. 图解验证用户名是否可用流程

验证用户名是否可用流程 2. 导入相关 jar

gson-2.2.4.jar

  1. UserServlet 程序添加 ajaxExistsUsername 方法

``` /* * ajax请求判断用户名是否存在 * @param req * @param resp * @throws ServletException * @throws IOException / protected void ajaxExistUsername(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 获取请求的参数username String username = req.getParameter("username"); // 调用userService.existUsername() boolean existsUsername = userService.existsUsername(username); // 把返回的结果封装为map对象 Map resultMap = new HashMap<>(); resultMap.put("existsUsername", existsUsername);

 Gson gson= new Gson();
 String json = gson.toJson(resultMap);

 resp.getWriter().write(json);

} ```

  1. 修改 regist.jsp 中的代码

// 给用户名绑定失去焦点事件 $("#username").blur(function () { // 1. 获取用户名 var username = this.value; //2 创建正则表达式对象 var usernamePatt = /^\w{5,12}$/; //3 使用test方法验证 if(!usernamePatt.test(username)) { // 用户名不合法 //4 提示用户结果 $("span.errorMsg").text("用户名不合法!"); } else{ // 用户名合法判断用户名是否存在 // alert("用户名合法"); $.getJSON("${pageScope.basePath}userServlet","action=ajaxExistsUsername&username=" + username, function (data) { if(data.existsUsername) { // 用户名存在 $("span.errorMsg").text("用户名已存在!"); } else { // 用户名可用 $("span.errorMsg").text("用户名可用!"); } }); } });

AJAX 添加商品到购物车

我们之前把商品添加到购物车,是刷新了整个页面,这样用户体验不是很好,我们可用使用 Ajax 请求来完成局部的更新。

  1. 图解商品加入购物车

加入购物车流程 2. CartServlet 程序添加 ajaxAddItem 方法

``` protected void ajaxAddItem(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 获取请求的参数 商品编号 int id = WebUtils.parseInt(req.getParameter("id"), 0); // 调用 bookService.queryBookById(id):Book 得到图书的信息 Book book = bookService.queryBookById(id); // 把图书信息,转换为CartItem商品项 CartItem cartItem = new CartItem(book.getId(), book.getName(), 1, book.getPrice(), book.getPrice()); // 调用Cart.addItem(CartItem);添加商品项 Cart cart = (Cart) req.getSession().getAttribute("cart"); if (cart == null) { cart = new Cart(); req.getSession().setAttribute("cart", cart); } cart.addItem(cartItem); System.out.println(cart);

// 最后一个添加的商品名称
req.getSession().setAttribute("lastName", cartItem.getName());

// 返回购物车总的商品数量和最后一个添加的商品名称
Map<String, Object> resultMap = new HashMap<String, Object>();

resultMap.put("totalCount", cart.getTotalCount());
resultMap.put("lastName", cartItem.getName());

Gson gson = new Gson();
String resultMapJsonString = gson.toJson(resultMap);

resp.getWriter().write(resultMapJsonString);

} ```

  1. 修改 index.jsp 页面

html 代码

js 代码

  1. 修改

BaseServlet 程序解决中文乱码问题

解决中文乱码问题

参考文献

  • 世通书屋网上书店系统的设计与开发——基于Asp.net技术(吉林大学·李清海)
  • 基于WEB的JSP网络售书系统(吉林大学·郭志峰)
  • 网上书店的设计与实现(山东大学·惠开敏)
  • 基于B/S架构的博文网络书店的设计与实现(电子科技大学·彭媛媛)
  • 基于B/S结构的电子商务的研究与应用(哈尔滨工程大学·车彦朋)
  • 网上书店系统设计与实现(吉林大学·关键)
  • 网络文学平台的设计与实现(华中科技大学·王俊)
  • 基于.NET的网上购书系统设计与实现(电子科技大学·李园媛)
  • 基于JSP的网上书店交易系统的设计与实现(吉林大学·徐迎新)
  • 基于J2EE架构网上书店的设计与实现(同济大学·蔡玮)
  • 基于SSH框架的网上书城系统设计与实现(成都理工大学·田涛)
  • 基于SSH框架的网上书城系统设计与实现(成都理工大学·田涛)
  • 网上购书系统的设计与实现(电子科技大学·谢宗燃)
  • 网上书店的设计与实现(同济大学·徐爱鸣)
  • 图书综合管理系统(吉林大学·王宇)

本文内容包括但不限于文字、数据、图表及超链接等)均来源于该信息及资料的相关主题。发布者:源码导航 ,原文地址:https://m.bishedaima.com/yuanma/35784.html

相关推荐

发表回复

登录后才能评论