跳转至

第14章 项目实战——尚硅谷书城

第 14 章

项目实战 —— 尚硅谷书城

前面介绍了 JavaWeb 学习阶段所涉及的技术知识,从前端页面开发,到后端接口实现,以及借助框架优化开发,实现前后端分离。至此,大家可以独自完成 JavaWeb 项目了。本章将带领大家共同完成一个项目 “尚硅谷书城”,综合运用本书所学知识,巩固和检验所学成果。

14.1 项目概述

尚硅谷书城项目主要是搭建一个图书的销售平台,后台进行图书的数据维护、添加图书、修改图书、删除图书等功能。前台进行图书的展示、加入购物车、购物车图书商品的操作,以及结账等功能。

14.1.1 功能介绍

尚硅谷书城项目包括的功能有登录注册、图书的增删改查操作、加入购物车、清空购物车、购物车商品数量的修改,以及结账等功能。该项目基本上能够涵盖本书所有的知识点,希望大家认真练习,做到知其然并知其所以然,在今后的开发中灵活运用所学知识,能够举一反三,融会贯通。

下面给大家展示一些项目截图,如图 14-1、图 14-2、图 14-3 所示。

43efac100d37fbd1ea5948ae21cf8f4ff7139be62a786e3d2318686a5c639f5f.jpg

这几张图片就是我们项目中要实现的部分功能,接下来我们从零开始,逐步完成一个成品项目。创建该项目的关键在于应用前面所学的知识点,基于此有一些功能暂时无法实现,例如,后台管理的权限控制、结账中支付接口的对接等,但并不影响项目的整体效果。尚硅谷书城项目采用 MVC 架构进行开发,涉及的技术有 Servlet、Thymeleaf、Filter、Session 等,数据库使用的是 MySQL。

7af52ce849d4afd75baab15cb024f51f5efcd6b2859992830405f8a71ad1dd71.jpg

04e5e17de40b36aa2b2b89e36a2942118b3cd7c625a6648e86cba3fd0eff4fa9.jpg

14.1.2 数据库设计

尚硅谷书城项目的数据库共涉及 4 张表,分别为用户表 t_users、图书表 t_books、订单表 t_order、订单详情表 t_order_item,表结构分别如表 14-1 至表 14-4 所示。

表 14-1 t_users 表结构

字段类型说明
idint、自增主键用户 ID
usernamevarchar(20)用户名
passwordvarchar(50)密码
emailvarchar(50)邮箱

表 14-2 t_books 表结构

字段类型说明
idint、自增主键图书ID
titlevarchar(50)书名
authorvarchar(50)作者
priceDouble单价
salesInt销量
stockInt库存
img_pathvarchar(200)图片路径

表 14-3 t_order 表结构

字段类型说明
order_idint、自增主键订单 ID
order_sequencevarchar(200)订单编号
create_timevarchar(200)创建时间
total_countint总件数
total_amountdouble总价
order_statusint订单状态(0:待发货 1:已发货 2:确认收货 3:已评价)
user_idint订单所属用户 ID

表 14-4 t_order_item 表结构

字段类型说明
item_idint、自增主键订单项 ID
book_namevarchar(50)书名
priceDouble单价
img_pathvarchar(200)图片路径
item_countInt件数
item_amountDouble金额
order_idInt订单项所属的订单 id

14.1.3 项目搭建

在对尚硅谷书城项目及用到的数据库表进行了简单了解后,接下来进入项目开发。首先完成一些项目开发前的准备工作。

1. 导入建表和初始化表数据的 sql 语句

从项目资源素材中获取 bookManager.sql 文件,在 atguigu 数据库下执行该文件,创建对应表格,如图 14-4 所示。

2. 创建项目

打开 IDEA,创建一个 Project,设置好项目名称和存储位置等信息,最后单击 “Create” 按钮创建即可,如图 14-5 所示。

8efbb3d9cdac84edb604850c84ce3c4312afe7c5c2fb659730affb5517f5ad2b.jpg

在项目上右键选择 “Add Framework Support” 进入新窗口,如图 14-6 所示。并在 Java EE 类目下选择 “Web Application” 选项为该项目添加 web 组件,最后单击 “OK” 按钮即可。如图 14-7 所示。

a223a3ec2e4f6232897154d7231df38dabf03e73d238c94b0e63cd98d13898a1.jpg

d6116f387e88ba64098ab208b14ba3ffc928f55a17bd4aaa8260d76d56f87eb9.jpg

969b48003f7ad4f7d332eff8b0ba2d397429240d804cecfcd9766c40cd6caff6.jpg

创建完后,项目目录会多出一个 web 组件,web 目录下的 index.jsp 删除即可,我们采用 Thymeleaf 进行视图渲染。BookManager 项目的目录结构如图 14-8 所示。

3. 添加数据库相关资源

接下来需要将一些工具的 jar 包,以及之前课程中封装过的工具类导入到项目内,清单如下。

- 相关 jar 包

- 工具类

- 配置文件:db.properties

下面依次将其添加到书城项目中。

(1) 添加 Tomcat 依赖。

单击菜单中的 “File”,然后选择 “Project Structure” 进入新窗口,如图 14-9 所示。选择 “Modules”,然后选中 “BookManager” 模块,单击右侧的 “Dependencies”,单击加号选择 “2 Library”,选中 “Tomcat 8.5.27”,再单击 “Add Selected”,最后单击 “OK” 按钮即可添加成功。

(2) 添加相关 jar 包。

在 WEB-INF 下创建 lib 目录,将相关 jar 包复制粘贴进去(从资料包中寻找),并右键 Add As Library 将其添加为项目的依赖,如图 14-10 所示。

9f4fa8a15e8c148abdfe401584b67d749a24915ed4dd41a670b19c4dc336c632.jpg

3e4fcb775565b7b3f206ed12b7d77d7357e114f90c75360527def19d8e455a46.jpg

(3) 创建配置文件。

在项目根目录下创建 resources 目录,并右键选择 “Mark Directory as”,标记该目录为 Resources Root 类型。这样项目编译时,会将该目录下的配置文件编译到类路径下。在 resources 目录下新建文件 db.properties,并添加如下配置信息。

driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/atguigu
username=root
password=root
initialSize=10
maxActive=20
inIdle=1
maxWait=1000 

需要注意,关于 MySQL 数据库的端口号、数据库名称,以及密码都需要设置成自己的。

(4)创建包名,添加工具类。

在 src 目录下将 package 准备好供后期使用。

将 JDBCTools、MD5Util、CommonResult 工具类复制到 util 包下,将 BaseDao 复制到 dao 包下,将 ViewBaseServlet 复制到 servlet.base 包下。

另外,在 web.xml 中设置 Thymeleaf 渲染视图的前缀和后缀。

<?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
    xml/ns/javaee/web-app_4_0.xsd"
    version="4.0">
    <!--配置全局初始化参数:配置 Thymeleaf 视图的前缀和后缀-->
    <context-param>
    <!--key 值是自定义的-->
    <param-name>view-prefix</param-name>
    <!--value 值是根据你的项目结构分析出来的-->
    <param-value>/WEB-INF/pages;</param-value>
    </context-param>
    <context-param>
    <param-name>view-suffix</param-name>
    <param-value>.html</param-value>
    </context-param>
</web-app> 

(5) 拷贝静态资源。

将静态资源中 static 文件夹拷贝到项目的 web 目录下,将 pages 文件夹拷贝到 WEB-INF 目录下,将 index.html 拷贝到 WEB-INF/pages 目录下。

操作后的项目结构如图 14-11 所示。

faf2111373291afb8106ad94f8c11bab6d3861c1b5574d558e3460d335c530c5.jpg

14.2 首页的访问

书城项目使用 Thymeleaf 进行视图渲染,将页面都存放在 / WEB-INF/pages/ 目录下,因此首页的访问,需要先发请求到 Servlet,再由 Thymeleaf 进行渲染。步骤如下。

IndexServlet 代码如下。

package com.atguigu.servlet.model;
//省略 import 语句
@WebSocket("/index.html")
public class IndexServlet extends++,ViewBaseServlet {
    @Override
    protected void doGet(HttpServletRequest request,HttpServletResponse response) throws ServletException, IOException {
    this doPost(request, response);
    }

    @Override
    protected void doPost(HttpServletRequest request,HttpServletResponse response) throws ServletException, IOException {
    this.processTemplate("index",request, response);
    }
} 

index.html 页面代码如下。

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>书城首页</title>
    <link rel="stylesheet" th:href="@{/static/css/minireset.css}" />
    <link rel="stylesheet" th:href="@{/static/css/common.css}" />
    <link rel="stylesheet" th:href="@{/static/css/iconfont.css}" />
    <link rel="stylesheet" th:href="@{/static/css/index.css}" />
    <link rel="stylesheet" th:href="@{/static/css/swiper.min.css}" />
</head>
<body>
<!-- body 代码省略 -->
</body> 

添加 Tomcat 镜像服务器部署书城项目,如图 14-12 所示。然后启动服务器端,测试首页是否访问成功。打开浏览器通过 “http://localhost:8888/BookManager_war_exploded/index.html” 访问书城首页,如图 14-13 所示。

86a7d7a00976bb0ae2f4c04c0bec74d83b51b3bea83f7b1757cf11d7c892f0a0.jpg

3791213d203cd00bdcf05eb60c1ccdea1e95634637e9a06d7040b9f64a8e1290.jpg

14.3 用户管理模块

用户管理模块主要包括三个功能,分别是用户注册、用户登录和用户注销。下面依次对这三个功能展开介绍。

14.3.1 用户注册

注册功能是用户的第一个操作,也是整个项目的第一个功能。重点理解按照 MVC 架构进行开发时,各层对应的代码结构及其调用关系。后面其他功能与此类似。

1. 开发实体类

在 bean 包下创建 User 类,对应数据库中的 t_users 表。示例代码如下。

package com.atguigu.bean;

public class User {
    private Integer id; //用户 id
    private String username; //用户名字
    private String password; //用户密码
    private String email; //用户邮箱
    //get,set 方法省略
} 

2. 注册页面的访问

实现在首页中单击 “注册” 超链接跳转注册页面。

(1)创建 ToRegistPageServlet 类,实现跳转注册页面。

package com.atguigu.servlet.model;

@WebServlet("/regist.html")
public class ToRegistPageServlet extends++,ViewBaseServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws,%InException, IOException { 
this.doPost(request, response);
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    this.processTemplate("user/regist", request, response);
} 

(2)修改 index.html 页面中注册对应的超链接,设置访问路径为 ToRegistPageServlet 类的映射路径。示例代码如下。

<a th:href="@{/regist.html}" class="register">注册</a> 

(3)修改 / WEB-INF/pages/user/ 目录下的 regist.html 页面,添加 Thymeleaf 的名称空间,实现页面渲染。

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8" />
    <title>尚硅谷会员注册页面</title>
    <link type="text/css" rel="stylesheet" th:href="@{/static/css/style.css}"/>
    <link rel="stylesheet" th:href="@{/static/css/register.css}" />
    <style type="text/css">
    .login_form {
    height: 420px;
    margin-top: 25px;
    }
    </style>
</head>
<body>
    <div id="login_header">
    <a th:href="@{/index.html}">
    <img class="logo_img" alt="" th:src="@{/static/img/logo.gif}" />
    </a>
    </div>
    <div class="login_banner">
    <div class="register_form">
    <h1>注册尚硅谷会员</h1>
    <form action="regist_success.html">
    <div class="form-item">
    <div>
    <label>用户名称:</label>
    <input type="text" placeholder="请输入用户名" />
    </div>
    <span class="errMess">用户名应为6~16位数组和字母组成</span>
    </div>
    <div class="form-item">
    <div>
    <label>用户密码:</label>
    <input type="password" placeholder="请输入密码" />
    </div>
    <span class="errMess">密码的长度至少为8位</span>
    </div>
</body>
</html> 
<div class="form-item">
    <div>
    <label>确认密码:</label>
    <input type="password" placeholder="请输入确认密码" />
    </div>
    <span class="errMess">密码两次输入不一致</span>
</div>
<div class="form-item">
    <div>
    <label>用户邮箱:</label>
    <input type="text" placeholder="请输入邮箱" />
    </div>
    <span class="errMess">请输入正确格式的邮箱</span>
</div>

<div class="form-item">
    <div>
    <label>验证码:</label>
    <div class="verify">
    <input type="text" placeholder="" />
    <img th:src="@{/static/img/code.bmp}" alt="" height="40px"/>
    </div>
    </div>
    <span class="errMess">请输入正确的验证码</span>
</div>
    <button class="btn">注册</button>
</form>
</div>
</div>
<div id="bottom">
    <span>
    尚硅谷书城.Copyright &copy;
    </span>
</div>
</body>
</html> 

然后启动项目,单击首页的 “注册”,成功跳转注册页面,如图 14-14 所示。

接下来分别实现注册页面中涉及的验证码展示、表单正则验证,以及用户名唯一验证等功能,并且注册功能完善后进行统一测试。

3. 验证码图片动态展示

(1)在 web.xml 中配置 KaptchaServlet 的映射路径。

<!--kaptchaServlet 的访问路径,生成验证码图片-->
<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</url-pattern>
</servlet-mapping> 

825c5da839b8aafe6f9dcfee09de22361e0c0cbf6573a01f5192e8a91a37210a.jpg

(2)修改 regist.html 页面,实现验证码动态展示功能。

在 regist.html 页面中修改 <img> 标签的访问路径,为 KaptchaServlet 的映射路径,并绑定单击事件。

<img :src="codeSrc" alt="" height="40px" width="100px" @click="changeCode"/> 

在 regist.html 页面 <head> 标签内引入 vue.js 文件,并在 <body> 标签内添加最外层 <div> 标签,id 值为 box,用于挂载 Vue 代码,关键代码如下。

<head>
    <!-- 省略部分代码 -->
    <script th:src="@{/static/script/vue.js}"></script</head>
<body>
    <div id="box">
    <!-- 省略 body 内代码 -->
    </div>
</body>

编写如下 Vue 代码。

<script>
    new Vue({
    el:"#box",
    data: {
    codeSrc:"kaptcha"
    }
    },
    methods: {
    changeCode:function () {
    //time=new Date( )是为了每次的请求路径都不一样,避免浏览器缓存
    this.codeSrc="kaptcha?time="+new Date( );
    }
    }
})
</script> 

4. 表单的正则验证

关于注册页面中的表单项,我们需要对注册用户提出一些数据格式要求,如用户名的组成部分、用户名的长度,密码的长度,以及邮箱的格式是否符合要求等。

首先,将表单以及提示内容和 Vue 的数据模型进行绑定。其次,在表单元素上分别添加事件:输入框添加 blur 失去焦点事件;表单上添加 submit 提交事件,并且在 Vue 中创建函数进行数据的正则验证。修改后的 regist.html 文件示例代码如下。

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8" />
    <title>尚硅谷会员注册页面</title>
    <link type="text/css" rel="stylesheet" th:href="@{/static/css/style.css}" />
    <link rel="stylesheet" th:href="@{/static/css/register.css}" />
    <style type="text/css">
    .login_form {
    height: 420px;
    margin-top: 25px;
    }
    </style>
    <script th:src="@{/static/script/vue.js}"></script>
    </head>
<body>
<div id="box">
    <div id="login_header">
    <a th:href="@{/index.html}">
    <img class="logo_img" alt="" th:src="@{/static/img/logo.gif}" />
    </a>
    </div>
    <div class="login_banner">
    <div class="register_form">
    <h1>注册尚硅谷会员</h1>
    <form action="regist_success.html" @submit="checkRegist">
    <div class="form-item">
    <div>
    <label>用户名称:</label>
    <input type="text" placeholder="请输入用户名" name="username"@blur="checkUsername"
    v-model="username"/>
    </div>
    <span class="errMess">{{usernameMsg}}</span>
    </div>
    <div class="form-item">
    <div>
    <label>用户密码:</label>
    <input type="password" placeholder="请输入密码" name="password" @blur="checkPassword"
    v-model="password"/>
    </div>
    <span class="errMess">{{passwordMsg}}</span>
    </div>
    <div class="form-item">
    <div>
    <label>确认密码:</label>
    <input type="password" placeholder="请输入确认密码" @blur="checkConfirmPassword"
    v-model="confirmPassword"/>
    </div>
    <span class="errMess">{{confirmPasswordMsg}}</span>
    </div>
    <div class="form-item">
    <div>
</body>
</html> 
<label>用户邮箱:</label>
<input type="text" placeholder="请输入邮箱" name="email" @blur="checkEmail"
v-model="email"/>
</div>
<span class="errMess">{{emailMsg}}</span>
</div>
<div class="form-item">
<div>
<label>验证码:</label>
<div class="verify">
<input type="text" placeholder="" name="code".v-model="code" @blur="checkCode"/>
<img :src="codeSrc" alt="" height="40px" width="100px" @click="changeCode"/>
</div>
</div>
<span class="errMess">{{codeMsg}}</span>
</div>
<button class="btn">注册</button>
</form>
</div>
</div>
<div id="bottom">
<span>
尚硅谷书城.Copyright &copy;
</span>
</div>
</div>
<script>
new Vue(
{
    el:"#box",
    data: {
    username:H",
    usernameMsg: "用户名应为6~16位数组和字母组成",
    password:H",
    passwordMsg: "密码的长度至少为8位",
    confirmPassword:H",
    confirmPasswordMsg: "密码两次输入不一致",
    email:H",
    emailMsg: "请输入正确的邮箱格式",
    code:H",
    codeMsg: "请输入验证码",
    codeSrc: "kaptcha",
    usernameFlag: false,
    passwordFlag: false,
    confirmPasswordFlag: false,
    emailFlag: false,
    codeFlag: false,
    },
    methods: {
    changeCode:function () {
    //time=new Date()是为了每次的请求路径都不一样,避免浏览器缓存
    this.codeSrc="kaptcha?time="+new Date();
    },
    checkUsername:function () {
    //校验用户名是否符合要求
    }
} 
var usernameReg=/^[a-zA-Z0-9]{6,16}$/;
if(usernameReg.test(this.username)){
    this.usernameMsg="√";
    this.usernameFlag=true;
}else{
    this.usernameMsg="用户名应为6~16位数组和字母组成"
    this.usernameFlag=false;
},
checkPassword:function() {
    var passwordReg=/^[a-zA-Z0-9_]{8,}$/
    if(passwordReg.test(this.password)){
    this.passwordMsg="√"
    this.passwordFlag=true;
}else{
    this.passwordMsg="密码的长度至少为8位"
    this.passwordFlag=false;
}
},
checkConfirmPassword:function() {
    if(this.password==this.confirmPassword){
    this.confirmPasswordMsg="√"
    this.confirmPasswordFlag=true;
}else{
    this.confirmPasswordMsg="密码两次输入不一致"
    this.confirmPasswordFlag=false;
}
},
checkEmail:function() {
    var emailReg=/^[a-zA-Z0-9_\.-]+@([a-zA-Z0-9-]+[\.]{1})+[a-zA-Z]+\$/;
    if(emailReg.test(this.email)){
    this.emailMsg="√"
    this.emailFlag=true;
}else{
    this.emailMsg="请输入正确的邮箱格式"
    this.emailFlag=false;
}
},
checkCode:function() {
    if(this.code=='') {
    this.codeMsg='验证码不能为空';
    this.codeFlag=true;
}else{
    this.codeMsg='';
    this.codeFlag=false;
}
},
checkRegist:function() { 
if(!(this.usernameFlag&&this.passwordFlag&&this.confirmPasswordFlag&&this.emailFlag() && this.codeFlag)) {
    event.preventDefault()
    }
} 
}
)
</script>
</body>
</html> 

5. 用户名唯一性验证

关于用户名需要一个特殊的验证,即要求用户注册时使用的用户名是唯一的。在注册之前,需要根据用户输入的用户名去数据库查询,如果能查询到结果,就说明该用户名已被其他用户注册,并且不能重复使用;如果查询不到结果,就说明该用户名可用。这里,我们采用 AJAX 异步请求进行用户名唯一性的验证。具体步骤如下。

(1)在 regist.html 文件中引入 Axios 框架的支持文件。

<script th:src="@{/static/script/axios.min.js}"></script> 

(2)修改 checkUsername 函数,在用户名格式验证成功后,发送异步请求到服务器端。

checkUsername:function () {
    //校验用户名是否符合要求
    var usernameReg=/^[a-zA-Z0-9]{6,16}$/;
    if(usernameReg.test(this.username)) {
    //当用户名格式已经正确,就可以发送异步请求到后台,进行用户名重复的验证
    axios({
    method:"post",
    url:"checkUsername",
    params:{
    username:this.username
    }
    }).then(response => {
    var result=response.data; //{flag:true/false}
    if(result.flag) {
    this.usernameMsg="√"
    this.usernameFlag=true;
    }else{
    this.usernameMsg="用户名重复,请更换!"
    this.usernameFlag=false;
    }
    });
    }else{
    this.usernameMsg="用户名应为6~16位数组和字母组成"
    this.usernameFlag=false;
    }
} 

(3)创建 CheckUsernameServlet 类,继承 HttpServlet,进行用户名验证的请求处理,示例代码如下。

package com.atguigu.servlet.model;
//省略 import 语句
@Servlet("/checkUsername")
public class CheckUsernameServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    this.doPost(request, response);
    }
    @Override 
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    //1. 获取请求参数
    String username = request.getParameter("username");
    //2. 调用业务层处理业务
    UserService userService=new UserServiceImpl();
    User user = userService.checkUsername(username);
    //3. 给响应
    PrintWriter writer = response.getWriter();
    CommonResult commonResult=null;
    if(user==null){
    commonResult=CommonResult.ok();
    }else{
    commonResult=CommonResult.error();
    }

    //将 CommonResult 写回去
    String s = new Gson().toJson(commonResult);
    System.out.println("s = " + s);///{flag:true/false}
    writer.write(s);
} 

(4)完成业务层和持久层相应操作。

业务层创建 UserService 接口,添加验证用户名是否重复的方法,示例代码如下。

package com.atguigu.service;
import com.atguigu.bean.User;

public interface UserService {
    //验证用户名是否重复
    User checkUsername(String username);
} 

业务层创建 UserServiceImpl 类,实现 UserService 接口,示例代码如下。

package com.atguigu.service.impl;
//省略 import 语句
public class UserServiceImpl implements UserService {
    privateilikDaoNASDAQ = new UserDaoImpl();
    @Override
    public User checkUsername(String username) {
    return userDao.findUserByUsername(username);
    }
} 

持久层创建 UserDao 接口,示例代码如下。

package com.atguigu.dao;
import com.atguigu.bean.User;
import java.sql.SQLException;

public interface UserDao {
    User findUserByUsername(String username);
} 

持久层创建 UserDaoImpl 类,实现 UserDao 接口,示例代码如下。

package com.atguigu.dao.impl; //省略 import 语句
public class UserDaoImpl implements UserDao {
    private QueryRunner runner=new QueryRunner();

    @Override
    public User findUserByUsername(String username) {

    try {
    Connection connection = JDBCTools.getConnection();
    String sql="select * from t_users where username=?";
    return runner.query(connection,sql,new BeanHandler<User>(User.class),username);
    }catch (SQLException e) {
    e.printStackTrace();
    }finally {
    try {
    JDBCTools.freeConnection();
    }catch (SQLException e) {
    throw new RuntimeException(e);
    }
    }
    return null;
    }
} 

6. 单击注册按钮进行数据提交

前面已经对数据的格式进行了验证,也对用户名的唯一性进行了验证,接下来单击 “注册” 按钮进行表单的提交,将数据保存到数据库中完成注册功能。

(1)创建 RegistServlet 类,处理表单提交请求,注册成功跳转到注册成功页面,注册失败跳转到原页面,示例代码如下。

package com.atguigu.servlet.model;
//省略 import 语句

@WebServlet("/regist")
public class RegistServlet extends++,ViewBaseServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws,%";
    ServletException, IOException {
    this doPost(request, response);
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws,%";
    ServletException, IOException {
   ária
   ária = newáriaImpl();
    //1. 获取请求参数
    //1.1 获取用户的信息数据
    Map<String, String[]> parameterMap = request.getParameterMap();
    User user=new User();
    try {
    BeanUtils populate(user, parameterMap);
    } catch (IllegalAccessException e) {
    e.printStackTrace();
    } catch (InvocationTargetException e) {
    e.printStackTrace();
    }
} 
//1.2 获取验证码数据
String code = request.getParameter("code");
System.out.println("用户输入验证码:"+code);
//从 session 中根据 KAPTCHA_SESSION_KEY 获取正确验证码
Object kaptcha_session_key = request.getSession().getAttribute("KAPTCHA_SESSION_KEY");
System.out.println("正确验证码:" + kaptcha_session_key);

boolean regist=false;
if (code.equals(kaptcha_session_key)) {
    //验证码正确
    //2. 调用业务层处理业务
    regist = userService.regist(user);
}
//3. 给响应
if (regist) {
    processTemplate("user/regist_success",request,response);
} else {
    request.setAttribute("codeErrMsg","验证码错误");
    processTemplate("user/regist",request,response);
}
} 

(2)将表单请求路径设置为 RegistServlet 的请求路径。

<form action="regist" @submit="checkRegist"> 

(3)完成业务层和持久层的相应操作。

在 UserService 接口中,添加将用户信息保存到数据库的方法,示例代码如下。

boolean regist(User user); 

在 UserServiceImpl 实现类中,实现 UserService 接口的方法,示例代码如下。

@Override
public boolean regist(User user) {
    //1. 对密码进行加密(待实现)
    user.setPassword(MD5Util.encode(user.getPassword()));
    //2. 新增
    return userDao.saveUser(user);
} 

在 UserDao 接口中,添加向数据库新增用户的方法,示例代码如下。

boolean saveUser(User user); 

在 UserDaoImpl 实现类中,实现 UserDao 接口的方法,示例代码如下。

@Override
public boolean saveUser(User user) {

    try {
    Connection connection = JDBCTools.getConnection();
    String sql="insert into t_users values(null,?,?,?)";
    int update = runner.update(connection, sql, user.getUsername(), user.getPassword(), user.getEmail());
    if (update > 0)
    return true;
    } catch (SQLException e) {
    e.printStackTrace();
    }
    finally { 
try {
    JDBCTools.freeConnection();
} catch (SQLException e) {
    throw new RuntimeException(e);
}
return false; 

如果登录失败,就跳转到 regist.html 页面,需要实现数据回显及错误信息提示,在 Vue 的 data 中增加内容,示例代码如下。

data: {
    username:"[ [\${param.username}]]",
    usernameMsg:"[ [\${param.username==null?'用户名应为6~16位数组和字母组成':'√'}]]",
    password:"[ [\${param.password}] ],
    passwordMsg:"[ [\${param.password==null?'密码的长度至少为8位':'√'}]]",
    confirmPassword:"[ [\${param.password}]]",
    confirmPasswordMsg:"[ [\${param.password==null?'密码两次输入不一致':'√'}]]",
    email:"[ [\${param.email}]]",
    emailMsg:"[ [\${param.email==null?'请输入正确格式的邮箱':'√'}]]",
    code:"[ [\${param.code}]]",
    codeMsg:"[ [\${codeErrMsg==null?'请输入验证码':codeErrMsg]}]]",
    codeSrc:"kaptcha",
    usernameFlag:"[ [\${param.username==null?"+false+":"+true+"}]]",
    passwordFlag:"[ [\${param.password==null?"+false+":"+true+"}]]",
    confirmPasswordFlag:"[ [\${param.password==null?"+false+":"+true+"}]]",
    emailFlag:"[ [\${param.email==null?"+false+":"+true+"}]]",
    codeFlag:"[ [\${param.code==null?"+false+":"+true+"}]]"
} 

下面对注册具体功能进行测试,如图 14-15 所示,填写注册信息。然后单击 “注册”,如图 14-16 所示,跳转至注册成功页面。

3889224118f4883da37a6f4614c7c40ef6afc2f775d0d25b3727cfc828ce2084.jpg

6390035836f5b425087de5b4e68e1e1c55c6a808590eb9a21868f7bc65ad87bb.jpg

14.3.2 Servlet 的优化

至此,该项目的注册功能已经完成。不难发现,针对简单的注册功能已经创建了很多 Servlet,也就是说,后期在实现其他功能时,还会创建更多的 Servlet,因此我们需要对 Servlet 进行优化。保证让相同类型的请求都发送到同一个 Servlet 上,这样就大大减少了 Servlet 的个数。

首先,需要满足一个条件,即将请求路径进行规范化。回顾一下之前请求 Servlet 的路径:去注册页面的请求路径是 /regist.html;检查用户名唯一性的请求路径是 /checkUsername;注册的请求路径是 /regist。这里可以将三个 Servlet 合并成一个 UserServlet,统一处理和用户相关的请求,后面登录的相关请求也发送到 UserServlet。

假设 UserServlet 的访问路径是 /user,那么问题就是如何让一个 Servlet 处理多个不同的请求。解决方案就是,不同的请求都携带一个 key 值相同但 value 值不同的请求参数。例如,user?method=regist.html、user?method=checkUsername、user?method=regist,这样 Servlet 就可以获取 method 的请求参数值,根据 value 值的不同来调用不同的方法,进行不同请求的处理。下面对注册涉及的 Servlet 进行优化。

(1)修改请求路径携带 method 参数。

修改 index.html 页面中注册对应的超链接,示例代码如下。

<a th:href="@{/user?method=regist.html}" class="register">注册</a> 

修改 regist.html 页面中检查用户名唯一性的请求路径,示例代码如下。

axios({
    method:"post",
    url:"user?method=checkUsername",
    params: {
    username:this.username
    }
}).then(response => {
    var result=response.data; // {flag:true/false}
    if(result.flag) {
    this.usernameMsg="√"
    this.usernameFlag=true;
    }else{
    this.usernameMsg="用户名重复,请更换!"
    this.usernameFlag=false;
    }
}); 

修改 regist.html 页面中表单的请求路径,示例代码如下。

<form th:action="@{/user?method=regist}" @submit="checkRegist" method="post">
<!-- 省略表单项代码 -->
</form> 

(2)创建 UserServlet,将 ToRegistPageServlet、RegistServlet、CheckUsernameServlet 三个合并到 UserServlet,示例代码如下。

package com.atguigu.servlet.model;
//省略 import 语句

@Servlet("/user")
public class UserServlet extends++,ViewBaseServlet {

    private UserService userService=newáriaImpl();
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    this doPost(request, response);
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    //获取 method 请求参数
    String method = request.getParameter("method");
    //进行判断
    if(method.equals("regist.html")) {
    toRegistPage(request, response);
    }else if(method.equals("checkUsername")) { 
checkUsername(request, response);
} else if(method.equals("regist")){
    regist(request, response);
}
protected void toRegistPage(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    this.processTemplate("user/regist", request, response);
}
protected void checkUsername(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    //1. 获取请求参数
    String username = request.getParameter("username");
    //2. 调用业务层处理业务
    User user = userService.checkUsername(username);
    //3. 给响应
    PrintWriter writer = response.getWriter();
    CommonResult commonResult=null;
    if(user==null){
    commonResult=CommonResult.ok();
    }else{
    commonResult=CommonResult.error();
    }

    //将 CommonResult 写回去
    String s = new Gson().toJson(commonResult);
    System.out.println("s = " + s); // {flag:true/false}
    writer.write(s);
}

protected void regist(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    //1. 获取请求参数
    //1.1 获取用户的信息数据
    Map<String, String[]> parameterMap = request.getParameterMap();
    User user=new User();
    try {
    BeanUtils populate(user, parameterMap);
    } catch (IllegalAccessException e) {
    e.printStackTrace();
    } catch (InvocationTargetException e) {
    e.printStackTrace();
    }
    //1.2 获取验证码数据
    String code = request.getParameter("code");
    System.out.println("用户输入验证码:"+code);
    //从 session 中根据 KAPTCHA_SESSION_KEY 获取正确的验证码
    Object kaptcha_session_key = request.getSession().getAttribute("KAPTCHA_SESSION_KEY");
    System.out.println("正确验证码:" + kaptcha_session_key);

boolean regist=false;
if(code.equals(kaptcha_session_key)){
    //验证码正确
    //2. 调用业务层处理业务 
regist = userService.regist(user);
}
//3. 给响应
if(regist){
    processTemplate("user/regist_success", request, response);
} else{
    request.setAttribute("codeErrMsg", "验证码错误");
    processTemplate("user/regist", request, response);
}
} 

本节完成了 Servlet 优化,大大减少了 Servlet 文件的个数,注册相关的 ToRegistPageServlet、RegistServlet、CheckUsernameServlet 就可以删除了。

14.3.3 用户登录

用户注册完后就会进行登录,接下来我们将实现登录功能,具体如下。

1. 单击 “登录” 按钮跳转登录页面

(1)修改 index.html 中的 “登录” 超链接,访问路径为 “user?method=login.html”,示例代码如下。<a th:href="@{/user?method=login.html}" class="login"> 登录 </a>

(2)修改 UserServlet,新增一个分支(加粗部分),示例代码如下。

if(method.equals("regist.html")){
    toRegistPage(request, response);
} else if(method.equals("checkUsername")){
    checkUsername(request, response);
} else if(method.equals("regist")){
    regist(request, response);
} else if(method.equals("login.html")){
    toLoginPage(request, response);
} 

在 UserServlet 类中,新增 toLoginPage () 方法实现跳转登录页面,示例代码如下。

protected void toLoginPage(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    this.processTemplate("user/login", request, response);
} 

(3)修改 login.html 页面,示例代码如下。

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8" />
    <title>尚硅谷会员登录页面</title>
    <link type="text/css" rel="stylesheet" th:href="@{/static/css/style.css}" />
</head>
<body>
    <div id="login_header">
    <a th:href="@{/index.html}">
    <img class="logo_img" alt="" th:src="@{/static/img/logo.gif}" />
    </a>
    </div>
    <div class="login_banner">
    <div id="l_content">
    <span class="login_word">欢迎登录</span>
    </div>
    </body>
</html> 
</div>

<div id="content">
    <div class="login_form">
    <div class="login_box">
    <div class="tit">
    <h1>尚硅谷会员</h1>
    </div>
    <div class="msg_cont">
    <b></b>
    <span class="errorMsg">请输入用户名和密码</span>
    </div>
    <div class="form">
    <form action="login_success.html">
    <label>用户名称:</label>
    <input
    class="itxt"
    type="text"
    placeholder="请输入用户名"
    autocomplete="off"
    tabindex="1"
    name="username"
    id="username"
    />
    <br />
    <br />
    <label>用户密码:</label>
    <input
    class="itxt"
    type="password"
    placeholder="请输入密码"
    autocomplete="off"
    tabindex="1"
    name="password"
    id="password"
    />
    <br />
    <br />
    <input type="submit" value="登录" id="sub_btn" />
    </form>
    <div class="tit">
    <a th:href="@{/user?method=regist.html}">立即注册</a>
    </div>
    </div>
    </div>
    </div>
</div>

<div id="bottom">
    <span>
    尚硅谷书城.Copyright &copy;2015
</span>
</div>

</body>
</html> 

重启项目进入首页,单击 “登录” 按钮,如图 14-17 所示,成功跳转登录页面。

252607169e025066fff2421952b3871b8b6cb81c644fe85e6b993343feab9574.jpg

2. 对登录页面表单进行非空校验

<script th:src="@{/static/script/vue.js}"></script> 
<div id="box">
<!-- 省略 body 标签中所有内容 -->
</div>
<script>
new Vue({
    el: "#box",
    data: {
    username: "",
    password: "",
    errMsg: "请输入用户名和密码"
    },
    methods: {
    checkLogin: function () {
    //1. 验证用户名是否为空
    if (this.username=="") {
    //2. 需要在上方进行提示
    this.errMsg="用户名不能为空"
    event.preventDefault();
    return;
    }
    if (this.password=="") {
    this.errMsg="密码不能为空"
    event.preventDefault();
    }
    }
    }
})
</script> 

(3)在表单上绑定提交事件,修改之前 <body> 标签内的代码,具体如下。

<div id="box">
    <div id="login_header">
    <a th:href="@{/index.html}">
    <img class="logo_img" alt="" th:src="@{/static/img/logo.gif}" />
    </a>
    </div>
    <div class="login_banner">
    <div id="1_content">
    <span class="login_word">欢迎登录</span>
    </div>
    <div id="content">
    <div class="login_form">
    <div class="login_box">
    <div class="tit">
    <h1>尚硅谷会员</h1>
    </div>
    <div class="msg_cont">
    <b></b>
    <span class="errorMsg">{{errMsg}}</span>
    </div>
    <div class="form">
    <form th:action="@{/user?flag=login}" @submit="checkLogin" method="post">
    <label>用户名称:</label>
    <input
    class="itxt"
    type="text"
    placeholder="请输入用户名"
    autocomplete="off"
    tabindex="1"
    name="username"
    id="username"
    v-model="username"
    />
    <br />
    <br />
    <label>用户密码:</label>
    <input
    class="itxt"
    type="password"
    placeholder="请输入密码"
    autocomplete="off"
    tabindex="1"
    name="password"
    id="password"
    v-model="password"
    />
    <br />
    <br />
    <input type="submit" value="登录" id="sub_btn" />
    </form>
    <div class="tit">
    <a th:href="@{/user?method=regist.html}">立即注册</a>
    </div>
</div> 
</div>
</div>
</div>
</div>
</div>
<div id="bottom">
<span>
尚硅谷书城.Copyright &copy;2015
</span>
</div>
</div> 

3. 实现登录功能

(1)修改登录表单的 action 属性值,示例代码如下。

<form th:action="@{/user?method=login}" @submit="checkLogin" method="post"> 

(2)修改 UserServlet,新增处理登录请求的代码。

在 UserServlet 的 doPost () 方法中新增一个分支(加粗部分),代码如下。

@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    //获取 method 请求参数
    String method = request.getParameter("method");
    //进行判断
    if (method.equals("regist.html")){
    toRegistPage(request, response);
    } else if (method.equals("checkUsername")){
    checkUsername(request, response);
    } else if (method.equals("regist")){
    regist(request, response);
    } else if (method.equals("login.html")){
    toLoginPage(request, response);
    } else if (method.equals("login")){
    login(request, response);
    }
} 

在 UserServlet 中新增 login () 方法,处理登录请求,示例代码如下。

protected void login (HttpServletResponse request, HttpServletResponse response) throws ServletException, IOException {
    String username = request.getParameter("username");
    String password = request.getParameter("password");
    // 调用业务层进行登录功能操作
    User user = userService.login(username, password);
    // 根据 user 是否有值判断登录是否成功
    if (user != null) {
    request.getSession().setAttribute("user", user);
    processTemplate("user/login_success", request, response);
    } else {
    request.setAttribute("errMsg", "用户名或密码错误");
    processTemplate("user/login", request, response);
    }
} 

(3)完善业务层代码。另外,对于持久层,登录可以使用 UserDao 和 UserDaoImpl 中的 findUserByUsername (String username) 方法查询用户信息。

在 UserService 接口中新增 login () 登录方法,示例代码如下。

User login(String username, String password);

在 UserServiceImpl 实现类中实现 login () 方法,示例代码如下。

@Override
public User login(String username, String password) {
    //1. 使用用户名去查询
    User user = userDao.findUserByUsername(username);
    //2. 做密码的校验(如果注册的时候有加密,需要处理)
    if (user != null) {
    String encode = MD5Util.encode(password);
    if (user.getPassword().equalstag encode)) {
    return user;
    }
    }
    return null;
} 

(4)完善登录和登录成功页面。

修改 login.html 页面中 Vue 代码的 data 内容,示例代码如下。

data: {
    username:"[[\${param.username}]]",
    password:"[[\${param.password}]]", 
    errMsg:"[[\${errMsg==null?'请输入用户名和密码':errMsg}]]"
} 

完善 login_success.html 登录成功页面,示例代码如下。

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>尚硅谷会员注册页面</title>
<link type="text/css" rel="stylesheet" th:href="@{/static/css/style.css}" />
<style type="text/css">
h1 {
text-align: center;
margin-top: 200px;
}
h1 a {
color:red;
}
</style>
</head>
<body>
<div id="header">
<a th:href="@{index.html}">
<img class="logo_img" alt="" th:src="@{static/img/logo.gif}" />
</div>
<span>欢迎<span class="um_span" th:text="${session.user.username}">张总</span>
光临尚硅谷书城</span>
<a href="../order/order.html">我的订单</a>
<a th:href="@{index.html}">注销</a>&nbsp;&nbsp;
<a th:href="@{index.html}">返回</a>
</div> 
</div>
<div id="main">
    <h1>欢迎回来 <a th:href="@{index.html}">转到主页</a></h1>
</div>
<div id="bottom">
    <span>
    尚硅谷书城.Copyright &copy;2015
    </span>
</div>
</body>
</html> 

运行代码,输入已注册的用户信息进行登录,如图 14-18 所示,成功跳转登录成功页面。

8ff8b7c125b2f002e4ffb3fa6b037513727127383e06c1cae9767199b36b19aa.jpg

4. index.html 页面的头信息完善

登录成功后,修改首页展示用户信息。首页的头信息根据 session 中是否有 user 对象判断是否处于登录状态,从而显示不同的内容,如果不处于登录状态就显示 “登录” 和 “注册” 的超链接,如果处于登录状态就显示 “欢迎:XXX” 和 “注销” 的超链接。

<div class="topbar-right" th:if="${session.user==null}">
    <a th:href="@{/user?method=login.html}" class="login">登录</a>
    <a th:href="@{/user?method=regist.html}" class="register">注册</a>
    <a
    href="./pages/cart/cart.html"
    class="cart iconfont icon-gouwuche">
    购物车
    <div class="cart-num">3</div>
    </a>
    <a href="./pages/manager/book_manager.html" class="admin">后台管理</a>
</div>
<!--登录后风格-->
<div class="topbar-right" th:if="${session.user!=null}">
    <span>欢迎你<b th:text="${session.user.username}">张总</b></span>
    <a href="#" class="register">注销</a>
    <a href="pages/cart/cart.jsp" class="cart iconfont icon-gouwuche">
    购物车
    <div class="cart-num">3</div>
    </a>
    <a href="./pages/manager/book_manager.html" class="admin">后台管理</a>
</div> 

运行代码,重新登录用户信息,查看首页,如图 14-19 所示。

2e054f707be8df4a28a4046374d71c8bb7b05798d3340d15e19380777df9d97e.jpg

14.3.4 用户注销

登录完成后,首页会出现注销的超链接。注销功能,就是单击 “注销” 按钮后发送请求至后台 Servlet,然后实现将 session 中的 User 对象移除即可。

(1)修改首页的注销超链接,示例代码如下。

<a th:href="@{/user?method=logout}" class="register">注销</a>

(2)修改 UserServlet,新增注销的相关代码。

在 UserServlet 的 doPost () 方法中新增一个分支(加粗部分),示例代码如下。

@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    //获取 method 请求参数
    String method = request.getParameter("method");
    //进行判断
    if (method.equals("regist.html")){
    toRegistPage(request, response);
    }else if (method.equals("checkUsername")){
    checkUsername(request, response);
    }else if (method.equals("regist")){
    regist(request, response);
    }else if (method.equals("login.html")){
    toLoginPage(request, response);
    }else if (method.equals("login")){
    login(request, response);
    }else if (method.equals("logout")){
    logout(request, response);
    }
} 

在 UserServlet 中新增 logout () 方法,示例代码如下。

protected void logout (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    request.getSession().removeAttribute("user");
    response.sendRedirect(request.getContextPath() + "/index.html");
} 

运行代码,登录成功后,单击首页中 “注销” 按钮后成功注销。

14.4 后台管理模块

后台管理模块主要是对图书的管理,包括图书的查看、图书的添加、图书的修改、图书的删除操作,在首页有一个 “后台管理” 超链接,本项目内并没有涉及权限相关功能,因此该超链接是可以直接单击的,侧重点主要放在功能实现、知识点练习上。

14.4.1 图书列表展示

单击首页的 “后台管理” 超链接,将数据库 t_books 表中的图书数据显示在图书列表页面,暂时不考虑分页功能。

(1)创建 Book 实体类,示例代码如下。

public class Book {
    private Integer bookId;
    private String bookName;
    private String author;
    private Double price;
    private Integer sales;
    private Integer stock;
    private String imgPath;
    // get, set 代码省略
} 

(2)修改 index.html 首页中的 “后台管理” 超链接,示例代码如下。

<a th:href="@{/book?method=findAll}" class="admin"> 后台管理 </a>

需要注意的是,登录前和登录后对应的 <div> 标签中都有 “后台管理” 超链接,这两处需要同步修改。

(3)创建 BookServlet 类,并创建 findAll () 方法处理查询图书的请求,示例代码如下。

package com.atguigu.servlet.model;
//省略 import 语句

@Servlet("/book")
public class BookServlet extends++,viewBaseServlet {

    private BookService bookService=new BookServiceImpl();
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    this doPost(request, response);
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    String method = request.getParameter("method");
    if(method.equals("findAll")) {
    findAll(request, response);
    }
    }

    protected void findAll(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    List<Book> all = bookService.findAll();
    request.setAttribute("books",all);
} 
processTemplate("manager/book_manager", request, response);
} 

(4)完善业务层和持久层代码。

创建 BookService 接口,增添 findAll () 方法,示例代码如下。

public interface BookService {
    List<Book> findAll();
} 

创建 BookServiceImpl 实现类,实现 BookService 接口的 findAll () 方法,示例代码如下。

public class BookServiceImpl implements BookService {
    private BookDao bookDao = new BookDaoImpl();
    @Override
    public List<Book> findAll() {
    return bookDao.findAll();
    }
} 

创建 BookDao 接口,增添 findAll () 方法,示例代码如下。

public interface BookDao {
    List<Book> findAll();
} 

创建 BookDaoImpl 实现类,实现 BookDao 接口的 findAll () 方法,示例代码如下。

public class BookDaoImpl implements BookDao {
    //由dbutils的QueryRunner,变成JdbcTemplate
    private QueryRunner runner=new QueryRunner();
    @Override
    public List<Book> findAll() {
    try {
    Connection connection = JDBCTools.getConnection();
    String sql="select id bookId, title bookName, author, price, sales, stock, img_path imgPath from t_books";
    return runner.query(connection, sql, new BeanListHandler<Book>(Book.class));
    } catch (SQLException e) {
    e.printStackTrace();
    } finally {
    try {
    JDBCTools.freeConnection();
    } catch (SQLException e) {
    throw new RuntimeException(e);
    }
    }
    return null;
    }
} 

(5)修改 pages/manager/ 目录下的 book_manager.html 页面,示例代码如下。

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<link rel="stylesheet" th:href="@{/static/css/minireset.css}" />
<link rel="stylesheet" th:href="@{/static/css/common.css}" />
<link rel="stylesheet" th:href="@{/static/css/cart.css}" />
<link rel="stylesheet" th:href="@{/static/css/bookManger.css}" /> 
</head>
<body>
<div class="header">
    <div class="w">
    <div class="header-left">
    <a th:href="@{/index.html}">
    <img th:src="@{/static/img/logo.gif}" alt=""/></a>
    <h1>图书管理系统</h1>
    </div>
    <div class="header-right">
    <a href="#" class="order">图书管理</a>
    <a th:href="@{/order_manager.html}" class="destory">订单管理</a>
    <a th:href="@{/index.html}" class="gohome">返回商城</a>
    </div>
    </div>
</div>
<div class="list">
    <div class="w">
    <div class="add">
    <a href="book_add.html">添加图书</a>
    </div>
    <table>
    <thead>
    <tr>
    <th>图片</th>
    <th>商品名称</th>
    <th>价格</th>
    <th>作者</th>
    <th>销量</th>
    <th>库存</th>
    <th>操作</th>
    </tr>
    </thead>
    <tbody>
    <tr th:each="book : ${books}">
    <td><img th:src="${book.imgPath}" alt="" /></td>
    <td th:text="${book.bookName}">活着</td>
    <td th:text="${book.price}">100.00</td>
    <td th:text="${book.author}">余华</td>
    <td th:text="${book.sales}">200</td>
    <td th:text="${book.stock}">400</td>
    <td>
    <a th:href="@{book(flag='toEditPage',id=${book.bookId})}">修改</a>
    <a th:href="@{book(flag='delete',id=${book.bookId})}" class="del">删除</a>
    </td>
    </tr>
    </tbody>
</body>
</table>
<div class="footer">
    <div class="footer-right">
    <div>首页</div>
    <div>上一页</div>
    <ul> 
<li class="active">1</li>
<li>2</li>
<li>3</li>
</ul>
<div>下一页</div>
<div>末页</div>
<span>共10页</span>
<span>30条记录</span>
<span>到第</span>
<input type="text" />
<span></span>
<button>确定</button>
</div>
</div>
</div>
<div class="bottom">
<div class="w">
<div class="top">
<ul>
<li>
<a href="">
<img th:src="@{/static/img/bottom1.png}" alt="" />
<span>大咖级讲师亲自授课</span>
</a>
</li>
<li>
<a href="">
<img th:src="@{/static/img/bottom2.png}" alt="" />
<span>课程为学员成长持续赋能</span>
</a>
</li>
<li>
<a href="">
<img th:src="@{/static/img/bottom2.png}" alt="" />
<span>学员真实情况大公开</span>
</a>
</li>
</ul>
</div>
<div class="content">
<dl>
<dt>关于尚硅谷</dt>
<dd>教育理念</dd>
</dl>
<dl>
<dt>资源下载</dt>
<dd>视频下载</dd>
</dl>
<dl>
<dt>加入我们</dt>
<dd>招聘岗位</dd>
</dl>
<dl> 
<dt>联系我们</dt>
<dd>http://www.atguigu.com</dd>
<dd></dd>
</dl>
</div>
</div>
<div class="down">
尚硅谷书城.Copyright ©
</div>
</div>
</body>
</html> 

运行代码,单击首页的 “后台管理”,如图 14-20 所示,成功实现图书列表展示功能。

d680348025433ca64420ceca7e7858ffdc54149f99787d6c382e0e50fb7dee0c.jpg

14.4.2 添加图书

图书列表展示完成后,接下来实现添加图书功能,暂时不考虑图片添加的功能。

(1)修改 book_manager.html 页面的 “添加图书” 超链接,示例代码如下。

<a th:href="@{/book?method=book_add.html}">添加图书</a> 

(2)在 BookServlet 中创建方法进行新增页面的渲染。

在 BookServlet 的 doPost () 方法中新增一个分支(加粗部分),示例代码如下。

@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    String method = request.getParameter("method");
    if (method.equals("forall")) {
    findAll(request, response);
    } else if (method.equals("book_add.html")) {
    toAddPage(request, response);
    } else if (method.equals("add")) {
    add(request, response);
    }
} 

在 BookServlet 中新增 toAddPage () 方法和 add () 方法,处理图书数据的保存请求,示例代码如下。

protected void toAddPage(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
    processTemplate("manager/book_add", request, response);
} 
protected void add(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    Map<String, String[]> parameterMap = request.getParameterMap();
    Book book = new Book();
    try {
    BeanUtils populate(book, parameterMap);
    } catch (IllegalArgumentException e) {
    e.printStackTrace();
    } catch (InvocationTargetException e) {
    e.printStackTrace();
    }
    bookService.insert(book);
    response.sendRedirect(request.getContextPath() + "/book?method = findAll");
} 

(3)修改 pages/manager/ 目录下的 book_add.html 页面,设置页面内表单的请求路径,示例代码如下。

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <link rel="stylesheet" th:href="@{/static/css/minireset.css}" />
    <link rel="stylesheet" th:href="@{/static/css/common.css}" />
    <link rel="stylesheet" th:href="@{/static/css/style.css}" />
    <link rel="stylesheet" th:href="@{/static/css/cart.css}" />
    <link rel="stylesheet" th:href="@{/static/css/bookManger.css}" />
    <link rel="stylesheet" th:href="@{/static/css/register.css}" />
    <link rel="stylesheet" th:href="@{/static/css/book_edit.css}" />
</head>
<body>
<div class="header">
    <div class="w">
    <div class="header-left">
    <a th:href="@{/index.html}">
    <img th:src="@{/static/img/logo.gif}" alt="" />
    </div>
    <h1>添加图书</h1>
    </div>
    <div class="header-right">
    <a href="./book_manager.html" class="order">图书管理</a>
    <a href="./order_manager.html" class="destory">订单管理</a>
    <a th:href="@{/index.html}" class="gohome">返回商城</a>
    </div>
    </div>
</div>
<div class="login_banner">    <div class="register_form">    <form th:action="@{/book?method=add}" method="post">
    <div class="form-item">
    <div>
    <label>名称:</label>
    <input type="text" placeholder="请输入名称" name="bookName"/>
    </div>
    <span class="errMess" style="visibility: visible;"><span>
    </div>
    <div class="form-item"> 
<div>
    <label>价格:</label>
    <input type="number" placeholder="请输入价格" name="price"/>
</div>
    <span class="errMess"></span>
</div>
<div class="form-item">
    <div>
    <label>作者:</label>
    <input type="text" placeholder="请输入作者" name="author"/>
</div>
    <span class="errMess"></span>
</div>
<div class="form-item">
    <div>
    <label>销量:</label>
    <input type="number" placeholder="请输入销量" name="sales"/>
</div>
    <span class="errMess"></span>
</div>
<div class="form-item">
    <div>
    <label>库存:</label>
    <input type="number" placeholder="请输入库存" name="stock"/>
</div>
    <span class="errMess"></span>
</div>

<button class="btn">提交</button>
</form>
</div>
<div class="bottom">
尚硅谷书城.Copyright ©2015
</div>
</body>
</html> 

(4)完善业务层和持久层代码。

在 BookService 接口中,新增 insert () 添加图书方法,示例代码如下。

void insert(Book book); 

在 BookServiceImpl 实现类中实现 insert () 方法,示例代码如下。

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

在 BookDao 接口中,新增 insert () 添加图书方法,示例代码如下。

void insert(Book book); 

在 BookDaoImpl 实现类中实现 insert () 方法,示例代码如下。

@Override
public void insert(Book book) {
    try {
    Connection connection = JDBCTools.getConnection();
    String sql="insert into t_books values(null,?,?,?,?,?,?)";
runner.update(connection, sql, book.getBookName(), book.getAuthor(), book.getPrice(), book.getSales(), book.getStock(), book.getImgPath());
} catch (SQLException e) {
    e.printStackTrace();
} finally {
    try {
    JDBCTools.freeConnection();
    } catch (SQLException e) {
    throw new RuntimeException(e);
    }
} 

运行代码,单击图书管理页面的 “添加图书” 按钮并输入图书信息,如图 14-21 所示,成功添加一条记录,不过输入中文出现了乱码问题。

991e8dd0f765084b875abde5dea08ba36ebf9a8f760704044fce3cfd20daf1ae.jpg

14.4.3 处理请求和响应中文乱码

在添加图书时,如果输入中文会出现乱码问题,可以创建 EncodingFilter 过滤器,处理请求和响应乱码,示例代码如下。

package com.atguigu.filter;
//省略 import 语句

@WebFilter(urlPatterns = "/*")
public class EncodingFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
    servletRequest.setCharacterEncoding("utf-8");
    servletResponse.setContentType("text/html;charset=utf-8");
    filterChain.doFilter(servletRequest, servletResponse);
    }
    @Override
    public void destroy() {
    }
} 

再次运行代码,添加图书信息,如图 14-22 所示,输入中文,无乱码问题。

0e586895f25cc6d970e9fec5c5e45d43f1c74a81257e2aa80522a59962d4513e.jpg

14.4.4 修改图书

修改图书功能是单击修改超链接,跳转至修改页面,页面内显示原始数据,让用户在原始数据的基础上进行修改。修改完成后,用户单击 “保存” 按钮,进行数据的修改。和新增功能一样,暂时不考虑图片添加的功能。具体步骤如下。

(1)完善 “修改” 超链接的请求路径,示例代码如下。

<a th:href="@{/book(method='edit.html',id=${book.bookId})}">修改</a> 

(2)在 BookServlet 中新增代码,跳转到修改页面并展示原始数据。

在 BookServlet 的 doPost () 方法中增加两个分支(加粗部分),示例代码如下。

@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    String method = request.getParameter("method");
    if (method.equals("findAll")) {
    findAll(request, response);
    } else if (method.equals("book_add.html")) {
    toAddPage(request, response);
    } else if (method.equals("add")) {
    add(request, response);
    } else if (method.equals("edit.html")) {
    toEditPage(request, response);
    } else if (method.equals("update")) {
    update(request, response);
    }
} 

在 BookServlet 中新增 toEditPage () 方法和 update () 方法,示例代码如下。

protected void toEditPage(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    String id = request.getParameter("id");
    Book book = bookService.getById(Integer.parseInt(id));
    request.setAttribute("book", book);
    processTemplate("manager/book_edit", request, response);
}
protected void update(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 
Map<String, String[]> parameterMap = request.getParameterMap();
Book book=new Book();
try {
    BeanUtils populate(book, parameterMap);
} catch (IllegalArgumentException e) {
    e.printStackTrace();
} catch (InvocationTargetException e) {
    e.printStackTrace();
}

bookService.update(book);
response.sendRedirect(request.getContextPath() + "/book?method=findAll");
} 

(3)修改 pages/manager/ 目录下的 book_edit.html 页面,设置页面中表单的请求路径,示例代码如下。

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <link rel="stylesheet" th:href="@{/static/css/minireset.css}" />
    <link rel="stylesheet" th:href="@{/static/css/common.css}" />
    <link rel="stylesheet" th:href="@{/static/css/style.css}" />
    <link rel="stylesheet" th:href="@{/static/css/cart.css}" />
    <link rel="stylesheet" th:href="@{/static/css/bookManger.css}" />
    <link rel="stylesheet" th:href="@{/static/css/register.css}" />
    <link rel="stylesheet" th:href="@{/static/css/book_edit.css}" />
</head>
<body>
<div class="header">
    <div class="w">
    <div class="header-left">
    <a th:href="@{/index.html}">
    <img th:src="@{/static/img/logo.gif}" alt=""/></a>
    <h1>编辑图书</h1>
    </div>
    <div class="header-right">
    <a href="./book_manager.html" class="order">图书管理</a>
    <a href="./order_manager.html" class="destory">订单管理</a>
    <a th:href="@{/index.html}" class="gohome">返回商城</a>
    </div>
    </div>
</div>
<div class="login banner">
    <div class="register_form">
    <form th:action=" @{/book?method=update}" method="post">
    <input type="hidden" name="bookId" th:value="${book.bookId}">
    <div class="form-item">
    <div>
    <label>名称:</label>
    <input type="text" placeholder="请输入名称" name="bookName" th:value="${book.bookName}"
    </div>
    <span class="errMess" style="visibility: visible;">请输入正确的名称</span>
    </div>
    <div class="form-item">
    <div> 
<label>价格:</label>
    <input type="number" placeholder="请输入价格" name="price" th:value="${book.price}" />
</div>
<span class="errMess">请输入正确数字</span>
</div>
<div class="form-item">
    <div>
    <label>作者:</label>
    <input type="text" placeholder="请输入作者" name="author" th:value="${book.author}" />
</div>
<span class="errMess">请输入正确作者</span>
</div>
<div class="form-item">
    <div>
    <label>销量:</label>
    <input type="number" placeholder="请输入销量" name="sales" th:value="${book.sales}" />
</div>
<span class="errMess">请输入正确销量</span>
</div>
<div class="form-item">
    <div>
    <label>库存:</label>
    <input type="number" placeholder="请输入库存" name="stock" th:value="${book.stock}" />
</div>
<span class="errMess">请输入正确库存</span>
</div>

<button class="btn">提交</button>
</form>
</div>
<div class="bottom">
    尚硅谷书城.Copyright ©2015
</div>
</body>
</html> 

(4)完善业务层和持久层代码。

在 BookService 接口中新增 getById () 方法和 update () 方法,示例代码如下。

Book getById(Integer id);
void update(Book book); 

在 BookServiceImpl 实现类中,实现 BookService 接口新增方法,示例代码如下。

@Override
public Book getById(Integer id) {
    return bookDao.getById(id);
}
@Override
public void update(Book book) {
    bookDao.update(book);
}

在 BookDao 接口中,新增 getById () 方法和 update () 方法,示例代码如下。

Book getById(Integer id);
void update(Book book); 

在 BookDaoImpl 实现类中,实现 BookDao 接口新增方法,示例代码如下。

@Override 
public Book getById(Integer id) {
    try {
    Connection connection = JDBCTools.getConnection();
    String sql="select id bookId, title bookName, author, price, sales, stock, img_path imgPath from t_books where id=?";
    return runner.query(connection, sql, new BeanHandler<Book>(Book.class), id);
    } catch (SQLException e) {
    e.printStackTrace();
    } finally {
    try {
    JDBCTools.freeConnection();
    } catch (SQLException e) {
    throw new RuntimeException(e);
    }
    }
    return null;
}

@Override
public void update(Book book) {
    try {
    Connection connection = JDBCTools.getConnection();
    String sql="update t_books set title=?, author=?, price=?, sales=?, stock=? where id=?”;
    runner.update(connection, sql, book.getBookName(), book.getAuthor(), book.getPrice(),
    book.getSales(), book.getStock(), book.getBookId());
    } catch (SQLException e) {
    e.printStackTrace();
    throw new RuntimeException();
    } finally {
    try {
    JDBCTools.freeConnection();
    } catch (SQLException e) {
    throw new RuntimeException(e);
    }
    }
} 

运行代码,修改前面出现中文乱码的图书,如图 14-23 所示,修改成功。

e631e783a4b0565296f6bac007f8f61fec61afaaf4c695eaa848256bb807b214.jpg

14.4.5 删除图书

删除图书的流程相对简单,单击 “删除” 超链接,将当前图书从数据库中移除即可。具体步骤如下。

(1)修改 “删除” 超链接,示例代码如下。

<a th:href="@{book(method='delete',id=${book.bookId})}" class="del">删除</a> 

(2) BookServlet 新增处理删除功能的代码。

在 BookServlet 的 doPost () 方法中新增一个分支(加粗部分),示例代码如下。

@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    String method = request.getParameter("method");
    if (method.equals("findAll")) {
    findAll(request, response);
    } else if (method.equals("book_add.html")) {
    toAddPage(request, response);
    } else if (method.equals("add")) {
    add(request, response);
    } else if (method.equals("edit.html")) {
    toEditPage(request, response);
    } else if (method.equals("update")) {
    update(request, response);
    } else if (method.equals("delete")) {
    delete(request, response);
    }
} 

在 BookServlet 中实现 delete () 方法,示例代码如下。

protected void delete (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    String id = request.getParameter("id");
    bookService.delete(Integer.parseInt(id));
    response.sendRedirect(request.getContextPath() + "/book?method=findAll");
} 

(3)完善业务层和持久层代码。

在 BookService 接口中新增 delete () 方法,示例代码如下。

void delete(Integer id); 

在 BookServiceImpl 实现类中,实现 BookService 接口新增方法,示例代码如下。

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

在 BookDao 接口中新增 delete () 方法,示例代码如下。

void delete(Integer id); 

在 BookDaoImpl 实现类中,实现 BookDao 接口新增方法,示例代码如下。

@Override
public void delete(Integer id) {
    try {
    Connection connection = JDBCTools.getConnection();
    String sql="delete from t_books where id=?";
    runner.update(connection, sql, id);
    } catch (SQLException e) {
    e.printStackTrace(); 
}finally {
    try {
    JDBCTools.freeConnection();
    }catch (SQLException e) {
    throw new RuntimeException(e);
    }
} 

运行代码,单击最后一条图书记录对应的 “删除”,发现删除成功,表示成功实现该删除功能。

14.5 前台图书展示

同样在 index.html 首页中,展示数据库中真实的图书信息,接下来修改相关代码实现此功能。

(1)修改 IndexServlet 中的 doPost () 方法,查询图书数据,存放在 HttpServletRequest 请求域内。

@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    BookService bookService=new BookServiceImpl();
    List<Book> all =bookService.findAll();
    request.setAttribute("books",all);
    this.processTemplate("index",request,response);
} 

(2)在 index.html 页面中,找到 class="list-content" 的 <div> 标签,替换成下面的内容,将请求域中数据渲染到页面上。

<div class="list-content">
    <div class="list-item" th:each="book : ${books}">
    <img th:src="${book.imgPath}" alt="">
    <p>书名:<span th:text="${book.bookName}">活着</span></p>
    <p>作者:<span th:text="${book.author}">余华</span></p>
    <p>价格:¥<span th:text="${book.price}">66.6</span></p>
    <p>销量:<span th:text="${book.sales}">230</span></p>
    <p>库存:<span th:text="${book.stock}">1000</span></p>
    <button>加入购物车</button>
    </div>
</div> 

运行代码,查看首页,如图 14-24 所示,成功展示图书信息。

04c589592f7293ab3fe6165d8da1feb7d43cf62eb6b5ed5ddce025f622e6faf3.jpg

14.6 购物车功能

购物车中的数据我们选择存储在 session 中,以达到对 session 进行练习目的。购物车功能我们采用前后端分离的方式实现,也就是使用 Vue 框架发送异步请求。购物车功能包括加入购物车、查看购物车、购物车数量的修改、购物车删除商品、购物车清空,以及结账功能。

14.6.1 准备工作

(1)创建 CartItem 类,存储购物项数据,示例代码如下。

package com.atguigu.bean;
import java.math.BigDecimal;

/**
 * 购物项
 * 购物车中一种商品的具体信息
 * 注意:对 setBook/setCount/进行有参构造,添加了 amount 的计算代码
 */
public class CartItem {
    private Book book; // 书的信息
    private Integer count; // 数量
    private Double amount; // 价格 (书的单价和数量计算得到的结果)

    // 计算金额 (书的单价*数量)
    public void setAmount() {
    if (this.book != null) {
    BigDecimal b1 = new BigDecimal(this.book.getPrice()+""); 
    BigDecimal b2 = new BigDecimal(this.count +"); 
    this.amount = b1.multiply(b2).doubleValue();
    }
    }

    public CartItem(Book book, Integer count) {
    this.book = book;
    this.count = count;
    setAmount();
    }

    public void setBook(Book book) {
    this.book = book;
    setAmount();
    }

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

    // 省略部分代码
} 

(2)创建 Cart 类,存储购物车数据,示例代码如下。

package com.atguigu.bean; 
public class Cart {
    private Integer totalCount; // 总数量
    private Double totalAmount; // 总金额
    // 将书的 id 值设置 Map 集合的 key
    private Map<Integer, CartItem> cartItemMap = new HashMap<>();
    // 该 Map 用于存储很多个购物项
    // 省略 get/set 方法
}

14.6.2 加入购物车

在首页单击商品下方的 “加入购物车” 按钮,发送异步请求,完成购物车的添加,具体步骤如下。

(1)修改 index.html 首页相关代码。

在首页中,引入 vue.js 和 axios.min.js 文件,示例代码如下。

<script th:src="@{/static/script/vue.js}"></script>
<script th:src="@{/static/script/axios.min.js}"></script> 

在 “加入购物车” 按钮上绑定单击事件,示例代码如下。

<!--将图书的 id 值存储在按钮的 id 属性上-->
<button @click="addCart" th:id="${book.bookId}">加入购物车</button>

并添加 Vue 组件,创建函数发送异步请求,示例代码如下。

<script>
new Vue({
    el:"#app",
    data: {
    totalCount:"[{$session.cart!=null?session.cart.totalCount:0}]]", //网页刷新会导致Vue重新渲染,因此需要让totalCount的值和购物车的总数量相同
},
methods: {
    addCart:function () {
    //1. 获取这本书的id值(变为获取当前单击按钮的id属性值)
    var id=event.target.id;
    //2. 向后台发送异步请求
    axios({
    method:"post",
    url:"cart",
    params: {
    method:"addCart",
    id:id
    }
    }).then(response=>{ //response.data : {flag:true,resultData:num}
    if(response.data.flag) {
    alert("添加购物车成功");
    this.totalCount=response.data.resultData
    }
    })
    }
    }
})
</script> 

(2)创建 CartServlet 类,处理加入购物车请求,示例代码如下。

package com.atguigu.servlet.model;
//省略 import 语句

@Servlet("/cart")
public class CartServlet extends++,ViewBaseServlet {
    private BookService bookService=new BookServiceImpl();

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    this doPost(request, response);
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    String method = request.getParameter("method");
    if(method.equals("addCart")) {
    addCart(request, response);
    }
    }

    protected void addCart(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    //1. 获取书的 id 值
    String id = request.getParameter("id");
    System.out.println("id = " + id); //知道用户点的是哪本书了
    //2. 调用业务层根据 id 查到书的信息(book 就是我们要添加到购物车内的书)
    Book book = bookService.getElementById(Integer.parseInt(id));
    //3. 判断此次加入购物车是第几次单击的(从 session 域内判断是否存在 Cart 对象)

    HttpSession session = request.getSession();
    Cart cart=(Cart)session.getAttribute("cart"); // key 暂时写 cart
    if(cart==null){
    //说明第一次单击加入购物车
    cart=new Cart();
    session.setAttribute("cart", cart);
    }

    //4. 将图书的信息,添加到购物车
    cart.addCart(book);
    //5. 给响应
    //响应中数据除了 ok 成功状态,还需要将购物车的总数量响应回去
    Integer totalCount = cart.getTotalCount();
    CommonResult commonResult = CommonResult.ok().setResultData(totalCount);
    String s = new Gson().toJson(commonResult);
    System.out.println("s = " + s); // {flag:true, resultData:num}
    response.getWriter().write(s);
    }
} 

(3)在 Cart 类中添加 addCart () 方法,将图书加入 Cart 对象的 Map 集合内,并编写代码计算购物车总

数量,示例代码如下。

public void addCart(Book book) {
    //判断这本图书是否是第一次添加?(判断 cartItemMap 中是否有这本书)
    CartItem cartItem = cartItemMap.get(book.getBookId());
    if (cartItem == null) {
    //说明这本图书之前没有添加过购物车
    CartItem cartItem1 = new CartItem(book, 1);
    cartItemMap.put(book.getBookId(), cartItem1);
    } else {
    //说明这本图书之前添加过购物车
    cartItem.setCount(cartItem.getCount() + 1);
    }
    System.out.println(cartItemMap); //为了查看 Map 集合内的东西
} 
public Integer getTotalCount() {
    Collection<CartItem> values = getCartItems();
    Integer totalCount=0;
    for (CartItem value : values) {
    totalCount += value.getCount();
    }
    this.totalCount=totalCount;
    return this.totalCount;
} 
public Collection<CartItem> getCartItems() {
    return cartItemMap.values();
} 

(4)再次修改首页,右上角的 “购物车” 超链接上的数字显示为实际购物车数量。

<div class="cart-num">{{totalCount}}</div> 

注意,登录前后两个位置需要同步修改。然后运行代码,查看首页,如图 14-25 所示。

f9b977773580fb642377e8d01caa176f7a14f99e20cc957d11e4c9fd5dae6dfb.jpg

当前尚未单击 “加入购物车”,右上角显示数量为 0,然后单击 “解忧杂货店” 对应的 “加入购物车” 按钮,如图 14-26 所示,弹出提示框,表明添加成功。

11f986705c14415935dae7203fc1c71e4f9f711111263696d7fad6795ea9cb32.jpg

单击 “确定” 按钮后,查看右上角购物车数量,由 0 变为 1,表明数量计算成功,如图 14-27 所示。

bf1d90b1bba1edf2ade1e53dfd7f2be897962aaba309c470fd636c90455844e4.jpg

14.6.3 查看购物车

单击首页上 “购物车” 超链接,跳转至购物车展示页面,利用 Vue 钩子函数发送异步请求到服务器端,获取购物车的相关数据,再通过 Vue 渲染到页面上,具体步骤如下。

(1)修改首页中 “购物车” 超链接的请求路径,示例代码如下。

<a th:href="@{/cart?method=toCartPage}" class="cart iconfont icon-gouwuche">
购物车
<div class="cart-num">{{totalCount}}</div>
</a> 

注意,登录前后两个位置需要同步修改。

(2)修改 CartServlet 类,编写代码处理购物车查看功能。

在 CartServlet 类的 doPost () 方法中新增以下两个分支(加粗部分),示例代码如下。

@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    String method = request.getParameter("method");
    if (method.equals("addCart")) {
    addCart(request, response);
    } else if (method.equals("toCartPage")) {
    toCartPage(request, response);
    } else if (method.equals("showCart")) {
    showCart(request, response);
    }
} 

在 CartServlet 中新增方法,toCartPage () 方法实现去购物车页面;showCart () 方法实现展示购物车信息。示例代码如下。

protected void toCartPage(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
    processTemplate("cart/cart",request,response);
}

protected void showCart(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    //1. 获取购物车所需要的数据
    HttpSession session = request.getSession();
    Cart cart = (Cart)session.getAttribute("cart");

    responseData(cart,request,response);
}

protected void responseData(Cart cart, HttpServletResponse request, HttpServletResponse response) throws ServletException, IOException {
    Integer totalCount=0;
    Double totalAmount=0D;
    Collection<CartItem> cartItems=new ArrayList<>();
    if(cart!=null){
    totalCount=cart.TotalCount();
    totalAmount=cart.TotalAmount();
    cartItems=cart.getCartItems();
    }
    //2. 将数据响应给 JavaScript
    List cartAll=new ArrayList();///[totalCount,totalAmount,[{{},{},{}}...]]
    cartAll.add(totalCount);
    cartAll.add(totalAmount);
    cartAll.add(cartItems);
    CommonResult commonResult = CommonResult.ok().setResultData(cartAll);
    ///{flag:true, resultData:[totalCount,totalAmount, [{},{},{}}...],}]
    String s = new Gson().toJson(commonResult);

    response.getWriter().write(s);
} 

(3)Cart 类中新增 getTotalAmount () 方法,获取购物车总金额,示例代码如下。

Collection<CartItem> values = getCartItems();
BigDecimal b1=new BigDecimal("0");
for (CartItem value : values) {
    BigDecimal b2=new BigDecimal(value.getAmount()+"");
    b1=b1.add(b2);
}
this.totalAmount=b1.doubleValue();
return this.totalAmount;
} 

(4)修改 pages/Cart/ 目录下的 cart.html 购物车页面,示例代码如下。

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<link rel="stylesheet" th:href="@{/static/css/minireset.css}" /> 
<link rel="stylesheet" th:href="@{/static/css/common.css}" />
<link rel="stylesheet" th:href="@{/static/css/cart.css}" />

<script th:src="@{/static/script/vue.js}"></script>
<script th:src="@{/static/script/axios.min.js}"></script>
</head>
<body>
<div id="app">
<div class="header">
<div class="w">
<div class="header-left">
<a th:href="@{/index.html}">
<img th:src="@{/static/img/logo.gif}" alt=""/></a>
<h1>我的购物车</h1>
</div>
<div class="header-right">
<h3>欢迎<span>张总</span>光临尚硅谷书城</h3>
<div class="order"><a href="order? flag=showOrder">我的订单</a></div>
<div class="destory"><a href="index.html">注销</a></div>
<div class="gohome">
<a th:href="@{/index.html}">返回</a>
</div>
</div>
</div>
</div>
<div class="list">
<div class="w">
<table>
<thead>
<tr>
<th>图片</th>
<th>商品名称</th>
<th>数量</th>
<th>单价</th>
<th>金额</th>
<th>操作</th>
</tr>
</thead>
<tbody v-if="totalCount==0">
<tr>
<td colspan="6">
<b>购物车为空,请单击继续购物</b>
</td>
</tr>
</tbody>
<tbody v-else>
<tr v-for="cartItem in cartItemList">
<td>
<img :src="cartItem.book.imgPath" alt="" />
</td>
<td>{{cartItem.book.bookName}}</td>
<td>
<!--将图书的id值,放在input表单的id属性上-->
<span class="count" @click="subCount">-</span>
<input class="count-num" type="text" v-model="cartItem.count": id="cartItem.book. 
bookId" @change="changeCount"/>
    <span class="count" @click="addCount">+</span>
    </td>
    <td>{{cartItem.book.price}}</td>
    <td>{{cartItem.amount}}</td>
    <td><a href="" @click.prevent="deleteCartItem" :id="cartItem.book.bookId">删除</a></td>
    </tr>
    </tbody>
    </table>
    <div class="footer">
    <div class="footer-left">
    <a href="#" class="clear-cart" @click.prevent="clearCart">清空购物车</a>
    <a href="#">继续购物</a>
    </div>
    <div class="footer-right">
    <div>共<span>{{totalCount}}</span>件商品</div>
    <div class="total-price">总金额<span>{{totalAmount}}</span>元</div>
    <a class="pay" href="order?flag=checkout">去结账</a>
    </div>
    </div>
    </div>
    </div>
    <div class="bottom">
    <div class="w">
    <div class="top">
    <ul>
    <li>
    <a href="">
    <img src="static/img/bottom1.png" alt="" />
    <span>大咖级讲师亲自授课</span>
    </a>
    </li>
    <li>
    <a href="">
    <img src="static/img/bottom2.png" alt="" />
    <span>课程为学员成长持续赋能</span>
    </a>
    </li>
    <li>
    <a href="">
    <img src="static/img/bottom2.png" alt="" />
    <span>学员真实情况大公开</span>
    </a>
    </li>
    </ul>
    </div>
    <div class="content">
    <dl>
    <dt>关于尚硅谷</dt>
    <dd>教育理念</dd>
    <!-- <dd>名师团队</dd><dd>学员心声</dd> -->
    </dl>
    <dl>
    <dt>资源下载</dt> 
<dd>视频下载</dd>
    <!-- <dd>资料下载</dd><dd>工具下载</dd> -->
</dl>
    <dl>
    <dt>加入我们</dt>
    <dd>招聘岗位</dd>
    <!-- <dd>岗位介绍</dd><dd>招贤纳士</dd> -->
</dl>
    <dl>
    <dt>联系我们</dt>
    <dd>http://www.atguigu.com</dd>
    <dd></dd>
    </dl>
    </div>
</div>
<div class="down">
    尚硅谷书城.Copyright ©2015
</div>
</div>
</div>
<script>
new Vue({
    el: "#app",
    data: {
    totalCount: "0",
    totalAmount: "0",
    cartItemList: []
    },
    methods: {},
    created:function () {
    //在Vue对象创建完后去发送异步请求,获取到购物车的数据,赋值给数据模型
    axios({
    method: "post",
    url: "cart",
    params: {
    method: "showCart"
    }
    }).then(
    response => {
    var result = response.data;
    if (result.flag) {
    //将数据复制给数据模型
    this.totalCount = result.resultData[0];
    this.totalAmount = result.resultData[1];
    this.cartItemList = result.resultData[2];
    }
    }
    }
    }
    }
}
</script>
</body>
</html> 

运行代码,单击首页的 “购物车” 超链接,成功跳转购物车页面。如图 14-28 所示,由于尚未添加图

书到购物车,所以数据为空。然后回到首页,添加几本图书到购物车,如图 14-29 所示。

89c32011e74bf187fafe8937e69a2d0a958d1ce1d164177eb354f4ac95b1dd48.jpg

cfaefb80332635143828a63da451385bd247e9542e87d23930a371e46d7992db.jpg

从图 14-29 中可知,添加购物车成功,并成功展示图书信息。

14.6.4 购物车中数量的加减和修改

在购物车页面,我们可以对购物项的数量进行增加或减少,也可以直接修改数量。实现思路是在加号 / 减号上绑定 click 事件,在数量的文本框上绑定 change 事件,然后创建对应的函数发送异步请求到服务器端进行数量的改变。具体步骤如下。

(1)修改 cart.html 页面代码。

在该页面,图书所对应的加号 / 减号,以及数量的文本框上绑定事件,示例代码如下。

<td>
    <!--将图书的 id 值,放在了 input 表单的 id 属性上-->
    <span class="count" @click="subCount">-</span>
    <input class="count-num" type="text" v-model="cartItem.count":id="cartItem.book.bookId"
    @change="changeCount"/>
    <span class="count" @click="addCount">+</span>
</td>

修改 Vue 代码,在 methods 中创建如下函数,发送异步请求到服务器端,示例代码如下。

addCount:function () {
    //获得到图书的id值
    var id=event.target.previousElementSibling.id;
    axios({
    method:"post",
    url:"cart",
    params({
    method:"addCount",
    id:id 
}
}).then(response=>{
    var result=response.data
    if(result.flag){
    //将数据复制给数据模型
    this.totalCount=result.resultData[0];
    this.totalAmount=result.resultData[1];
    this.cartItemList=result.resultData[2];
    }
})
subCount:function () {
    var id=event.target.nextElementSibling.id;
    axios({
    method:"post",
    url:"cart",
    params({
    method:"subCount",
    id:id 
    }
    }).then(response=>{
    var result=response.data
    if(result.flag){
    //将数据复制给数据模型
    this.totalCount=result.resultData[0];
    this.totalAmount=result.resultData[1];
    this.cartItemList=result.resultData[2];
    }
    })
},
changeCount:function () {
    var id=event.target.id;
    //count可以做一个正则验证,非数字提示非法字符,不进行异步请求的发送
    var count=event.target.value;
    axios({
    method:"post",
    url:"cart",
    params({
    method:"changeCount",
    id:id,
    count:count 
    }
    }).then(response=>{
    var result=response.data
    if(result.flag){
    //将数据复制给数据模型
    this.totalCount=result.resultData[0];
    this.totalAmount=result.resultData[1];
    this.cartItemList=result.resultData[2];
    }
    })
}

(2)修改 CartServlet 类,创建方法处理请求。

在 CartServlet 的 doPost () 方法中新增三个分支(加粗部分),示例代码如下。

@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    String method = request.getParameter("method");
    if (method.equals("addCart")) {
    addCart(request, response);
    } else if (method.equals("toCartPage")) {
    toCartPage(request, response);
    } else if (method.equals("showCart")) {
    showCart(request, response);
    } else if (method.equals("addCount")) {
    addCount(request, response);
    } else if (method.equals("subCount")) {
    subCount(request, response);
    } else if (method.equals("changeCount")) {
    changeCount(request, response);
    }
} 

在 CartServlet 中新增方法,addCount () 实现增加购物车数量;subCount () 实现减少购物车数量;changeCount () 实现修改购物车数量,示例代码如下。

protected void addCount ( requests, requests, requests) throws ServletException, IOException {
    String id = request.getParameter("id");
    HttpSession session = request.getSession();
    Cart cart = (Cart session.getAttribute("cart");
    cart.addCount(Integer.parseInt(id));
    responseData(cart, request, response);
}

protected void subCount ( requests, requests, requests) throws ServletException, IOException {
    String id = request.getParameter("id");
    HttpSession session = request.getSession();
    Cart cart = (Cart session.getAttribute("cart");
    cart.subCount(Integer.parseInt(
    id)
    );
    responseData(cart, request, response);
}

protected void changeCount ( requests, requests, requests) throws ServletException, IOException {
    String id = request.getParameter("id");
    String count = request.getParameter("count");
    HttpSession session = request.getSession();
    Cart cart = (Cart session.getAttribute("cart");
    cart.changeCount(Integer.parseInt(id), Integer.parseInt(count));
    responseData(cart, request, response);
} 

(3)在 Cart 类中同样新增 addCount ()、subCount ()、changeCount () 方法,示例代码如下。

public void addCount(Integer id) {
    CartItem cartItem = cartItemMap.get(id);
    cartItem.setCount(cartItem.getCount()+1);
} 
public void subCount(Integer id) {
    CartItem cartItem = cartItemMap.get(id);
    if (cartItem.getCount() == 1) {
    cartItemMap.remove(id);
    } else {
    cartItem.setCount(cartItem.getCount() - 1);
    }
}

public void changeCount(Integer id, Integer count) {
    CartItem cartItem = cartItemMap.get(id);
    cartItem.setCount(count);
} 

运行代码,如图 14-30 所示,测试购物车中图书数量的加减和修改,发现总数量和总金额随之改变,表明修改成功。

ea16745701de77935cfa384fcabbbb99429943408935c297340969ffec692f53.jpg

14.6.5 删除购物项和清空购物车

在购物车页面可以单击删除,将当前购物项从购物车中删除,单击清空购物车,将购物车对象直接从 session 中删除即可。具体步骤如下。

(1)修改 cart.html 文件相关代码。

在 “删除” 和 “清空购物车” 超链接上绑定单击事件,示例代码如下。

<td>
<a href="" @click.prevent="deleteCartItem" :id="cartItem.book.bookId">删除</a>
</td>
<a href="#" class="clear-cart" @click.prevent="clearCart">清空购物车</a> 

修改 Vue 代码,创建对应的函数,发送异步请求到 CartServlet,示例代码如下。

clearCart: function () {
    // 发送请求到服务器端,将购物车数据清空
    axios({
    method: "post",
    url: "cart",
    params: {
    method: "clearCart"
    }
}). then (response => {
    if (response.data.flag) {
    this.totalCount = 0;
    this.totalAmount = 0;
    }
}). 
this.cartItemList = [];
}
})
},
delete react
    deleteCartItem: function () {
    var id = event.target.id; // 获取到购物项图书的 id 值 (删除购物项的条件)
    axios({
    method: "post",
    url: "cart",
    params: {
    method: "deleteCartItem",
    id: id
    }
}). then (response => {
    var result = response.data
    if (result.flag) {
    // 将数据复制给数据模型
    this.totalCount = result.resultData[0];
    this.totalAmount = result.resultData[1];
    this.cartItemList = result.resultData[2];
    }
})
}, 

(2)在 CartServlet 中新增代码处理删除和清空购物车请求。

在 CartServlet 的 doPost () 方法中新增两个分支(加粗部分),示例代码如下。

@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    String method = request.getParameter("method");
    if (method.equals("addCart")) {
    addCart(request, response);
    } else if (method.equals("toCartPage")) {
    toCartPage(request, response);
    } else if (method.equals("showCart")) {
    showCart(request, response);
    } else if (method.equals("addCount")) {
    addCount(request, response);
    } else if (method.equals("subCount")) {
    subCount(request, response);
    } else if (method.equals("changeCount")) {
    changeCount(request, response);
    } else if (method.equals("clearCart")) {
    clearCart(request, response);
    } else if (method.equals("deleteCardItem")) {
    deleteCardItem(request, response);
    }
} 

并在 CartServlet 中创建 deleteCartItem () 和 clearCart () 两个方法,示例代码如下。

protected void clearCart(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    request.getSession().removeAttribute("cart");
    CommonResult ok = CommonResult.ok();
    String s = new Gson().toJson(ok);
    response.getWriter().write(s);
} 
protected void deleteCartItem(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
    //获得到请求参数
    String id = request.getParameter("id");
    HttpSession session = request.getSession();
    //获得到购物车对象
    Cart cart = (Cart) session.getAttribute("cart");
    //删除购物项
    cart.deleteCartItem(Integer.parseInt(id));
    if (cart.getTotalCount()==0) {
    session.removeAttribute("cart");
    }
    //删完之后,该怎么办?
   -responseData(cart, request, response);
} 

(3)在 Cart 类中同样创建 deleteCartItem () 方法,实现删除购物项操作。

// 删除购物项

public void deleteCartItem(Integer id) {
    cartItemMap.remove(id);
} 

运行代码,单击购物车页面的 “删除” 超链接发现对应图书信息被删除,然后单击 “清空购物车” 发现购物车为空。

14.7 结账功能

在购物车页面,可以单击 “去结账” 超链接进行订单的创建,由于付款功能没有介绍,所以暂且跳过付款功能,这里只创建订单即可,并且为了减少前端代码,结账功能默认针对购物车中的所有商品,省略选择商品的环节。

14.7.1 准备工作

针对订单表 t_order 和订单详情表 t_order_item,创建对应的两个实体类。

创建 Order 订单类,示例代码如下。

public class Order {
    private Integer orderId;
    private String orderSequence;
    private String createTime;
    private Integer totalCount;
    private Double totalAmount;
    private Integer orderStatus;
    private Integer userId;

    //省略 get、set 等方法
} 

创建 OrderItem 订单详情类,示例代码如下。

public class OrderItem {
    private Integer itemId;
    private String bookName;
    private Double price;
    private String imgPath;
    private Integer itemCount;
    private Double itemAmount; 
private Integer orderId;
//省略 get、set 等方法
} 

14.7.2 登录状态检查

由于 “结账” 功能和 “我的订单” 功能都需要获取当前登录人的信息,如果不进行登录就单击 “结账” 或 “我的订单”,就会出现异常,因此我们创建一个过滤器,用于检查登录状态,如果处于登录状态就放行,如果不处于登录状态就跳转至登录页面,让用户进行登录。

package com.atguigu.filter;
//省略 import 语句
@WebFilter(urlPatterns = "/order")
public class LoginFilter implements Filter {
    public void destroy() {
    }
    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
    //1. 检查本次请求是否处于登录状态
    HttpServletRequest request = (HttpServletRequest) req;
    HttpServletResponse response = (HttpServletRequest) resp;
    HttpSession session = request.getSession();
    Object user = session.getAttribute("user");
    if (user == null) {
    //说明不处于登录状态(跳转至登录页面)
    response.sendRedirect(request.getContextPath() + "/user?method=login.html");
    } else {
    //说明处于登录状态
    chain.doFilter(req, resp);
    }
    }
    public void init(FilterConfig config) throws ServletException {
    }
} 

同时,完善项目页面中涉及显示用户名的头信息,从 session 中获取数据进行展示。示例代码如下。

<h3>
欢迎<span><b th:text="${session.user.username}">张总</b></span>光临尚硅谷书城</h3> 

14.7.3 结账

单击 “去结账”,发送请求到服务器端,需要从 session 中获取购物车数据,创建订单项数据和订单数据,以及购物车清空操作。具体步骤如下。

(1)修改 cart.html 页面中 “去结账” 超链接,示例代码如下。

<a class="pay" th:href="@{/order?method=checkout}">去结账</a>

(2)创建 OrderServlet 类,并声明 checkout () 方法处理结账请求,示例代码如下。

package com.atguigu.servlet.model;
//省略 import 语句
@WebServlet("/order")
public class OrderServlet extends++,ViewBaseServlet {
    private OrderService orderService=new OrderServiceImpl(); 
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    this doPost(request, response);
}

@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    String method = request.getParameter("method");
    if (method.equals("checkout")) {
    checkout(request, response);
    }
}

protected void checkout(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    //处理结账的请求
    //1. 获取处理请求需要的相关数据
    //1.1 需要购物车的数据
    HttpSession session = request.getSession();
    Cart cart = (Cart) session.getAttribute("cart");
    //1.2 当前登录人的 User 对象
    User user=(User)session.getAttribute("user");
    //2. 调用业务层处理业务
    String sequence = orderService.checkout(cart, user);
    //2.1 清空购物车
    request.getSession().removeAttribute("cart");
    //3. 给响应
    request.setAttribute("sequence", sequence);
    processTemplate("cart/checkout", request, response);
} 

(3)完善业务层和持久层代码。

创建 OrderService 接口,并声明 checkout () 方法,示例代码如下。

package com.atguigu.service;
//省略 import 语句
public interface OrderService {
    /*
    * 返回值:订单号
    * 参数:购物车对象和当前登录人的 User 对象
    */
    String checkout(Cart cart, User user);
}

创建 OrderServiceImpl 实现类,实现 OrderService 接口中的方法,示例代码如下。

package com.atguigu.service.impl;
//省略 import 语句
public class OrderServiceImpl implements OrderService {
private OrderDao orderDao=new OrderDaoImpl();
private OrderItemDao orderItemDao=new OrderItemDaoImpl();
private BookDao bookDao=new BookDaoImpl(); 
@Override
public String checkout(Cart cart, User user) {
    //1. 新增订单记录
    //1.1 准备订单数据
    String orderSequence="atguigu"+System.currentTimeMillis();
    SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    String date = sdf.format(new Date());
    Order order=new Order(null,orderSequence,date,cart.getTotalCount(),
    cart.TotalAmount(),0,user.getId());
    //1.2 进行订单的新增操作
    orderDao.insert(order);
    //2. 新增订单项记录
    //2.1 获取刚刚新增订单的 id 值
    Integer orderId = orderDao.getId(orderSequence);
    //2.2 获取所有的购物项
    Collection<CartItem> cartItems = cart.getCartItems();
    for (CartItem cartItem : cartItems) {
    //2.3 准备订单项的数据
    OrderItem orderItem=new OrderItem(null, cartItem.getBook().getBookName(),
    cartItem.getBook().getPrice(), cartItem.getBook().getImgPath(), cartItem.getCount(),
    cartItem.getAmount(), orderId);
    //2.4 调用 dao 层进行数据的新增
    orderItemDao.insert(orderItem);
    //3. 修改图书的库存和销量
    Book book = cartItem.getBook();
    book.setStock(book.getStock() -cartItem.getCount());
    book.setSales(book.getSales() + cartItem.getCount());
    bookDao.update(book);
    }
    return orderSequence;
} 

创建 OrderDao 接口,声明 getId () 和 insert () 方法,示例代码如下。

package com.atguigu.dao;
import com.atguigu.bean.Order;

public interface OrderDao {
    void insert(Order order);
    Integer getId(String sequence);
} 

创建 OrderDaoImpl 实现类,并实现 OrderDao 接口,示例代码如下。

package com.atguigu.dao.impl;
//省略 import 语句
public class OrderDaoImpl implements OrderDao {
    private QueryRunner runner=new QueryRunner();
    @Override
    public void insert(Order order) {
    try {
    Connection connection = JDBCTools.getConnection();
    String sql="insert into t_order values(null,?,?,?,?,?,?)";
    runner.update(connection,sql,order.getOrderSequence(),
    order.getCreateTime(),order.TotalCount(), 
order.getTotalAmount(), order.getOrderStatus(), order.getUserId());
} catch (SQLException e) {
    e.printStackTrace();
    throw new RuntimeException();
} finally {
    try {
    JDBCTools.freeConnection();
    } catch (SQLException e) {
    throw new RuntimeException(e);
    }
}
@Override
public Integer getId(String sequence) {
    try {
    Connection connection = JDBCTools.getConnection();
    String sql="select order_id from t_order where order_sequence=?";
    return (Integer) runner.query(connection, sql,
    new ScalarHandler(), sequence);
    } catch (SQLException e) {
    e.printStackTrace();
    throw new RuntimeException();
} finally {
    try {
    JDBCTools.freeConnection();
    } catch (SQLException e) {
    throw new RuntimeException(e);
    }
    }
} 

创建 OrderItemDao 接口,声明 insert () 方法,示例代码如下。

package com.atguigu.dao;
import com.atguigu.bean.OrderItem;

public interface OrderItemDao {
    void insert(OrderItem orderItem);
} 

创建 OrderItemDaoImpl 实现类,并实现 OrderItemDao 接口,示例代码如下。

package com.atguigu.dao.impl;
//省略 import 语句

public class OrderItemDaoImpl implements OrderItemDao {
    private QueryRunner runner=new QueryRunner();
    @Override
    public void insert(OrderItem item) {
    try {
    Connection connection = JDBCTools.getConnection();
    String sql="insert into t_order_item values(null,?,?,?,?,?,?)";
    runner.update(connection,sql,item.getBookName(),item.getPrice(),
    item.getImgPath(),item.getItemCount(),
    item.getItemAmount(),item.getOrderId());
    }catch (SQLException e) {
    e.printStackTrace(); 
throw new RuntimeException();
}finally {
    try {
    JDBCTools.freeConnection();
    }catch (SQLException e) {
    throw new RuntimeException(e);
    }
    }
} 

(4)修改 checkout.html 结算页面,进行数据渲染,示例代码如下。

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>结算页面</title>
<link type="text/css" rel="stylesheet" th:href="@{/static/css/style.css}" >
<link rel="stylesheet" th:href="@{/static/css/minireset.css}" />
<link rel="stylesheet" th:href="@{/static/css/common.css}" />
<link rel="stylesheet" th:href="@{/static/css/cart.css}" />
<style type="text/css">
h1 {
text-align: center;
margin-top: 200px;
font-size: 26px;
}
.oid{
color: red;
font-weight: bolder;
}
</style>
</head>
<body>
<div class="header">
<div class="w">
<div class="header-left">
<a th:href="@{/index.html}">
<img th:src="@{/static/img/logo.gif}" alt="" />
></a>
<span>我的购物车</span>
</div>
<div class="header-right">
<h3>欢迎<span>张总</span>光临尚硅谷书城</h3>
<div class="order">
<a href="../order/order.html">我的订单</a></div>
<div class="destory"><a th:href="@{/index.html}">注销</a></div>
<div class="gohome">
<a th:href="@{/index.html}">返回</a>
</div>
</div>
</div>
</div>
</div>

<div id="main">
<h1>你的订单已结算,订单号为:<span class="oid">546845626455846</span></h1> 
</div>
<div id="bottom">
  <span>
    尚硅谷书城.Copyright &copy;2015
  </span>
</div>
</body>
</html> 

运行代码,从首页添加几本图书到购物车,如图 14-31 所示,单击 “去结账” 按钮。

440f090dac2bc36bb9b4288c3ff9d8db25f82f8a9c117dc35a33978f0a767789.jpg

由于当前未登录,所以跳转登录页面,成功实现登录拦截。然后再次回到首页来到购物车,单击 “去结账” 按钮,如图 14-32 所示,生成订单编号。

25afa5be32e36713d5435c8d0c3dd0a09342d90b04157beb1f2267197b2a4537.jpg

如果在已登录的状态下,单击 “去结账” 按钮将直接跳转该页面,生成订单编号。

14.7.4 我的订单

在登录成功页面和购物车页面都有 “我的订单” 超链接,单击 “我的订单” 将当前账户的订单展示在页面上,具体步骤如下。

(1)修改 “我的订单” 超链接,示例代码如下。

<a th:href="@{order?method=showOrder}">我的订单</a>

(2)在 OrderServlet 的 doPost () 方法中增加一个分支(加粗部分),示例代码如下。

@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    String method = request.getParameter("method");
    if (method.equals("checkout")) {
    checkout(request, response);
    } else if (method.equals("showOrder")) {
    showOrder(request, response);
    }
} 

并在 OrderServlet 类中,创建 showOrder () 方法,示例代码如下。

protected void showOrder(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException { 

//1. 获取到当前登录人的 User 对象

HttpSession session = request.getSession();
User user=(User)session.getAttribute("user"); 

//2. 调用业务层处理业务

List<Order> orders = orderService.showOrder(user.getId()); 

//3. 给响应

request.setAttribute("orders", orders);
processTemplate("order/order", request, response); 

(3) 完善业务层和持久层代码。

在 OrderService 接口中,创建 showOrder () 方法,示例代码如下。

List<Order> showOrder(Integer userId); 

在 OrderServiceImpl 实现类中,实现 OrderService 接口的方法,示例代码如下。

@Override
public List<Order> showOrder(Integer userId) {
    return orderDao.findOrderByUserId(userId);
} 

在 OrderDao 接口中,创建 findOrderByUserId () 方法,示例代码如下。

List<Order> findOrderByUserId(Integer userId); 

在 OrderDaoImpl 实现类中,实现 OrderDao 接口的方法,示例代码如下。

@Override
public List<Order> findOrderByUserId(Integer userId) {
    try {
    Connection connection = JDBCTools.getConnection();
    String sql="select " +
    "order_id orderId, order_sequence orderSequence," +
    "create_time createTime, total_counttotalCount," +
    "total_amount totalAmount, order_status orderStatus, user_id userId " +
    "from t_order where user_id = ?";
    return runner.query(connection, sql, new BeanListHandler<Order>(Order.class), userId);
    } catch (SQLException e) {
    e.printStackTrace();
    } finally {
    try {
    JDBCTools.freeConnection();
    } catch (SQLException e) {
    throw new RuntimeException(e); 
}
return null;
} 

运行代码,单击 “我的订单” 进入订单页面,如图 14-33 所示。

f06e009c4ad86851599ff9e6a5df23345e6b98ebb3b09fb60cff0aca78336779.jpg

14.8 本章小结

本章主要介绍了尚硅谷书城项目,该项目整合了全书所有的技术知识,功能模块包括用户管理模块、后台管理模块、前台图书展示、购物车功能及结账功能等。通过本章的学习,巩固和提升对 JavaWeb 相关知识的掌握和综合应用,加深对项目的理解和开发能力。

794991642e8edc364297d08cace059bcec40376cbee53c38c3c79baf358d88f4.jpg

2938d6a80a19a6e12fd3b6dd64dd61b46d4372a3dfb8c0b1baf63bebd9c8b403.jpg