商品名称 | 数量 | 单价 | 金额 | 操作 |
购物车竟然是空的~ | ||||
${cart.value.name} | ${cart.value.count} | ${cart.value.price} | ${cart.value.totalPrice} | 删除 |
链接: 点击获取资源
提取码: ih2c
再次感谢尚硅谷,我爱尚硅谷!!!!
目录
第一阶段:对注册页面的信息进行验证:
第二阶段:用户管理模块
2.1、创建数据库表
2.2、编写User实体类
2.3、工具类——JdbcUtils工具类:用于连接数据库。
2.4、提供BaseDao:操作数据库的方法【可继承BaseDao实现数据库操作】
2.5、提供DAO接口以及实现类【针对于某一张表的操作】
2.6、编写Service层和实现
第三阶段:jsp页面动态化
1、将所有的 html 页面 替换成 jsp 页面。并增加头部标签。
2、使用静态包含替换掉所有的 公共部分。
3、在页面上回显错误信息。
4、优化Servlet
5、请求参数的封装和BeanUtils工具类的使用
第四阶段:图书管理模块
4.1、构建数据库
4.2、创建 JavaBean 实体类
4.3、编写图书模块的Dao接口,以及实现类
4.4、编写图书模块的 service 接口 以及 实现类
4.5、编写图书模块的Web层
第五阶段:图书管理模块的分页管理
5.1、分页初步查询
5.2、上一页,下一页,首页,末页 的实现
5.3、跳到指定页码
5.4、页码边界检查
5.5、页码显示
5.6、分页对删除、修改、增加的影响
5.7、前台的分页管理
5.8、分页条的抽取
5.9、价格区间搜索
第六阶段:登录用户信息显示
6.1、登录后显示 欢迎信息
6.2、注销登录
6.3、表单重复提交问题
6.4、将验证码增加到书城项目中
第七阶段:购物车模块
7.1、购物车模型创建
7.2、 购物车功能的实现
7.3、增加购物车功能的实现
7.4、展示购物车
7.5、删除商品
7.6、清空购物车
7.7、修改购物车商品数量
7.8、首页购物车信息回显
第八阶段:订单模块
8.1、订单模型创建分析
8.2、订单与订单项数据库创建
8.3、订单项与订单的实体类创建
8.4、生成订单功能的实现
第九阶段:使用 Filter 和 ThreadLocal 完善系统
9.1、使用Filter拦截器:
9.2、使用 ThreadLocal:
9.3、使用 FIlter 统一给所有的 service 方法加上 try。。catch
9.4、使用 Tomcat 统一管理异常,展示友好的错误页面
第十阶段:使用 Ajax 验证用户名是否可用
使用 Ajax 修改增加购物车
要求:
验证用户名:必须由字母,数字下划线组成,并且长度为 5 到 12 位
验证密码:必须由字母,数字下划线组成,并且长度为 5 到 12 位
验证确认密码:和密码相同
邮箱验证:[email protected]
验证码:现在只需要验证用户已输入。因为还没讲到服务器。验证码生成
项目的三层架构:
Web 层 无法直接访问 Dao 层,需要借助 Service 层。
无论多复杂的项目都基本符合 javaEE 三层架构:
Dao层: 负责与数据库进行交互,一般都是有一个 BaseDao,为不同的模块提供 操作 数据库的方法【增删改查】,每一个模块或者一个业务都需要继承 BaseDao,并且 需要一个 dao接口和实现类。
Service 层:负责处理各种业务。一般是一个 service 接口 和 实现类。
Web : 服务与页面联调
项目环境搭建:
配置 Tomcat 服务器,将静态页面都放到web目录下。
包名设置:
DROP DATABASE IF EXISTS book;
CREATE DATABASE book;
USE book;
CREATE TABLE `t_user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(20) NOT NULL,
`password` varchar(32) NOT NULL,
`email` varchar(200) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `username` (`username`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
insert into `t_user`(`id`,`username`,`password`,`email`) values (1,'admin','admin','[email protected]');
public class User {
private Integer id;
private String username;
private String password;
private String email;
public User() {
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
", email='" + 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;
}
}
(1)将连接数据库的 jar 包导入 WEB—INF的lib目录下
(2)事先准备好配置文件【德鲁伊连接池】:用于保存 url ,user,password,driver等信息。
#驱动
driver=com.mysql.jdbc.Driver
#url
url=jdbc:mysql://127.0.0.1:3306/test
#账户
user=root
#密码
password=root
(3)编写JdbcUtils工具类【提前将德鲁伊连接池 jar 包导入lib目录下】
public class JdbcUtils {
private static DruidDataSource source;
static {
try {
/*加载流*/
Properties pros = new Properties();
InputStream is = JdbcUtils.class.getClassLoader().getResourceAsStream("jdbc.properties");
pros.load(is);
source = (DruidDataSource) DruidDataSourceFactory.createDataSource(pros);
} catch (Exception e) {
e.printStackTrace();
}
}
public static Connection getConnection() {
Connection conn = null;
try {
conn = source.getConnection();
} catch (SQLException e) {
e.printStackTrace();
}
return conn;
}
/*释放资源*/
public static void closeConnection(Connection conn) {
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
/*使用DBUtils操作数据库增删改查*/
public class BaseDao {
private static QueryRunner runner = new QueryRunner();
/*通用的增删改*/
public static int update(Connection conn, String sql, Object... args) {
int count = -1;
try {
/*影响数据库的条数*/
count = runner.update(conn, sql, args);
} catch (SQLException e) {
e.printStackTrace();
}
return count;
}
/*查询:只返回一条数据*/
public static T queryForOne(Connection conn, Class tClass, String sql, Object... args) {
BeanHandler beanHandler = new BeanHandler(tClass);
T t = null;
try {
t = runner.query(conn, sql, beanHandler, args);
} catch (SQLException e) {
e.printStackTrace();
}
return t;
}
/*返回多条记录*/
public static List queryForList(Connection conn, Class tClass, String sql, Object... args) {
BeanListHandler beanList = new BeanListHandler<>(tClass);
List list = new ArrayList<>();
try {
list = runner.query(conn, sql, beanList, args);
} catch (SQLException e) {
e.printStackTrace();
}
return list;
}
/*查询特殊值*/
public static Object queryForSingle(Connection conn, String sql, Object... args) {
ScalarHandler
public interface UserDao {
/**
* 返回 1 表示注册成功。
* @param user 用户注册的信息
* @return 影响数据库条数
*/
int saveInfo(User user);
/**
* 注册使用
* @param userName 根据用户名在数据库中进行查询
* @return 返回 user 信息表示用户已存在,返回 null 表示用户名可用
*/
User queryByUserName(String userName);
/**
* 登录使用
* @param userName 用户名
* @param password 密码
* @return 返回 user 信息表示用户名密码正确-登录成功,返回 null 表示登陆失败
*/
User queryByUserNameAndPassword(String userName,String password);
}
public class UserImplDao extends BaseDao implements UserDao {
@Override
public int saveInfo(User user) {
Connection conn = JdbcUtils.getConnection();
String sql = "insert into t_user(username,password,email) values(?,?,?)";
return BaseDao.update(conn, sql, user.getUsername(), user.getPassword(), user.getEmail());
}
@Override
public User queryByUserName(String userName) {
Connection conn = JdbcUtils.getConnection();
String sql = "select `id`, `userName` ,`password`, `email` from t_user where userName=?";
return BaseDao.queryForOne(conn, User.class, sql, userName);
}
@Override
public User queryByUserNameAndPassword(String userName, String password) {
Connection conn = JdbcUtils.getConnection();
String sql = "select `id`, `userName` ,`password`, `email` from t_user where userName=? and password=?";
return BaseDao.queryForOne(conn, User.class, sql, userName, password);
}
}
接口:
public interface UserService {
/**
* 注册信息
* @param user 注册的信息
*/
void saveInfo(User user);
/**
* 判断用户名是否重复
* @param userName 用户名
* @return
*/
boolean queryByUserName(String userName);
/**
* 登录
* @param userName 用户名
* @param password 密码
* @return
*/
User login(String userName,String password);
}
接口实现类:
public class UserServiceImpl implements UserService {
private UserImplDao userDao = new UserImplDao();
/**
*
* @param user 注册的信息
*/
@Override
public void saveInfo(User user) {
userDao.saveInfo(user);
}
/**
*
* @param userName 用户名
* @return true:用户名重复 false:用户名可用
*/
@Override
public boolean queryByUserName(String userName) {
User user = userDao.queryByUserName(userName);
if (user != null){
//不等于 null,说明查到了用户名,则用户名重复不可用。
return true ;
}
//相反,没有查找说明用户名可用
return false;
}
/**
*
* @param userName 用户名
* @param password 密码
* @return 返回 User 信息
*/
@Override
public User login(String userName, String password) {
return userDao.queryByUserNameAndPassword(userName, password);
}
}
注册功能的流程图:
页面路径问题:
web: base标签 + 相对路径
更改 register.html 和 register_success.html 中的所有路径。
实现注册功能:创建 RegisterServlet【注册】 业务类 ,部署Tomcat 并且配置 web.xml 文件。
注意:不要忘记引入 Servlet.jar 包
public class RegisterServlet extends HttpServlet {
//Web 层不能直接访问Dao层,通过 Service 层访问。
private UserServiceImpl userService = new UserServiceImpl();
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 1、获取参数
String username = request.getParameter("username");
String password = request.getParameter("password");
String email = request.getParameter("email");
String code = request.getParameter("code");
// 2、判断验证码是否正确:还没有学动态获取验证码,所以这里就写死了
if ("6n6np".equalsIgnoreCase(code)){
// 验证码正确
// 用户名是否重复
if(userService.existsUsername(username)){
// 用户名重复:返回注册页面
System.out.println("用户名["+username+"]重复");
request.getRequestDispatcher("/pages/user/regist.html").forward(request,response);
}else{
// 用户名不重复:跳转到注册成功界面,并向数据库中保存user信息
userService.registUser(new User(null,username,password,email));
request.getRequestDispatcher("/pages/user/regist_success.html").forward(request,response);
}
}else{
// 验证码错误:返回注册页面
System.out.println("验证码错误");
request.getRequestDispatcher("/pages/user/regist.html").forward(request,response);
}
}
}
实现登录功能:
public class LoginServlet extends HttpServlet {
private UserService userService = new UserServiceImpl();
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
/*获取参数*/
String username = request.getParameter("username");
String password = request.getParameter("password");
User user = userService.login(new User(null, username, password, null));
if (user != null){
/*用户名密码正确,跳转到登陆成功页面*/
System.out.println("登陆成功");
request.getRequestDispatcher("/pages/user/login_success.html").forward(request,response);
}else{
/*不正确,返回登录界面打印错误信息*/
System.out.println("用户名或密码错误");
request.getRequestDispatcher("/pages/user/login.html").forward(request,response);
}
}
}
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
更改之后检查页面的路径是否正确。加上 base 标签后,所有的路径都以这个路径为起始路径
登录成功的菜单:
替换:
<%--使用静态包含,替换登陆成功菜单--%>
<%@include file="/pages/common/login_succss_menu.jsp"%>
页脚:
替换:
<%--使用静态包含替换页脚--%>
<%@include file="/pages/common/footer.jsp"%>
jQuery,css,Bas 标签:
<%--使用静态包含替换所有的 jQuery,css,Base 标签--%>
<%@include file="/pages/common/head.jsp"%>
图书管理菜单:
替换:
<%--使用静态包含替换图书管理菜单--%>
<%@include file="/pages/common/manager_menu.jsp"%>
动态获取 base 路径:
<%--动态获取base标签中的路径--%>
<%
String basePath = request.getScheme() // http协议
+"://"
+request.getServerName() //服务器ip
+":"
+request.getServerPort() // 端口号
+request.getContextPath() // 根路径
+"/";
%>
登录时 输错用户名或者密码,要求显示在页面上,注册时:用户名存在或者验证码错误显示在 页面上,并且回显用户名和邮箱。
3.1 、登录时的错误回显,并回显用户名。
思路:
在 LoginServlet 程序端接受浏览器发送的请求信息【用户名和密码】,与数据库进行判断。
信息错误时,将 回显的信息保存到 request 域中。转发到 jsp 页面,使用 EL 表达式取出数据,回显到 jsp 页面上。
回显得数据存在 request 域中:
//将错误信息保存到 request域中。在 jsp 页面访问。并且回显username request.setAttribute("msg","输入的用户名或者密码错误"); request.setAttribute("username",username);
3.2、注册时回显错误信息和用户名,邮箱。
想要回显什么数据就往 域中保存数据即可【不一定都是request域】。在 jsp 页面取出。
第一个问题:其实我们知道 注册和登录功能 都属于用户模块,那么我们可不可以将他们合并成一个 Servlet呢?
解决方法:是可以解决的,通过代码可以发现,注册和登录功能其实和请求参数是一样的,请求参数是 login执行登录方法,regist 执行注册方法。那么我们可以在登录,注册页面的表单当中增加隐藏域,使用 if...else 用来区分登录还是注册。
第二个问题:在实际的项目开发当中,并不是有一个简单业务注册和登录,包括修改密码,增加删除各种各样的业务,每一个业务都用 if....else 判断太繁琐了。有没有一种方法,可以自动判断执行哪个方法?不使用 if...else
解决办法:使用反射机制,根据 隐藏域中的请求参数,来执行不同的方法。
第三个问题:每个项目中不仅仅有一个模块,对于这个项目来说还有图书管理模块.....每个模块都需要通过反射机制调用方法,所以我们可以将 反射机制调用方法 这部分重复的代码封装到 BaseServlet 类中。BaseServlet 继承 HttpServlet ,UserServlet 继承 BaseServlet。
增加隐藏域:
<%--增加隐藏域。方便区分登录还是注册--%>
<%--增加隐藏域。方便区分登录还是注册--%>
合并成一个UserServlet :
public class UserServlet extends HttpServlet {
private UserService userService = new UserServiceImpl();
//处理登录请求
protected void login(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
/*获取参数*/
String username = request.getParameter("username");
String password = request.getParameter("password");
User user = userService.login(new User(null, username, password, null));
if (user != null){
/*用户名密码正确,跳转到登陆成功页面*/
request.getRequestDispatcher("/pages/user/login_success.jsp").forward(request,response);
}else{
/*不正确,返回登录界面打印错误信息*/
//将错误信息保存到 request域中。在 jsp 页面访问。并且回显username
request.setAttribute("msg","输入的用户名或者密码错误");
request.setAttribute("username",username);
request.getRequestDispatcher("/pages/user/login.jsp").forward(request,response);
}
}
//处理注册请求
protected void regist(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 1、获取参数
String username = request.getParameter("username");
String password = request.getParameter("password");
String email = request.getParameter("email");
String code = request.getParameter("code");
// 2、判断验证码是否正确:还没有学动态获取验证码,所以这里就写死了
if ("6n6np".equalsIgnoreCase(code)){
// 验证码正确
// 用户名是否重复
boolean b = userService.existsUsername(username);
if(b){
// 用户名重复:返回注册页面
//回显数据
request.setAttribute("msg","用户名重复");
request.setAttribute("username",username);
request.setAttribute("email",email);
request.getRequestDispatcher("/pages/user/regist.jsp").forward(request,response);
}else{
// 用户名不重复:跳转到注册成功界面,并向数据库中保存user信息
userService.registUser(new User(null,username,password,email));
request.getRequestDispatcher("/pages/user/regist_success.jsp").forward(request,response);
}
}else{
// 验证码错误:返回注册页面
//回显数据
request.setAttribute("msg","验证码错误");
request.setAttribute("username",username);
request.setAttribute("email",email);
request.getRequestDispatcher("/pages/user/regist.jsp").forward(request,response);
}
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String action= request.getParameter("action");
if ("login".equals(action)){
login(request,response);
}else if ("regist".equals(action)){
regist(request,response);
}
}
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
/*获取请求参数*/
String action = request.getParameter("action");
/*通过反射机制调用方法*/
try {
/*("方法名","参数.class","参数.class"...)*/
/*this:表示 UserServlet */
Method method = this.getClass().getDeclaredMethod(action,HttpServletRequest.class,HttpServletResponse.class);
method.invoke(this,request,response);
} catch (Exception e) {
e.printStackTrace();
}
}
public class BaseServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
/*获取请求参数*/
String action = request.getParameter("action");
/*通过反射机制调用方法*/
try {
/*("方法名","参数.class","参数.class"...)*/
/*this:表示 UserServlet */
Method method = this.getClass().getDeclaredMethod(action,HttpServletRequest.class,HttpServletResponse.class);
method.invoke(this,request,response);
} catch (Exception e) {
e.printStackTrace();
}
}
}
BeanUtils 工具类,可以一次性将所有的参数注入到 Bean 对象中。
问题:每个项目中每块业务中的请求参数是不一样的,有的项目可能有十几,几十个请求参数,如果一个个都 request.getParameter() 是非常麻烦的。解决方法: BeanUtils工具类中的 BeanUtils.copyProperties(注入的对象,Map集合) 方法可以将一个 map 集合 注入 到一个对象中。正好 请求参数 也是一个 map 集合,我们可以利用这一特征将 请求参数注入到 JavaBean 对象中
BeanUtils.copyProperties() 方法 测试:
相同的道理:每块业务可能都需要获取请求参数,所以将 BeanUtils.copyProperties() 方法 封装到一个父类当中。
//一次性获取请求参数
public class WebUtils {
/*使用泛型代替具体的某个对象*/
public static T copyParamToBean(Map map, T bean){
try {
/*(对象,Map集合)*/
BeanUtils.copyProperties(bean,map);
} catch (Exception e) {
e.printStackTrace();
}
return bean ;
}
}
使用 BeanUtils.copyProperties(注入的对象,Map集合) 取代 request.getParameter() 。
/*使用封装好的 BeanUtils ,一次性获取所有的请求参数*/
User user = WebUtils.copyParamToBean(request.getParameterMap(), new User());
还有一点需要注意的是:
BeanUtils 底层使用的也是反射机制,通过调用 name 的 set 方法去注入的,如果你的 User实体类的 set 方法 和 你的 name 不一致,他是注入不了的。
我修改了 User实体类中 setUsername 方法:
USE bookbook; ## 切换到数据库
##创建图书表
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');
## 查看表内容
SELECT id,NAME,author,price,sales,stock,img_path FROM t_book;
public class Book {
/*Integer可以接收 null 值 */
private Integer id;
private String name;
private String author;
private BigDecimal price;
private Integer sales;
private Integer stock;
/*给一个默认图片的路径*/
private String img_path = "static/img/default.jpg";
@Override
public String toString() {
return "Book{" +
"id=" + id +
", name='" + name + '\'' +
", author='" + author + '\'' +
", price=" + price +
", sales=" + sales +
", stock=" + stock +
", img_path='" + img_path + '\'' +
'}';
}
public Book() {
}
public Book(Integer id, String name, String author, BigDecimal price, Integer sales, Integer stock, String img_path) {
this.id = id;
this.name = name;
this.author = author;
this.price = price;
this.sales = sales;
this.stock = stock;
/*给定的图片路径不是空的情况下,才能赋值。*/
if (img_path != null && !"".equals(img_path)) {
this.img_path = img_path;
}
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public BigDecimal getPrice() {
return price;
}
public void setPrice(BigDecimal price) {
this.price = price;
}
public Integer getSales() {
return sales;
}
public void setSales(Integer sales) {
this.sales = sales;
}
public Integer getStock() {
return stock;
}
public void setStock(Integer stock) {
this.stock = stock;
}
public String getImg_path() {
return img_path;
}
public void setImg_path(String img_path) {
this.img_path = img_path;
}
}
对 img_path 赋值 时,需判断是不是 null 或 "" ,是的话保留原来的值,不是才能赋值。
public interface BookDao {
/**
*
* @param book 增加书籍
* @return
*/
int addBook(Book book);
/**
*
* @param id 根据 ID 删除
*/
void delBook(int id);
/**
*
* @param book 修改
*/
void updateBook(Book book);
/**
*
* @param id 根据ID 查询
* @return
*/
Book queryForByID(int id);
/**
*
* @return 查询所有的书
*/
List queryForList();
}
public class BookImplDao 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 BaseDao.update(sql, book.getName(), book.getAuthor(), book.getPrice(),
book.getSales(), book.getStock(), book.getImg_path());
}
@Override
public void delBook(int id) {
String sql = "delete from t_book where id=?";
BaseDao.update(sql, id);
}
@Override
public void updateBook(Book book) {
String sql = "update t_book set `name`=? , `author`=? , `price`=? , `sales`=? , `stock`=? , `img_path`=? where id=?";
BaseDao.update(sql, book.getName(), book.getAuthor(), book.getPrice(),
book.getSales(), book.getStock(), book.getImg_path(), book.getId());
}
@Override
public Book queryForByID(int id) {
String sql = "select `id` , `name` , `author` , `price` , `sales` , `stock` , `img_path` from t_book where id=?";
return BaseDao.queryForOne(Book.class, sql, id);
}
@Override
public List queryForList() {
String sql = "select `id` , `name` , `author` , `price` , `sales` , `stock` , `img_path` from t_book";
return BaseDao.queryForList(Book.class, sql);
}
}
public interface BookService {
int addBook(Book book);
void update(Book book);
void delBook(int id);
Book queryForByID(int id);
List queryForList();
}
public class BookServiceImpl implements BookService {
private BookDao bookDao = new BookImplDao();
@Override
public int addBook(Book book) {
return bookDao.addBook(book);
}
@Override
public void update(Book book) {
bookDao.updateBook(book);
}
@Override
public void delBook(int id) {
bookDao.delBook(id);
}
@Override
public Book queryForByID(int id) {
return bookDao.queryForByID(id);
}
@Override
public List queryForList() {
return bookDao.queryForList();
}
}
创建 BookServlet 继承 BaseServlet ,实现 图书的增删改查。
配置好 xml 文件:
BookServlet
yangzhaoguang.web.BookServlet
BookServlet
/manager/bookServlet
BookServlet 中增加 list 方法,用于查询 t_book表中的所有数据:
public class BookServlet extends BaseServlet {
private BookService bookService = new BookServiceImpl();
//查询所有的数据,并显示到 book_manager.jsp 页面上
protected void list(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
List list = bookService.queryForList();
request.setAttribute("list",list);
/*请求转发到:book_manager.jsp页面 */
request.getRequestDispatcher("/pages/manager/book_manager.jsp").forward(request,response);
}
}
修改 manager_menu 中的 图书管理 连接,能够 执行 BookServlet 中的 list 方法:
action?list : 写的参数,使我们写的BaseServlet中的反射机制反射到,能够执行 list 方法。
修改 book_manager.jsp 页面,从 request 域中取出数据能够动态的显示到 jsp 页面中:
首先修改 book_manager.jsp 页面中 增加图书 的连接,跳转到 book_edit.jsp 页面进行修改:
添加图书
修改 book_edit.jsp 页面中表单提交的地址,使其能够执行到 add 方法:
一定要修改 book_edit.jsp 页面中表单的 name 属性 和自己写的 Book 实体类的名字一致,不然使用 BeanUtils 工具类获取请求参数时获取不到:
向 BookServlet 中 增加 add方法,向数据库中增加图书:
public class BookServlet extends BaseServlet {
private BookService bookService = new BookServiceImpl();
//查询所有的数据,并显示到 book_manager.jsp 页面上
protected void list(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
List list = bookService.queryForList();
request.setAttribute("list",list);
/*请求转发到:book_manager.jsp页面 */
request.getRequestDispatcher("/pages/manager/book_manager.jsp").forward(request,response);
}
//增加图书功能
protected void add(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
/*获取请求参数*/
Book book = WebUtils.copyParamToBean(request.getParameterMap(),new Book());
int i = bookService.addBook(book);
if (i != 0){
/*重定向
* 一定要重定向:不要使用转发,使用转发有 bug,刷新一次页面,数据库数据就会增加一条。
* 或者直接调用 list 方法 也是可以的。
* */
response.sendRedirect(""+request.getContextPath()+"/manager/bookServlet?action=list");
}
}
}
修改 book_manager.jsp 页面中的 删除 链接:
删除
并且为这个链接 增加 删除提示功能:
$(this).parent().parent().find("td:first").text()
$(this).parent() :表示 a 标签的父标签 td
$(this).parent().parent(). :表示 td 标签的父标签 tr
$(this).parent().parent().find("td:first").text() : 表示 tr 中 第一个 td 标签里面的内容。
在 BookServlet 中 提供一个 删除 的方法 :
public class BookServlet extends BaseServlet {
private BookService bookService = new BookServiceImpl();
//查询所有的数据,并显示到 book_manager.jsp 页面上
protected void list(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
List list = bookService.queryForList();
request.setAttribute("list",list);
/*请求转发到:book_manager.jsp页面 */
request.getRequestDispatcher("/pages/manager/book_manager.jsp").forward(request,response);
}
//增加图书功能
protected void add(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
/*获取请求参数*/
Book book = WebUtils.copyParamToBean(request.getParameterMap(),new Book());
int i = bookService.addBook(book);
if (i != 0){
/*重定向
* 一定要重定向:不要使用转发,使用转发有 bug,刷新一次页面,数据库数据就会增加一条。
* 或者直接调用 list 方法 也是可以的。
* */
response.sendRedirect(""+request.getContextPath()+"/manager/bookServlet?action=list");
}
}
protected void delete(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
/*获取参数*/
Book book = WebUtils.copyParamToBean(request.getParameterMap(), new Book());
bookService.delBook(book.getId());
//删除完数据,重新刷新
response.sendRedirect(""+request.getContextPath()+"/manager/bookServlet?action=list");
}
}
修改功能分为俩部分:1、回显修改图书的数据 2、进行修改
1、回显修改图书的数据 【根据ID查询数据】
修改 book_manager.jsp 页面中 修改 的链接:
<%--先获取修改图书的数据--%>
修改
在 BookServlet 中 提供一个 getBookInfo 的方法,用于回显数据:
public class BookServlet extends BaseServlet {
private BookService bookService = new BookServiceImpl();
//查询所有的数据,并显示到 book_manager.jsp 页面上
protected void list(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
List list = bookService.queryForList();
request.setAttribute("list",list);
/*请求转发到:book_manager.jsp页面 */
request.getRequestDispatcher("/pages/manager/book_manager.jsp").forward(request,response);
}
//增加图书功能
protected void add(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
/*获取请求参数*/
Book book = WebUtils.copyParamToBean(request.getParameterMap(),new Book());
int i = bookService.addBook(book);
if (i != 0){
/*重定向
* 一定要重定向:不要使用转发,使用转发有 bug,刷新一次页面,数据库数据就会增加一条。
* 或者直接调用 list 方法 也是可以的。
* */
response.sendRedirect(""+request.getContextPath()+"/manager/bookServlet?action=list");
}
}
protected void delete(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
/*获取参数*/
Book book = WebUtils.copyParamToBean(request.getParameterMap(), new Book());
bookService.delBook(book.getId());
//删除完数据,重新刷新
response.sendRedirect(""+request.getContextPath()+"/manager/bookServlet?action=list");
}
//回显修改图书数据
protected void getBookInfo(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
Book book = WebUtils.copyParamToBean(request.getParameterMap(), new Book());
Book forByID = bookService.queryForByID(book.getId());
/*将查到的数据存到 request 域中*/
request.setAttribute("book",forByID);
/*转发到 修改页面*/
request.getRequestDispatcher("/pages/manager/book_edit.jsp").forward(request,response);
}
}
修改 book_edit.jsp 页面中表单的 value 值,从 request 域中获取数据:
2、进行修改
在进行修改时注意:由于修改和增加在一个 book_edit.jsp 页面中,并且只有一个隐藏域,该如何区分是修改还是增加呢?
解决方法:判断请求参数中是否有 ID,有ID 是修改,没有是增加。
向 BookServlet 中增加 update 方法:
public class BookServlet extends BaseServlet {
private BookService bookService = new BookServiceImpl();
//查询所有的数据,并显示到 book_manager.jsp 页面上
protected void list(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
List list = bookService.queryForList();
request.setAttribute("list",list);
/*请求转发到:book_manager.jsp页面 */
request.getRequestDispatcher("/pages/manager/book_manager.jsp").forward(request,response);
}
//增加图书功能
protected void add(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
/*获取请求参数*/
Book book = WebUtils.copyParamToBean(request.getParameterMap(),new Book());
int i = bookService.addBook(book);
if (i != 0){
/*重定向
* 一定要重定向:不要使用转发,使用转发有 bug,刷新一次页面,数据库数据就会增加一条。
* 或者直接调用 list 方法 也是可以的。
* */
response.sendRedirect(""+request.getContextPath()+"/manager/bookServlet?action=list");
}
}
protected void delete(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
/*获取参数*/
Book book = WebUtils.copyParamToBean(request.getParameterMap(), new Book());
bookService.delBook(book.getId());
//删除完数据,重新刷新
response.sendRedirect(""+request.getContextPath()+"/manager/bookServlet?action=list");
}
//回显修改图书数据
protected void getBookInfo(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
Book book = WebUtils.copyParamToBean(request.getParameterMap(), new Book());
Book forByID = bookService.queryForByID(book.getId());
/*将查到的数据存到 request 域中*/
request.setAttribute("book",forByID);
/*转发到 修改页面*/
request.getRequestDispatcher("/pages/manager/book_edit.jsp").forward(request,response);
}
protected void update(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
Book book = WebUtils.copyParamToBean(request.getParameterMap(), new Book());
//进行修改
bookService.update(book);
//修改完,重新刷新数据
response.sendRedirect(""+request.getContextPath()+"/manager/bookServlet?action=list");
}
}
首先需要获取的数据:
pageNo :当前页码。
pageSize:每页显示数据条数。
pageNo 和 pageSize 都需要客户端当做参数传递给 BookServlet ,因为要根据这俩个参数求下面三个的参数。
pageTotal:总页码数
pageTotalCount / pageSize ,如果有余数 pageTotal 还需要+1.
pageTotalCount:总记录条数
需要经过 Dao层在数据库进行查询:select count(*) from t_book ;
items:每页显示的数据
需要经过 Dao层在数据库进行查询:select * from t_book limit begin,pageSize ;
begin : 开始查询的索引 begin = (pageNo -1)* pageSize
Page 类 :
// Page 对象
/**
*
* @param 分页对象使用 泛型
*/
public class Page {
public static final Integer PAGE_SIZE = 4 ;
//当前页码
private Integer pageNo ;
// 总页码
private Integer pageTotal ;
//总记录数
private Integer pageTotalCount;
//每页显示数量
private Integer pageSize = PAGE_SIZE ;
//每页数据
private List item;
public Page(Integer pageNo, Integer pageTotal, Integer pageTotalCount, Integer pageSize, List item) {
this.pageNo = pageNo;
this.pageTotal = pageTotal;
this.pageTotalCount = pageTotalCount;
this.pageSize = pageSize;
this.item = item;
}
public Page() {
}
public Integer getPageNo() {
return pageNo;
}
public void setPageNo(Integer pageNo) {
this.pageNo = pageNo;
}
public Integer getPageTotal() {
return pageTotal;
}
public void setPageTotal(Integer pageTotal) {
this.pageTotal = pageTotal;
}
public Integer getPageTotalCount() {
return pageTotalCount;
}
public void setPageTotalCount(Integer pageTotalCount) {
this.pageTotalCount = pageTotalCount;
}
public Integer getPageSize() {
return pageSize;
}
public void setPageSize(Integer pageSize) {
this.pageSize = pageSize;
}
public List getItem() {
return item;
}
public void setItem(List item) {
this.item = item;
}
@Override
public String toString() {
return "Page{" +
"pageNo=" + pageNo +
", pageTotal=" + pageTotal +
", pageTotalCount=" + pageTotalCount +
", pageSize=" + pageSize +
", item=" + item +
'}';
}
}
BookDao :
/**
* 获取总记录数
* @return
*/
Integer queryForPageTotalCount();
/**
*
* @param begin 开始的索引
* @param pageSize 显示条数
* @return 获取每页显示的数据
*/
List queryForItems(int begin, int pageSize);
BookImplDao:
/**
*
* @return 返回总记录条数
*/
@Override
public Integer queryForPageTotalCount() {
String sql = "select count(*) from t_book";
Number count = (Number) BaseDao.queryForSingle(sql);
return count.intValue();
}
/**
*
* @param begin 开始的索引
* @param pageSize 显示条数
* @return 返回每页的数据
*/
@Override
public List queryForItems(int begin, int pageSize) {
String sql = "select `id` , `name` , `author` , `price` , `sales` , `stock` , `img_path` from t_book limit ?,?";
return BaseDao.queryForList(Book.class,sql,begin,pageSize);
}
BookService :
/**
*
* @return 获取总记录数
*/
int queryForPageTotalCount();
/**
*
* @param pageNo
* @param pageSize
* @return 获取 page 对象
*/
Page page(int pageNo, int pageSize);
BookServiceImpl :
/**
*
* @return 获取总记录数
*/
@Override
public int queryForPageTotalCount() {
int count = bookDao.queryForPageTotalCount();
return count;
}
/**
* 在 Service 层为 page对象中的属性赋值
* @param pageNo
* @param pageSize
* @return page 对象
*/
@Override
public Page page(int pageNo, int pageSize) {
Page page = new Page<>();
//设置当前页码
page.setPageNo(pageNo);
//设置每页显示数量
page.setPageSize(pageSize);
//获取总记录数
int pageTotalCount = bookDao.queryForPageTotalCount();
//设置总记录数
page.setPageTotalCount(pageTotalCount);
//获取 总页码 pageTotal
int pageTotal = pageTotalCount / pageSize ;
//如果 还有余数,总页码要进行加一
if (pageTotalCount % pageSize > 0){
pageTotal++;
}
//设置总页码
page.setPageTotal(pageTotal);
//获取每页显示数据
int begin = (page.getPageNo() -1) * pageSize ;
List items = bookDao.queryForItems(begin,pageSize);
//设置每页显示的数据
page.setItem(items);
return page;
}
BookServlet :
//分页处理
protected void page(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//获取请求参数中的当前页码。
//如果没有这个参数,默认从 第一页 开始
int pageNo = WebUtils.parseInt(request.getParameter("pageNo"),1);
//没有规定每页显示条数,就使用默认值
int pageSize = WebUtils.parseInt(request.getParameter("pageSize"),Page.PAGE_SIZE);
//获取 page 对象
Page page = bookService.page(pageNo,pageSize);
//将 page 对象保存到 request 域中
request.setAttribute("page",page);
//转发 到book_manager.jsp页面
request.getRequestDispatcher("/pages/manager/book_manager.jsp").forward(request,response);
}
在 book_manager.jsp 页面中获取 request 域中 page 对象中的 item 数据:
注意:不要写错:page 类中的 item 才是从数据库中查询出来的数据。不要写成 page.name。。。
首页: 将 pageNo 设置为 1
上一页:将 pageNo -1 .
下一页:将 pageNo +1
末页:将 pageNo 设置为总页码数 pageNo = pageTotal
到 首页的时候,上一页和首页应该设置为不可见,或者无法使用的状态,到末页的时候,下一页和末页应该设置为不可见,或者无法使用的状态
解决方法:只需要 判断 pageNo 的值,如果大于1,就显示上一页和首页。小于 pageTotal 时就显示下一页和末页
跳到指定页码步骤:
为 确定 按钮绑上点击事件
获取文本框中的val
将 pageNo = val 作为参数,使用 location 跳转到 BookServlet ,执行 page 方法。
为了防止输入的页码数超过总页码数:
其实在 BookServiceImpl 中设置 pageNo 值的时候,加入一个判断就好了,如果 pageNo < 1 , 就让 pageNo =1 自动跳转到第一页,如果 pageNo > pageTotal ,就让 pageNo = pageTotal,
自动跳到最后一页、
//页码边界检查
if (pageNo < 1){
pageNo = 1 ;
}
if (pageNo > pageTotal){
pageNo = pageTotal ;
}
//设置当前页码
page.setPageNo(pageNo);
一共有俩种情况: 【具体情况和页面上显示的页码数有关系,比如页面上显示 7 个页码,那么 就分小于 7 和 大于 7 】
一、总页码小于等于5
1 2 3 4 5 无论点击哪个页码,5个页码总是在页面上显示。
二、总页码大于5。假设一共有 10 页,那么又会分三种情况。
1、当前页码属于前三页【1,2,3】,那么页面显示为:
1 2 3 4 5 页码范围就是: 1 ~ 5
2、当前页码属于后三页【8,9,10】,那么页面显示为:
6 7 8 9 10 页码范围是:pageTotal -4 ~ pageTotal
3、当前页码属于中间页【4,5,6,7】,页面显示为:
2 3 【4】 5 6
3 4 【5】 6 7
4 5 【6】 7 8
5 6 【7】 8 9
页码范围是: pageNo -2 ~ pageNo +2
<%--页码显示--%>
<%--情况 1:如果总页码小于等于 5 的情况,页码的范围是:1-总页码--%>
<%--当前页码加个标记--%>
【${i}】
<%--点击页码跳转到对应的页码上--%>
${i}
<%--情况 2:总页码大于 5 的情况。假设一共 10 页--%>
<%--小情况 1:当前页码为前面 3 个:1,2,3 的情况,页码范围是:1-5.--%>
<%--当前页码加个标记--%>
【${i}】
<%--点击页码跳转到对应的页码上--%>
${i}
<%--小情况 2:当前页码为最后 3 个,8,9,10,页码范围是:总页码减 4 - 总页码--%>
<%--当前页码加个标记--%>
【${i}】
<%--点击页码跳转到对应的页码上--%>
${i}
<%--小情况 3:4,5,6,7,页码范围是:当前页码减 2 - 当前页码加 2--%>
<%--当前页码加个标记--%>
【${i}】
<%--点击页码跳转到对应的页码上--%>
${i}
代码优化:
每种判断都需要 forEach 遍历,代码重复太多,其实改变的也就是 遍历 的范围 begin 和 end ,我们只需要记录 begin 和 end 的范围,在判断完毕之后进行遍历即可。
<%--页码显示--%>
<%--情况 1:如果总页码小于等于 5 的情况,页码的范围是:1-总页码--%>
<%--当前页码加个标记--%>
【${i}】
<%--点击页码跳转到对应的页码上--%>
${i}
<%--情况 2:总页码大于 5 的情况。假设一共 10 页--%>
<%--小情况 1:当前页码为前面 3 个:1,2,3 的情况,页码范围是:1-5.--%>
<%--记录 begin 和 end--%>
<%--小情况 2:当前页码为最后 3 个,8,9,10,页码范围是:总页码减 4 - 总页码--%>
<%--记录 begin 和 end--%>
<%--小情况 3:4,5,6,7,页码范围是:当前页码减 2 - 当前页码加 2--%>
<%--记录 begin 和 end--%>
<%--进行遍历--%>
<%--当前页码加个标记--%>
【${i}】
<%--点击页码跳转到对应的页码上--%>
${i}
由于我们做了分页查询之后,修改、删除、增加完在跳到 list 就不行了,这是因为我们在页面上获取的数据是从 page 属性里得到的,我们需要跳转到 page。
增加:
当我们增加完之后,我们希望跳转到最后一页,能够看到我们增加的数据。
只需要将 总页码 当做参数传给 BookServlet 中。
修改:
删除:
我们可以在/pages/client 目录下 新建一个 index.jsp,之前的 index.jsp 页面 只负责转发到Servlet 中。Servlet 转发到 新的 index.jsp 页面中、
/pages/client/index.jsp : 只留一个 将之前做好的 分页条 拷贝到 index.jsp 页面中,并把 /manager/bookServlet 修改为:/client/bookServlet 在前台和后台进行分页处理的时候,我们发现只有这个 请求地址不同。其他的都一样,那么我们就可以将 这个请求地址 在 Servlet 中设置好。然后将分页条的代码封装起来。 首先在 Page实体类中 设置一个 url 属性,用来设置 请求地址。 分别在 BookServlet 和 ClientServlet 中设置 前台和后台的 请求地址: 使用 ${requestScope.page.url } 代替请求地址。并且将 前台和后台的分页条代码封装到 /common/page_ngv.jsp 中: 在页面中 分别使用静态包含引入: 修改 index.jsp 页面 价格搜索的表单。 点击查询之后,会将 min,max 请求参数发送给 ClientServlet x向ClientServlet 中增加 getPagePrice 方法,处理用户价格搜索并且分页: BookService 接口: BookService 实现类: BookDao: BookDao实现类: 在登录成功后,显示 : 欢迎 用户名。 只需要在登陆成功的时候,将用户名保存到 session 域中。在 jsp 页面中取出来即可。 在登录成功 和 不登录的时候,主页面的不同: 登陆成功后: 未登录时: 在 index.jsp 页面处,判断 session域中是否有 username的值: 1、清除session域 2、重定向到 主页 或者 登录页面 修改注销请求地址: 解决办法:使用验证码 使用谷歌验证码: 1、增加 jar 包 2、在 web.xml文件中配置 KaptchaServlet 类 。这个类是jar包中人家写好的类。 3、在表单中使用 img 标签,使用验证码。 4、在 loginServlet 中 获取session中的验证码并用变量保存起来,删除 session 中的验证码,并验证 表单中的验证码 与 session 中是否一致。 session 域中 的 KEY :com.google.code.kaptcha.Constants类中的KAPTCHA_SESSION_KEY 变量 session 域中取出验证码后,一定要删除。千万别忘记!!!!!! 1、引进 jar 包 2、配置 web.xml 文件 3、使用 img 标签并使用验证码 4、Servlet 中获取 验证码并进行判断。 动态获取验证码,。每点击一次验证码图片就更换验证码: 给 验证码图片增加一个点击事件,每次单击都发送 验证码 链接: 由于浏览器中都有缓存机制,访问相同的路径会增加到缓存当中。缓存一般由:请求地址+参数 决定,所以我们可以增加一个 可变的参数。每次点击图片都更改参数。 由于购物车 是使用 session 实现的,不需要与数据库交互,不用写Dao和Service 层。 为 加入购物车 按钮 绑上点击事件,只要单击它就发送请求信息到 CartServlet 中。 注意:这里不要加 ID 绑上点击事件,因为这个按钮再forEach循环里,ID是唯一的,class可以为多个按钮榜单击击事件 在 CartServlet 中创建 addItem 方法:增加图书到购物车。 1、获取图书的ID 2、根据ID,bookService.queryById 查询图书的信息。 3、将 book 转换为 CartItem 。 4、创建购物车,。并且自始至终只用这一个购物车。 5、将 图书 增加到购物车 6、重定向。 购物车中的信息都保存在了 session域中,所以直接从 session 域中取出数据就行了。 cart.jsp 页面: CartServlet 中增加删除商品的方法: 点击 清空购物车 时要进行提示:确定要清除购物车吗? cart.jsp 页面: CartServlet : 为 购物车中的 商品数量 加一个文本框: 使用 change 事件,当文本框中 value 发生变化时,发送请求给 CartServlet CartServlet: 在增加图书到购物车时,往session域中保存最后一个增加商品的信息。 index.jsp 页面: 在 CartServlet 的 addItem 方法中增加: 订单:就是页面中的内容。包日期,金额,状态等信息。用户编号是用于区分该订单是属于哪个用户的。 订单项:当我们点击 查看详情 时,会出现类似于购物车中的内容,有商品名称,数量...等等一些属性,orderId:用于区分查看的哪个订单。 订单中有些功能管理员和用户是不一样的。管理员:查看所有订单,负责发货,查看订单详情。用户:查看订单详情,查看我的订单,签收订单。 订单: 订单项: OrderDao 和 OrderItemDao:一个实体类对应一个Dao OrderDaoImpl 和 OrderItemImpl : OrderService :负责生成订单。一个模块对应一个 service ,模块中有几个功能,,模块就对应几个实现的方法。 Service 中不仅要实现生成订单的功能,还要实现对商品的销量和库存动态的修改。 OrderServlet : 修改页面跳转的路径:点击 去结账,生成订单,。 在 checkout.jsp 页面显示订单号: 使用拦截器对 manager目录下的所有页面进行拦截,只有登录后才能访问。 拦截操作: 配置文件: 目前程序中的bug: 假设我们在生成订单的时候,正好在生成完订单,即将保存订单项的时候。出现了一个异常,那么在数据库中有订单的信息,但是并没有订单项的信息。 所以我们希望使用JDBC手动提交的方式操作数据库。 分析如下: 我们的编写程序时,并没有创建线程,所以整个项目都是在一个线程中完成的。 下面进行修改: JdbcUtils : 确保我们整个项目中的 Jdbc 操作都使用一个 Connection 连接对象。 BaseDao: BaseDao 中 一定要记得抛出异常,将异常抛给 Servlet 中处理。 OrderServlet: 我们不仅仅是给订单的业务加上try...catch ,我们希望给所有的业务都加上 try'...catch,那么有没有一种方法可以一次性的给所有的业务都加上Servlet ? 可以的,使用 Filter 过滤器。我们知道 Filter 的作用就是:调用下一个Filter 或者 资源,【Servlet 也是资源】,那么如果在 Servlet 中出现异常的话,他会继续抛给它的上级 Filter,所以我们就可以在 Filter 中统一捕捉异常。 当我们在 Filter 中加上try..catch后, 当 service 层出现异常后,并没有回滚事务,而是提交事务,这就出现问题了。这是为什么呢? 原因是:在 Servlet 中继承了 BaseServlet,当出现异常后,他会抛给BaseServlet,但是呢,BaseServlet 却是可以捕捉异常,那么在 Filter 中就会捕捉不到异常了。当然会出问题了,所以我们在 BaseServlet 中也要把异常抛给 Filter。 现在出现异常就会回滚事务,不会提交了。 Filter : web.xml: 出现异常时,我们虽然在后台处理了,不会影响数据。但是在页面中,并没有展示出来,还是一片空白,用户看见了根本不知道是啥,所以我们希望能够展示错误页面提示用户。 准备好俩个错误页面,一个用于500提示,一个用于 404提示。 错误页面可以再web.xml 文件中配置: 最后一定要在 TransactionFilter 中把异常抛出去,不然服务器接收不到异常,就不会跳转错误页面。 UserServlet : regist.jsp 页面: CartServlet : index.jsp 页面:5.8、分页条的抽取
5.9、价格区间搜索
//价格搜索分页
protected void getPageByPrice(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//获取请求参数中的当前页码。
//如果没有这个参数,默认从 第一页 开始
int pageNo = WebUtils.parseInt(request.getParameter("pageNo"), 1);
//没有规定每页显示条数,就使用默认值
int pageSize = WebUtils.parseInt(request.getParameter("pageSize"), Page.PAGE_SIZE);
// 获取 价格区间的最大值和最小值
int min = WebUtils.parseInt(request.getParameter("min"), 0);
int max = WebUtils.parseInt(request.getParameter("max"), Integer.MAX_VALUE);
//获取 page 对象
Page
/**
* 根据价格区间 获取数据并进行分页
* @param pageNo
* @param pageSize
* @param min
* @param max
* @return 返回 page 对象
*/
Page
@Override
public Page
/**
* 根据价格区间,获取总记录数
* @param min
* @param max
* @return
*/
int queryForPageTotalCountByPrice(int min, int max);
/**
* 根据价格区间搜索,并进行分页处理
* @param begin
* @param pageSize
* @param min
* @param max
* @return
*/
List
public int queryForPageTotalCountByPrice(int min, int max) {
String sql = "select count(*) from t_book where price between ? and ?";
Number count = (Number)BaseDao.queryForSingle(sql, min, max);
return count.intValue();
}
/**
* 根据价格区间搜索,并进行分页处理
* @param begin
* @param pageSize
* @param min
* @param max
* @return
* 为了用户体验,查询完使用 order by根据价格高低进行排序。
*/
@Override
public List
第六阶段:登录用户信息显示
6.1、登录后显示 欢迎信息
6.2、注销登录
/*处理注销业务*/
protected void loginOut(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 删除 session
request.getSession().invalidate();
// 重定向到 主页
response.sendRedirect(request.getContextPath());
}
注销
6.3、表单重复提交问题
protected void login(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String username = req.getParameter("username");
// 获取到 session 中的验证码
// key:是 jar 包中设置好的变量。
//com.google.code.kaptcha 下的 KAPTCHA_SESSION_KEY 变量;
String token = (String) req.getSession().getAttribute(KAPTCHA_SESSION_KEY);
// 删除 session 中的验证码,防止重复提交
req.getSession().removeAttribute(KAPTCHA_SESSION_KEY);
//获取 表单中的验证码
String code = req.getParameter("code");
//判断
if (token != null && token.equals(code)){
System.out.println("登录成功。");
//第一种问题:转发, 会造成重复提交表单的问题。使用重定向可解决问题
// req.getRequestDispatcher("login_success.jsp").forward(req,resp);
/* try {
//第二种问题:当服务器出现延迟时,不断点击 登录 也会重复提交
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}*/
resp.sendRedirect("login_success.jsp");
}else{
System.out.println("不要重复提交表单");
}
}
6.4、将验证码增加到书城项目中
<%--使用 谷歌验证码--%>
/*给图片一个点击事件*/
$("#code_img").click(function () {
// src : 是 指当前正在响应的DOM对象。这个对象可读,可写。
// 设置动态的验证码,谷歌浏览器可以用,但是火狐和IE会把相同地址的路径增加到缓存里去
//所以并不会动态显示。
//解决办法:可以增加一个参数,每次点击都会改变参数
this.src = "http://localhost:8080/book/kaptcha.jpg?a="+new Date() ;
});
第七阶段:购物车模块
7.1、购物车模型创建
/*购物车商品模型*/
public class CartItems {
//商品id
private Integer id ;
//商品名
private String name ;
//商品数量
private Integer count ;
//商品价格
private BigDecimal price;
//总价格
private BigDecimal totalPrice ;
public CartItems() {
}
public CartItems(Integer id, String name, Integer count, BigDecimal price, BigDecimal totalPrice) {
this.id = id;
this.name = name;
this.count = count;
this.price = price;
this.totalPrice = totalPrice;
}
@Override
public String toString() {
return "CartItems{" +
"id=" + id +
", name='" + name + '\'' +
", count=" + count +
", price=" + price +
", totalPrice=" + totalPrice +
'}';
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getCount() {
return count;
}
public void setCount(Integer count) {
this.count = count;
}
public BigDecimal getPrice() {
return price;
}
public void setPrice(BigDecimal price) {
this.price = price;
}
public BigDecimal getTotalPrice() {
return totalPrice;
}
public void setTotalPrice(BigDecimal totalPrice) {
this.totalPrice = totalPrice;
}
}
/*购物车模型*/
public class Cart {
//总商品数量
private Integer totalCount ;
//总商品价格
private BigDecimal totalPrice ;
// 购物车商品
private List
7.2、 购物车功能的实现
/*购物车模型*/
public class Cart {
//总商品数量
//由于不需要人为更改,不需要使用全局变量
// private Integer totalCount ;
//总商品价格
// private BigDecimal totalPrice ;
// 购物车商品
//使用 map 集合。 key:商品id, value:商品信息。
private Map
7.3、增加购物车功能的实现
//实现购物车模块的一些功能
public class CartServlet extends BaseServlet {
private BookService bookService = new BookServiceImpl();
//增加购物车
protected void addItem(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//1、获取图书的id
int id = WebUtils.parseInt(request.getParameter("id"),0);
//2、根据 id 查询图书的信息
Book book = bookService.queryForByID(id);
//3、将图书信息转换为 CartItems
CartItems cartItems = new CartItems(book.getId(),book.getName(),1,book.getPrice(),book.getPrice());
//4、将 购物车cart 放到 session域中,购物车只能有一个。
//以下 代码保证了:自始至终只用了一个购物车。这样才能对相同的图书进行计数。
Cart cart = (Cart) request.getSession().getAttribute("cart");
if (cart == null){
//创建购物车
cart = new Cart();
//放到 session 域中
request.getSession().setAttribute("cart",cart);
}
//5、增加到购物车
cart.addItem(cartItems);
System.out.println(cart);
/** response.sendRedirect("index.jsp");
* 这样重定向还有一个 bug,他跳转的并不是原来的页面。无论在第几页增加的购物车。他跳转的总是 首页。
* 那么我们希望在第二页增加的购物车还跳转到第二页。
* HTTP协议中请求头中有一个 Referer 参数:他保存了 页面 的地址。我们只需要将跳转的地址给成 Referer 的地址即可。
*/
System.out.println(request.getHeader("Referer"));
response.sendRedirect("Referer 保存的地址:" +request.getHeader("Referer"));
}
}
7.4、展示购物车
7.5、删除商品
<%--发送删除请求--%>
删除
//删除商品操作
protected void deleteItem(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
int id = WebUtils.parseInt(request.getParameter("id"), 0);
Cart cart = (Cart) request.getSession().getAttribute("cart");
if (cart != null){
//删除
cart.deleteItem(id);
System.out.println(cart);
//重定向
response.sendRedirect(request.getHeader("Referer"));
}
}
7.6、清空购物车
清空购物车
$(".clear").click(function () {
return confirm("清空购物车吗?");
});
//清空购物车
protected void clear(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//1、 获取购物车
Cart cart = (Cart) request.getSession().getAttribute("cart");
if (cart != null){
// 清空购物车
cart.clear();
//重定向到原来的页面
response.sendRedirect(request.getHeader("Referer"));
}
}
7.7、修改购物车商品数量
<%--bookId:自定义的标签。--%>
//修改商品数量
protected void updateCount(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//获取参数
int id = WebUtils.parseInt(request.getParameter("id"),0);
int count = WebUtils.parseInt(request.getParameter("count"),1);
//获取购物车
Cart cart = (Cart) request.getSession().getAttribute("cart");
if (cart != null){
//修改
cart.updateCount(id,count);
//重定向到原来的页面
response.sendRedirect(request.getHeader("Referer"));
}
}
7.8、首页购物车信息回显
// 将最后一次放到购物车中的商品 增加到 session 域中
request.getSession().setAttribute("lastItem",cartItems.getName());
第八阶段:订单模块
8.1、订单模型创建分析
8.2、订单与订单项数据库创建
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`) )
8.3、订单项与订单的实体类创建
public class Order {
private String orderId ;
private Date createTime ;
private BigDecimal price ;
//订单状态,0表示未发货,1 表示已发货,2 表示 已签收
private Integer status = 0;
private String userId ;
public Order() {
}
public Order(String orderId, Date createTime, BigDecimal price, Integer status, String userId) {
this.orderId = orderId;
this.createTime = createTime;
this.price = price;
this.status = status;
this.userId = userId;
}
@Override
public String toString() {
return "Order{" +
"orderId='" + orderId + '\'' +
", createTime=" + createTime +
", price=" + price +
", status=" + status +
", userId='" + userId + '\'' +
'}';
}
public String getOrderId() {
return orderId;
}
public void setOrderId(String orderId) {
this.orderId = orderId;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
public BigDecimal getPrice() {
return price;
}
public void setPrice(BigDecimal price) {
this.price = price;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
}
//订单项
public class OrderItem {
private Integer id ;
private String name ;
private Integer count ;
private BigDecimal price ;
private BigDecimal totalPrice ;
//订单编号
private String orderId;
public OrderItem() {
}
@Override
public String toString() {
return "OrderItem{" +
"id=" + id +
", name='" + name + '\'' +
", count=" + count +
", price=" + price +
", totalPrice=" + totalPrice +
", orderId='" + orderId + '\'' +
'}';
}
public OrderItem(Integer id, String name, Integer count, BigDecimal price, BigDecimal totalPrice, String orderId) {
this.id = id;
this.name = name;
this.count = count;
this.price = price;
this.totalPrice = totalPrice;
this.orderId = orderId;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getCount() {
return count;
}
public void setCount(Integer count) {
this.count = count;
}
public BigDecimal getPrice() {
return price;
}
public void setPrice(BigDecimal price) {
this.price = price;
}
public BigDecimal getTotalPrice() {
return totalPrice;
}
public void setTotalPrice(BigDecimal totalPrice) {
this.totalPrice = totalPrice;
}
public String getOrderId() {
return orderId;
}
public void setOrderId(String orderId) {
this.orderId = orderId;
}
}
8.4、生成订单功能的实现
Dao 层:
public interface OrderDao {
//保存订单
int saveOrder(Order order);
}
public interface OrderItemDao {
//保存订单项
int saveOrderItem(OrderItem orderItem);
}
public class OrderDaoImpl extends BaseDao implements OrderDao {
/**
* 由于 user_id 有外键约束,一定与t_user表中的id,相对应。
* @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 BaseDao.update(sql,order.getOrderId(),order.getCreateTime(),order.getPrice(),
order.getStatus(),order.getUserId());
}
}
public class OrderItemDaoImpl extends BaseDao implements OrderItemDao {
/**
* 生成订单项
* @param orderItem
* @return
*/
@Override
public int saveOrderItem(OrderItem orderItem) {
String sql = "insert into t_order_item(name,count,price,total_price,order_id) values(?,?,?,?,?)";
return update(sql,orderItem.getName(),orderItem.getCount(),orderItem.getPrice(),orderItem.getTotalPrice(),orderItem.getOrderId());
}
}
Service 层:
public interface OrderService {
/**
* 生成订单
* @param cart 购物车
* @param userId 用户id
* @return 返回订单号
*/
String createOrder(Cart cart,Integer userId);
}
public class OrderServiceImpl implements OrderService {
private OrderDao orderDao = new OrderDaoImpl();
private OrderItemDao orderItemDao = new OrderItemDaoImpl();
private BookDao bookDao = new BookImplDao();
@Override
/**
* 生成订单方法实现
*/
public String createOrder(Cart cart, Integer userId) {
//获取订单号
//订单号的特定===唯一性。
//我们可以加上 时间戳+用户id 生成订单号
String orderId = System.currentTimeMillis() + "" + userId;
//生成订单
Order order = new Order(orderId, new Date(), cart.getTotalPrice(), 0, userId);
// 保存到数据库中
orderDao.saveOrder(order);
//生成订单项
//将 购物车中 items 的所有商品项 转换为订单项
Map
Web 层:
public class OrderServlet extends BaseServlet {
private OrderService orderService = new OrderServiceImpl();
protected void createOrder(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//从 session 域中取出购物车和userid
Cart cart = (Cart) request.getSession().getAttribute("cart");
User loginUser = (User) request.getSession().getAttribute("user");
//如果 loginUser 为空,说明还没有进行登录,跳转到 登录界面
if (loginUser == null){
//重定向记得加上 根路径
response.sendRedirect(request.getContextPath()+"/pages/user/login.jsp");
//一般跳转,重定向之后,不要再执行代码了。
return;
}
Integer userId = loginUser.getId();
//生成订单
String orderId = orderService.createOrder(cart, userId);
//将 订单号 放到 session域中
request.getSession().setAttribute("orderId",orderId);
//重定向到 /pages/cart/checkout.jsp 页面
response.sendRedirect(request.getContextPath() + "/pages/cart/checkout.jsp");
}
}
去结账
你的订单已结算,订单号为${sessionScope.orderId}
第九阶段:使用 Filter 和 ThreadLocal 完善系统
9.1、使用Filter拦截器:
public class ManagerFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@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){
//没有登录的情况下,跳转到登录界面
servletRequest.getRequestDispatcher("/pages/user/login.jsp").forward(servletRequest,servletResponse);
}else{
filterChain.doFilter(servletRequest,servletResponse);
}
}
@Override
public void destroy() {
}
}
9.2、使用 ThreadLocal:
public class JdbcUtils {
private static DruidDataSource source;
// 使用 ThreadLocal
private static ThreadLocal
/*使用DBUtils操作数据库增删改查*/
public class BaseDao {
private static QueryRunner runner = new QueryRunner();
/*通用的增删改*/
public static int update(String sql, Object... args) {
Connection conn = JdbcUtils.getConnection();
int count = -1;
try {
/*影响数据库的条数*/
count = runner.update(conn, sql, args);
} catch (SQLException e) {
e.printStackTrace();
//这里一定要抛出异常。在 Service 层处理提交事务或者回滚。
//如果不抛出异常,在 OrderServlet 中 不会进行提交事务或者回滚事务的处理,就会报错。
throw new RuntimeException(e);
// 在使用 ThreadLocal 后,就不需要在关闭了,因为关闭是和提交或者回滚事务一起的。
// }finally {
// JdbcUtils.closeConnection(conn);
}
return count;
}
/*查询:只返回一条数据*/
public static
public class OrderServlet extends BaseServlet {
private OrderService orderService = new OrderServiceImpl();
protected void createOrder(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//从 session 域中取出购物车和userid
Cart cart = (Cart) request.getSession().getAttribute("cart");
User loginUser = (User) request.getSession().getAttribute("user");
//如果 loginUser 为空,说明还没有进行登录,跳转到 登录界面
if (loginUser == null){
//重定向记得加上 根路径
response.sendRedirect(request.getContextPath()+"/pages/user/login.jsp");
//一般跳转,重定向之后,不要再执行代码了。
return;
}
Integer userId = loginUser.getId();
//在生成订单时,处理 BaseDao 中抛过来的异常。这也是为什么在BaseDao中需要抛出异常。
String orderId = null;
try {
//生成订单
orderId = orderService.createOrder(cart, userId);
JdbcUtils.commitAndClose();//提交事务并且释放资源
} catch (Exception e) {
//如果出现异常,就回滚事务并且释放资源
JdbcUtils.rollbackAndClose();
e.printStackTrace();
}
//将 订单号 放到 session域中
request.getSession().setAttribute("orderId",orderId);
//重定向到 /pages/cart/checkout.jsp 页面
response.sendRedirect(request.getContextPath() + "/pages/cart/checkout.jsp");
}
}
9.3、使用 FIlter 统一给所有的 service 方法加上 try。。catch
public class TransactionFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
// Filter 中统一处理异常。
try {
filterChain.doFilter(servletRequest,servletResponse);
JdbcUtils.commitAndClose(); //提交事务
} catch (Exception e) {
JdbcUtils.rollbackAndClose(); //回滚事务
e.printStackTrace();
}
}
@Override
public void destroy() {
}
}
9.4、使用 Tomcat 统一管理异常,展示友好的错误页面
第十阶段:使用 Ajax 验证用户名是否可用
// 使用 Ajax 验证用户名是否可用
protected void ajaxExistUsername(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//获取参数
String username = request.getParameter("username");
boolean existsUsername = userService.existsUsername(username);
// 将结果封装成 Map 集合
Map
// 使用 Ajax 验证用户名是否可用
$("#username").blur(function () {
//获取参数
var username = this.value;
// 发送请求
$.getJSON("http://localhost:8080/book/user","action=ajaxExistUsername&username="+username,function (data) {
if (data.existsUsername){
$("span.errorMsg").text("用户名已存在");
} else{
$("span.errorMsg").text(" √ 用户名可用");
}
});
});
使用 Ajax 修改增加购物车:
//使用 Ajax 增加购物车
protected void ajaxAddItem(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//1、获取图书的id
int id = WebUtils.parseInt(request.getParameter("id"), 0);
//2、根据 id 查询图书的信息
Book book = bookService.queryForByID(id);
//3、将图书信息转换为 CartItems
CartItems cartItems = new CartItems(book.getId(), book.getName(), 1, book.getPrice(), book.getPrice());
//4、将 购物车cart 放到 session域中,购物车只能有一个。
//以下 代码保证了:自始至终只用了一个购物车。这样才能对相同的图书进行计数。
Cart cart = (Cart) request.getSession().getAttribute("cart");
if (cart == null) {
//创建购物车
cart = new Cart();
//放到 session 域中
request.getSession().setAttribute("cart", cart);
}
//5、增加到购物车
cart.addItem(cartItems);
System.out.println("购物车的情况:"+cart);
System.out.println("Referer 保存的地址:" + request.getHeader("Referer"));
// 使用 Ajax 响应到 jsp页面。
Map