JavaWeb 书城项目
表单验证的实现
表单验证主要使用 jQuery 实现,IDE 为 IDEA。
导入项目
把原有的文件导入, 原有文件链接 提取码:nefu
导入文件 接下来我们要修改 login.html 以及 regist.html [
login 与 regist
把
jquery.js
放入
static/script
文件夹下
[
regist 部分
- 验证用户名:必须由字母,数字下划线组成,并且长度为 5 到 12 位
- 验证密码:必须由字母,数字下划线组成,并且长度为 5 到 12 位
- 验证确认密码:和密码相同
- 邮箱验证; xxxxx@xxx.com
- 验证码:现在只需要验证用户已输入
具体大致流程如下:
-
$(#id).val()
获得表单项的值 -
/ /
创建正则项表达式 -
使用
test
方法测试 -
$("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 全部代码如下
```
```
login 部分
login 部分也是一样的思路,因为表单标签没有提供
id
,我们要为其添加一个
id
标签,即修改原始代码如下
后面就和注册一样的思路,利用
JQuery
验证表单即可。
login.html 全部代码如下
```
```
用户注册和登录
之前已经做好前端页面,现在要通过 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
表。
通过建立
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 配置文件
内容根据自己情况修改
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 页面
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 页面
-
添加
base
标签 -
修改
base
标签对相对路径的影响 - 修改注册表单的提交地址和请求方式
修改登录表单
##### 编写 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
页面只需要如下两个步骤。
- 在头部添加如下语句
<%@ 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" %>
```
之后在公共位置改为如下代码
<%-- 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"%>
表单提交失败的错误回显
具体实现思路
-
在
Servlet
程序中将错误回显信息放入request
域中 -
在
jsp
页面中输出回显信息
修改 Servlet 程序
修改
LoginServlet
和
RegistServlet
程序如下
修改 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
中。
-
导入需要的
jar
包: commons-beanutils-1.8.0.jar commons-logging-1.1.1.jar -
编写
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
- 在程序中加入如下代码即可调用方法将请求参数注入到
JavaBean
中
调用方法
使用 EL 表达式修改表单回显
我们可以使用
EL
表达式来修改表单回显,这样能使代码更简洁。
图书模块
前四个阶段我们完成了用户的注册与登录功能,并对代码进行了优化,第五阶段我们完成书城项目的图书模块,属于后台管理中的图书管理功能,主要包括图书的添加,删除,修改以及显示。
数据库表
首先编写图书模块的数据库表,使用如下
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 层,页面联调测试
图书列表功能的实现
列表功能流程
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
标签库遍历输出图书信息。
添加图书功能的实现
- 添加图书流程细节
添加图书流程
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");
}
- 修改
book_edit.jsp
页面
修改 book_edit.jsp
这里如果使用
post
方法提交会中文乱码,可以通过修改
post
的编码解决。
删除图书功能的实现
- 图解删除流程
删除流程
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");
}
修改删除的连接地址
2. 给删除添加确认提示操作
删除属于危险项,所以我们需要添加确认操作,给删除的
a
标签绑定单击事件。
```
```
修改图书功能的实现
修改图书 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");
}
- 解决
book_edit.jsp
页面,既要实现添加,又要实现修改操作。
实现添加与修改
图书分页
前五个部分我们已经完成了用户的注册与登录模块,以及后台的图书管理,第六部分我们完成图书的分页部分,分页的原因就是一页显示全部信息太繁杂了,所以需要需要分页来解决这个问题。
分页模块的分析
分页模型 Page 的抽取
由分页的视图分析出分页的对象模型
Page
类有如下属性
```
/*
* Page是分页的模型对象
* @param
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
id
,
name
,
author
,
price
,
sales
,
stock
,
img_path
imgPath" +
" from t_book limit ?,?";
return queryForList(Book.class, sql, begin, pageSize);
}
```
BookService
代码
```
@Override
public Page
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);
}
修改图书管理请求地址
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
来进行数据边界的有效检查,所以设置当前页码要放在设置总页码之后。
页码的显示
一般来说,显示页码的时候,不仅会显示当前页的页码,还会显示前几页的页码,以及后几页的页码,然后点击这些页码就可以跳转到指定页。 这里实现一次显示 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>
修改分页后,增加,删除,修改图书信息的回显页面
2.在
book_edit.jsp
页面中使用隐藏域记录下
pageNo
参数
使用隐藏域记录下 pageNo 参数
3.在服务器重定向时,获取当前页码追加上进行跳转
追加当前页码进行跳转
首页 index.jsp 的跳转
因为首页也需要分页,所以我们访问首页的时候需要让其通过
ClientBookServlet
程序让其跳转到
web目录/pages/client目录的/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" %>
```
修改
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
属性,再把分页条抽取出来,就可以简单的调用分页条。
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
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
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);
}
用户功能完善
之前我们做出了用户功能的注册与登录功能,这次我们将用户功能完善,包括用户登录显示用户名,注销用户,以及验证码的使用
登录显示用户名
一般来说,我们登录之后会显示用户名,我们之前是写死的,这次改成动态的。
-
UserServlet
程序中保存用户登录的信息。因为我们登录之后的所有网页都是要显示用户名的,所以不能保存到request
域,而是要保存到session
域。 - 修改
login_success_menu.jsp
,,因为我们之前把登录成功之后的菜单信息提取出一个
jsp
文件,所以只需要修改这个公共的部分。
修改 login_success_menu.jsp 3. 修改首页
index.jsp
页面的菜单,使其在登录之后也能显示用户信息。
修改首页 index.jsp
登出注销用户
-
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());
}
- 修改注销的菜单地址
修改注销菜单的地址
使用验证码的原因及原理
之前验证码一直是写死的,这次我们实现动态的验证码。使用验证码的原因之一是为了防止用户重复提交表单而产生错误。 表单重复提交有以下三种情况。
- 提交完表单。服务器使用请求转来进行页面跳转。这个时候,用户按下功能键 F5,就会发起最后一次的请求。造成表单重复提交问题。解决方法:这个可以使用重定向来进行跳转
- 用户正常提交服务器,但是由于网络延迟等原因,迟迟未收到服务器的响应,这个时候,用户以为提交失败,就会着急,然后多点了几次提交操作,也会造成表单重复提交。
- 用户正常提交服务器。服务器也没有延迟,但是提交完成后,用户回退浏览器。重新提交。也会造成表单重复提交。
最后两种情况无法通过重定向解决,所以我们可以使用验证码解决。
谷歌 kaptcha 图片验证码的使用
我们使用现有的
jar
包来完成我们的验证码功能。
-
导入谷歌验证码的
jar
包
kaptcha-2.3.2.jar
-
在
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>
使用验证码图片 2. 在服务器获取谷歌生成的验证码和客户端发送过来的验证码比较使用
服务器比较验证码 3. 切换验证码,用户有时候可能看不清验证码,所以我们还要提供点击图片替换的功能。
// 给验证码图片绑定单击事件
$("#code_img").click(function () {
// 在事件响应的function函数中有一个this对象,这个this对象,是当前正在响应事件的dom对象
// src 属性表示验证码img标签的图片路径。它可读可写
// 添加随机变量,防止调用缓存
this.src = "${basePath}kaptcha.jpg?d=" + new Date();
});
购物车模块
上一部分,我们对用户功能进行了完善,这一部分我们完成购物车模块,主要包括添加商品到购物车,删除商品,清空购物车。
购物车模块分析
我们使用
Session
版本实现购物车,这样就不需要
Dao
层和
Service
层了。
购物车模型编写
-
创建
CartItem
类定义购物车中的商品项,其有以下属性
private Integer id; //编号
private String name; //名称
private Integer count; //数量
private BigDecimal price; //单价
private BigDecimal totalprice; //总价
-
创建
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);
}
} ```
加入购物车功能的实现
-
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.图解说明,如何跳回添加商品的页面
跳回添加商品的页面
购物车的展示
删除购物车商品项
-
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"));
}
```
-
修改
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
属性,以及清空确认提示操作
修改购物车商品的数量
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"));
}
} ```
- 修改
cart.jsp
修改 cart.jsp(一)
修改 cart.jsp(二)
首页购物车数据回显
- 在添加商品到购物车的时候,保存最后一个添加的商品名称。
修改 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 层和页面联调
生成订单
-
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");
} ```
修改 cart.jsp
2. 修改
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");
}
修改 manager_menu.jsp
2. 修改
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");
} ```
修改 login_menu.jsp
2. 修改
order.jsp
页面
显示订单详情
-
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);
}
}
-
在
order
目录下新建order_detail.jsp
页面
``` <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ page contentType="text/html;charset=UTF-8" language="java" %>
商品名称 | 数量 | 单价 | 金额 |
${orderItem.name} | ${orderItem.count} | ${orderItem.price} | ${orderItem.totalPrice} |
```
修改 order.jsp
2. 修改
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");
}
- 修改
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");
}
- 修改
order.jsp
页面,点击签收请求地址
修改 order.jsp
Filter 过滤器和 Aajx 请求
第十部分我们做一个结尾,主要包括使用 Filter 过滤器进行权限检查,使用 Filter 和 ThreadLocal 组合管理事务,将异常同一交给 Tomcat 并展示友好的错误页面,使用 Aajx 请求改进功能。
Filter 过滤器实现权限检查
我们要使用 Filter 过滤器拦截/pages/manager/所有内容,实现权限检查。
-
Filter
的工作流程如下
Filter 工作流程图
2.
Filter
过滤器的使用步骤:
``` 1、 编写一个类去实现 Filter 接口
2、 实现过滤方法 doFilter()
3、 到 web.xml 中去配置 Filter ```
-
Filter
的生命周期
Filter 的生命周期包含几个方法
1、构造器方法
2、init 初始化方法
第 1,2 步,在 web 工程启动的时候执行(Filter 已经创建)
3、doFilter 过滤方法
第 3 步,每次拦截到请求,就会执行
4、destroy 销毁
第 4 步,停止 web 工程的时候,就会执行(停止 web 工程,也会销毁 Filter 过滤器)
-
Filter
的拦截路径
```
--精确匹配
--目录匹配
--后缀名匹配
-
创建
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() {
}
} ```
-
配置
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 组合管理事务
-
ThreadLocal
的作用
``` ThreadLocal 的作用,它可以解决多线程的数据安全问题。
ThreadLocal 它可以给当前线程关联一个数据(可以是普通变量,可以是对象,也可以是数组,集合)
ThreadLocal 的特点: 1、ThreadLocal 可以为当前线程关联一个数据。(它可以像 Map 一样存取数据,key 为当前线程) 2、每一个 ThreadLocal 对象,只能为当前线程关联一个数据,如果要为当前线程关联多个数据,就需要使用 多个 ThreadLocal 对象实例。 3、每个 ThreadLocal 对象实例定义的时候,一般都是 static 类型 4、ThreadLocal 中保存数据,在线程销毁后。会由 JVM 虚拟自动释放 ```
- 使用
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);
}
}
} ```
- 使用
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
展示友好的错误信息页面,这样用户就不用面对一大堆问题代码了。
-
在
web.xml
中我们可以通过错误页面配置来进行管理
```
- 编写错误页面,我们就简单做一下
error500.jsp
```
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
返回首页
```
error404.jsp
``` <%@ page contentType="text/html;charset=UTF-8" language="java" %>
返回首页
```
-
TransactionFilter
要把异常抛给
Tomcat
修改 TransactionFilter
使用 AJAX 验证用户名是否可用
- 图解验证用户名是否可用流程
验证用户名是否可用流程
2. 导入相关
jar
包
gson-2.2.4.jar
-
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
Gson gson= new Gson();
String json = gson.toJson(resultMap);
resp.getWriter().write(json);
} ```
-
修改
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
请求来完成局部的更新。
加入购物车流程
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);
} ```
-
修改
index.jsp
页面
- 修改
BaseServlet
程序解决中文乱码问题
解决中文乱码问题
参考文献
- 世通书屋网上书店系统的设计与开发——基于Asp.net技术(吉林大学·李清海)
- 基于WEB的JSP网络售书系统(吉林大学·郭志峰)
- 网上书店的设计与实现(山东大学·惠开敏)
- 基于B/S架构的博文网络书店的设计与实现(电子科技大学·彭媛媛)
- 基于B/S结构的电子商务的研究与应用(哈尔滨工程大学·车彦朋)
- 网上书店系统设计与实现(吉林大学·关键)
- 网络文学平台的设计与实现(华中科技大学·王俊)
- 基于.NET的网上购书系统设计与实现(电子科技大学·李园媛)
- 基于JSP的网上书店交易系统的设计与实现(吉林大学·徐迎新)
- 基于J2EE架构网上书店的设计与实现(同济大学·蔡玮)
- 基于SSH框架的网上书城系统设计与实现(成都理工大学·田涛)
- 基于SSH框架的网上书城系统设计与实现(成都理工大学·田涛)
- 网上购书系统的设计与实现(电子科技大学·谢宗燃)
- 网上书店的设计与实现(同济大学·徐爱鸣)
- 图书综合管理系统(吉林大学·王宇)
本文内容包括但不限于文字、数据、图表及超链接等)均来源于该信息及资料的相关主题。发布者:源码导航 ,原文地址:https://m.bishedaima.com/yuanma/35784.html