JavaWeb书城项目
一、第一阶段对注册页面的信息进行验证:要求:验证用户名:必须由字母,数字下划线组成,并且长度为 5 到 12 位验证密码:必须由字母,数字下划线组成,并且长度为 5 到 12 位验证确认密码:和密码相同邮箱验证:xxxxx@xxx.com验证码:现在只需要验证用户已输入。因为还没讲到服务器。验证码生成<script type="text/javascript">// 页面加载完成之后$
尚硅谷书城项目:自己整理的笔记以及全部实现过程,原理。
链接: 点击获取资源
提取码: ih2c
再次感谢尚硅谷,我爱尚硅谷!!!!
目录
2.3、工具类——JdbcUtils工具类:用于连接数据库。
2.4、提供BaseDao:操作数据库的方法【可继承BaseDao实现数据库操作】
1、将所有的 html 页面 替换成 jsp 页面。并增加头部标签。
第九阶段:使用 Filter 和 ThreadLocal 完善系统
9.3、使用 FIlter 统一给所有的 service 方法加上 try。。catch
9.4、使用 Tomcat 统一管理异常,展示友好的错误页面
一、第一阶段:对注册页面的信息进行验证:
要求:
验证用户名:必须由字母,数字下划线组成,并且长度为 5 到 12 位
验证密码:必须由字母,数字下划线组成,并且长度为 5 到 12 位
验证确认密码:和密码相同
邮箱验证:xxxxx@xxx.com
验证码:现在只需要验证用户已输入。因为还没讲到服务器。验证码生成
<script type="text/javascript">
// 页面加载完成之后
$(function(){
// 给注册按钮添加事件
$("#sub_btn").click(function(){
// 获取用户名
var usernameValue = $("#username").val();
// 验证用户名是否合法,规则如下:必须由字母,数字,下划线组成,并且长度为5到15位。
var usernameReg = /^\w{5,15}$/;
// 验证用户信息
if (!usernameReg.test(usernameValue)) {
// 提示用户
$("span.errorMsg").text("用户名不合法!");
return false;
}
// 获取密码
var passwordValue = $("#password").val();
// 验证密码是否合法,规则如下:必须由字母,数字,下划线组成,并且长度为5到15位。
var passwordReg = /^\w{5,15}$/;
// 验证用户信息
if (!passwordReg.test(passwordValue)) {
// 提示用户
$("span.errorMsg").text("密码不合法!");
return false;
}
// 获取确认密码
var repwdValue = $("#repwd").val();
// 验证确认密码和密码一致
if (passwordValue != repwdValue) {
// 提示用户
$("span.errorMsg").text("确认密码和密码不一致!");
return false;
}
// 获取用户名
var emailValue = $("#email").val();
// 验证邮件输入是否合法。
var emailReg = /^([a-z0-9_\.-]+)@([\da-z\.-]+)\.([a-z\.]{2,6})$/;
if (!emailReg.test(emailValue)) {
// 提示用户
$("span.errorMsg").text("邮件输入不合法!");
return false;
}
// 获取验证码信息
var codeValue = $("#code").val();
// 验证验证码不为空!
if (codeValue == "") {
$("span.errorMsg").text("验证码不能为空!");
return false;
}
/*当信息正确的时候去掉信息*/
$("span.errorMsg").text("");
return true;
});
});
</script>
二、第二阶段:用户管理模块
项目的三层架构:
Web 层 无法直接访问 Dao 层,需要借助 Service 层。
无论多复杂的项目都基本符合 javaEE 三层架构:
Dao层: 负责与数据库进行交互,一般都是有一个 BaseDao,为不同的模块提供 操作 数据库的方法【增删改查】,每一个模块或者一个业务都需要继承 BaseDao,并且 需要一个 dao接口和实现类。
Service 层:负责处理各种业务。一般是一个 service 接口 和 实现类。
Web : 服务与页面联调
项目环境搭建:
配置 Tomcat 服务器,将静态页面都放到web目录下。
包名设置:
DAO持久层:
2.1、创建数据库表
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','admin@atguigu.com');
2.2、编写User实体类
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;
}
}
2.3、工具类——JdbcUtils工具类:用于连接数据库。
(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();
}
}
}
}
2.4、提供BaseDao:操作数据库的方法【可继承BaseDao实现数据库操作】
/*使用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> T queryForOne(Connection conn, Class<T> tClass, String sql, Object... args) {
BeanHandler<T> beanHandler = new BeanHandler<T>(tClass);
T t = null;
try {
t = runner.query(conn, sql, beanHandler, args);
} catch (SQLException e) {
e.printStackTrace();
}
return t;
}
/*返回多条记录*/
public static <T> List<T> queryForList(Connection conn, Class<T> tClass, String sql, Object... args) {
BeanListHandler<T> beanList = new BeanListHandler<>(tClass);
List<T> 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<Object> handler = new ScalarHandler<>();
Object data = null;
try {
data = runner.query(conn, sql, handler);
} catch (SQLException e) {
e.printStackTrace();
}
return data;
}
}
2.5、提供DAO接口以及实现类【针对于某一张表的操作】
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);
}
}
2.6、编写Service层和实现
接口:
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 层:
注册功能的流程图:
页面路径问题:
web: base标签 + 相对路径
<!--永远固定相对路径跳转的结果--> <base href="http://localhost:8080/book/">
更改 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);
}
}
}
三、第三阶段:jsp页面动态化
1、将所有的 html 页面 替换成 jsp 页面。并增加头部标签。
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
更改之后检查页面的路径是否正确。加上 base 标签后,所有的路径都以这个路径为起始路径
<base href="http://localhost:8080/book/">
2、使用静态包含替换掉所有的 公共部分。
登录成功的菜单:
替换:
<%--使用静态包含,替换登陆成功菜单--%>
<%@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() // 根路径
+"/";
%>
<!--永远固定相对路径跳转的结果-->
<base href="<%=basePath%>">
3、在页面上回显错误信息。
登录时 输错用户名或者密码,要求显示在页面上,注册时:用户名存在或者验证码错误显示在 页面上,并且回显用户名和邮箱。
3.1 、登录时的错误回显,并回显用户名。
思路:
在 LoginServlet 程序端接受浏览器发送的请求信息【用户名和密码】,与数据库进行判断。
信息错误时,将 回显的信息保存到 request 域中。转发到 jsp 页面,使用 EL 表达式取出数据,回显到 jsp 页面上。
回显得数据存在 request 域中:
//将错误信息保存到 request域中。在 jsp 页面访问。并且回显username request.setAttribute("msg","输入的用户名或者密码错误"); request.setAttribute("username",username);
3.2、注册时回显错误信息和用户名,邮箱。
想要回显什么数据就往 域中保存数据即可【不一定都是request域】。在 jsp 页面取出。
4、优化Servlet
第一个问题:其实我们知道 注册和登录功能 都属于用户模块,那么我们可不可以将他们合并成一个 Servlet呢?
解决方法:是可以解决的,通过代码可以发现,注册和登录功能其实和请求参数是一样的,请求参数是 login执行登录方法,regist 执行注册方法。那么我们可以在登录,注册页面的表单当中增加隐藏域,使用 if...else 用来区分登录还是注册。
第二个问题:在实际的项目开发当中,并不是有一个简单业务注册和登录,包括修改密码,增加删除各种各样的业务,每一个业务都用 if....else 判断太繁琐了。有没有一种方法,可以自动判断执行哪个方法?不使用 if...else
解决办法:使用反射机制,根据 隐藏域中的请求参数,来执行不同的方法。
第三个问题:每个项目中不仅仅有一个模块,对于这个项目来说还有图书管理模块.....每个模块都需要通过反射机制调用方法,所以我们可以将 反射机制调用方法 这部分重复的代码封装到 BaseServlet 类中。BaseServlet 继承 HttpServlet ,UserServlet 继承 BaseServlet。
解决第一个问题:
增加隐藏域:
<%--增加隐藏域。方便区分登录还是注册--%>
<input type="hidden" name="action" value="login">
<%--增加隐藏域。方便区分登录还是注册--%>
<input type="hidden" name="action" value="regist">
合并成一个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();
}
}
}
5、请求参数的封装和BeanUtils工具类的使用
BeanUtils 工具类,可以一次性将所有的参数注入到 Bean 对象中。
问题:每个项目中每块业务中的请求参数是不一样的,有的项目可能有十几,几十个请求参数,如果一个个都 request.getParameter() 是非常麻烦的。解决方法: BeanUtils工具类中的 BeanUtils.copyProperties(注入的对象,Map集合) 方法可以将一个 map 集合 注入 到一个对象中。正好 请求参数 也是一个 map 集合,我们可以利用这一特征将 请求参数注入到 JavaBean 对象中
BeanUtils.copyProperties() 方法 测试:
相同的道理:每块业务可能都需要获取请求参数,所以将 BeanUtils.copyProperties() 方法 封装到一个父类当中。
//一次性获取请求参数
public class WebUtils {
/*使用泛型代替具体的某个对象*/
public static <T> T copyParamToBean(Map<String, String[]> 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 方法:
我们可以看到 username 是 null 的,并没有注入进去。
第四阶段:图书管理模块
4.1、构建数据库
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;
4.2、创建 JavaBean 实体类
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 或 "" ,是的话保留原来的值,不是才能赋值。
4.3、编写图书模块的Dao接口,以及实现类
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<Book> 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<Book> queryForList() {
String sql = "select `id` , `name` , `author` , `price` , `sales` , `stock` , `img_path` from t_book";
return BaseDao.queryForList(Book.class, sql);
}
}
4.4、编写图书模块的 service 接口 以及 实现类
public interface BookService {
int addBook(Book book);
void update(Book book);
void delBook(int id);
Book queryForByID(int id);
List<Book> 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<Book> queryForList() {
return bookDao.queryForList();
}
}
4.5、编写图书模块的Web层
创建 BookServlet 继承 BaseServlet ,实现 图书的增删改查。
配置好 xml 文件:
<servlet>
<servlet-name>BookServlet</servlet-name>
<servlet-class>yangzhaoguang.web.BookServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>BookServlet</servlet-name>
<url-pattern>/manager/bookServlet</url-pattern>
</servlet-mapping>
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<Book> 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 页面进行修改:
<td><a href="pages/manager/book_edit.jsp">添加图书</a></td>
修改 book_edit.jsp 页面中表单提交的地址,使其能够执行到 add 方法:
<form action="manager/bookServlet?action=add" method="post">
一定要修改 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<Book> 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 页面中的 删除 链接:
<td><a href="manager/bookServlet?action=delete&id=${c.id}" class="del">删除</a></td>
并且为这个链接 增加 删除提示功能:
$(this).parent().parent().find("td:first").text()
$(this):表示某个 id 正在操作 <a></a>标签
$(this).parent() :表示 a 标签的父标签 td
$(this).parent().parent(). :表示 td 标签的父标签 tr
$(this).parent().parent().find("td:first").text() : 表示 tr 中 第一个 td 标签里面的内容。
<script type="text/javascript">
//删除标签绑定单击事件
$(function () {
$(".del").click(function () {
/*
* confirm:确认框,点击确认返回true,点击取消 返回 false;
* return false ; 阻止页面跳转。
*
* */
return confirm("确定删除《"+($(this).parent().parent().find("td:first").text())+"》这本书吗?");
});
});
</script>
在 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<Book> 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 页面中 修改 的链接:
<%--先获取修改图书的数据--%>
<td><a href="manager/bookServlet?action=getBookInfo&id=${c.id}">修改</a></td>
在 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<Book> 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<Book> 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");
}
}
第五阶段:图书管理模块的分页管理
5.1、分页初步查询
首先需要获取的数据:
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 <T> 分页对象使用 泛型
*/
public class Page<T> {
public static final Integer PAGE_SIZE = 4 ;
//当前页码
private Integer pageNo ;
// 总页码
private Integer pageTotal ;
//总记录数
private Integer pageTotalCount;
//每页显示数量
private Integer pageSize = PAGE_SIZE ;
//每页数据
private List<T> item;
public Page(Integer pageNo, Integer pageTotal, Integer pageTotalCount, Integer pageSize, List<T> 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<T> getItem() {
return item;
}
public void setItem(List<T> 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<Book> 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<Book> 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<Book> 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<Book> page(int pageNo, int pageSize) {
Page<Book> 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<Book> 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<Book> 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。。。
动态获取 页码数据:
5.2、上一页,下一页,首页,末页 的实现
首页: 将 pageNo 设置为 1
上一页:将 pageNo -1 .
下一页:将 pageNo +1
末页:将 pageNo 设置为总页码数 pageNo = pageTotal
到 首页的时候,上一页和首页应该设置为不可见,或者无法使用的状态,到末页的时候,下一页和末页应该设置为不可见,或者无法使用的状态
解决方法:只需要 判断 pageNo 的值,如果大于1,就显示上一页和首页。小于 pageTotal 时就显示下一页和末页
5.3、跳到指定页码
跳到指定页码步骤:
为 确定 按钮绑上点击事件
获取文本框中的val
将 pageNo = val 作为参数,使用 location 跳转到 BookServlet ,执行 page 方法。
5.4、页码边界检查
为了防止输入的页码数超过总页码数:
其实在 BookServiceImpl 中设置 pageNo 值的时候,加入一个判断就好了,如果 pageNo < 1 , 就让 pageNo =1 自动跳转到第一页,如果 pageNo > pageTotal ,就让 pageNo = pageTotal,
自动跳到最后一页、
//页码边界检查
if (pageNo < 1){
pageNo = 1 ;
}
if (pageNo > pageTotal){
pageNo = pageTotal ;
}
//设置当前页码
page.setPageNo(pageNo);
5.5、页码显示
一共有俩种情况: 【具体情况和页面上显示的页码数有关系,比如页面上显示 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
<%--页码显示--%>
<c:choose>
<%--情况 1:如果总页码小于等于 5 的情况,页码的范围是:1-总页码--%>
<c:when test="${requestScope.page.pageTotal <= 5}">
<c:forEach begin="1" end="${requestScope.page.pageTotal}" var="i">
<%--当前页码加个标记--%>
<c:if test="${requestScope.page.pageNo == i}">
【${i}】
</c:if>
<c:if test="${requestScope.page.pageNo != i}">
<%--点击页码跳转到对应的页码上--%>
<a href="manager/bookServlet?action=page&pageNo=${i}">${i}</a>
</c:if>
</c:forEach>
</c:when>
<%--情况 2:总页码大于 5 的情况。假设一共 10 页--%>
<c:when test="${requestScope.page.pageTotal > 5}">
<c:choose>
<%--小情况 1:当前页码为前面 3 个:1,2,3 的情况,页码范围是:1-5.--%>
<c:when test="${requestScope.page.pageNo <= 3}">
<c:forEach begin="1" end="5" var="i">
<%--当前页码加个标记--%>
<c:if test="${requestScope.page.pageNo == i}">
【${i}】
</c:if>
<c:if test="${requestScope.page.pageNo != i}">
<%--点击页码跳转到对应的页码上--%>
<a href="manager/bookServlet?action=page&pageNo=${i}">${i}</a>
</c:if>
</c:forEach>
</c:when>
<%--小情况 2:当前页码为最后 3 个,8,9,10,页码范围是:总页码减 4 - 总页码--%>
<c:when test="${requestScope.page.pageNo > requestScope.page.pageTotal -3}">
<c:forEach begin="${requestScope.page.pageTotal-4}" end="${requestScope.page.pageTotal}" var="i">
<%--当前页码加个标记--%>
<c:if test="${requestScope.page.pageNo == i}">
【${i}】
</c:if>
<c:if test="${requestScope.page.pageNo != i}">
<%--点击页码跳转到对应的页码上--%>
<a href="manager/bookServlet?action=page&pageNo=${i}">${i}</a>
</c:if>
</c:forEach>
</c:when>
<c:otherwise>
<%--小情况 3:4,5,6,7,页码范围是:当前页码减 2 - 当前页码加 2--%>
<c:forEach begin="${requestScope.page.pageNo-2}" end="${requestScope.page.pageNo+2}" var="i">
<%--当前页码加个标记--%>
<c:if test="${requestScope.page.pageNo == i}">
【${i}】
</c:if>
<c:if test="${requestScope.page.pageNo != i}">
<%--点击页码跳转到对应的页码上--%>
<a href="manager/bookServlet?action=page&pageNo=${i}">${i}</a>
</c:if>
</c:forEach>
</c:otherwise>
</c:choose>
</c:when>
</c:choose>
代码优化:
每种判断都需要 forEach 遍历,代码重复太多,其实改变的也就是 遍历 的范围 begin 和 end ,我们只需要记录 begin 和 end 的范围,在判断完毕之后进行遍历即可。
<%--页码显示--%>
<c:choose>
<%--情况 1:如果总页码小于等于 5 的情况,页码的范围是:1-总页码--%>
<c:when test="${requestScope.page.pageTotal <= 5}">
<c:forEach begin="1" end="${requestScope.page.pageTotal}" var="i">
<%--当前页码加个标记--%>
<c:if test="${requestScope.page.pageNo == i}">
【${i}】
</c:if>
<c:if test="${requestScope.page.pageNo != i}">
<%--点击页码跳转到对应的页码上--%>
<a href="manager/bookServlet?action=page&pageNo=${i}">${i}</a>
</c:if>
</c:forEach>
</c:when>
<%--情况 2:总页码大于 5 的情况。假设一共 10 页--%>
<c:when test="${requestScope.page.pageTotal > 5}">
<c:choose>
<%--小情况 1:当前页码为前面 3 个:1,2,3 的情况,页码范围是:1-5.--%>
<c:when test="${requestScope.page.pageNo <= 3}">
<%--记录 begin 和 end--%>
<c:set var="begin" value="1" />
<c:set var="end" value="5" />
</c:when>
<%--小情况 2:当前页码为最后 3 个,8,9,10,页码范围是:总页码减 4 - 总页码--%>
<c:when test="${requestScope.page.pageNo > requestScope.page.pageTotal -3}">
<%--记录 begin 和 end--%>
<c:set var="begin" value="${requestScope.page.pageTotal-4}" />
<c:set var="end" value="${requestScope.page.pageTotal}" />
</c:when>
<c:otherwise>
<%--小情况 3:4,5,6,7,页码范围是:当前页码减 2 - 当前页码加 2--%>
<%--记录 begin 和 end--%>
<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="${requestScope.page.pageNo == i}">
【${i}】
</c:if>
<c:if test="${requestScope.page.pageNo != i}">
<%--点击页码跳转到对应的页码上--%>
<a href="manager/bookServlet?action=page&pageNo=${i}">${i}</a>
</c:if>
</c:forEach>
5.6、分页对删除、修改、增加的影响
由于我们做了分页查询之后,修改、删除、增加完在跳到 list 就不行了,这是因为我们在页面上获取的数据是从 page 属性里得到的,我们需要跳转到 page。
增加:
当我们增加完之后,我们希望跳转到最后一页,能够看到我们增加的数据。
只需要将 总页码 当做参数传给 BookServlet 中。
修改:
删除:
5.7、前台的分页管理
我们可以在/pages/client 目录下 新建一个 index.jsp,之前的 index.jsp 页面 只负责转发到Servlet 中。Servlet 转发到 新的 index.jsp 页面中、
/pages/client/index.jsp : 只留一个 <div class="b_list"> , 并且循环遍历。
<c:forEach items="${requestScope.page.item}" var="book">
<div class="b_list">
<div class="img_div">
<img class="book_img" alt="" src="static/img/default.jpg" />
</div>
<div class="book_info">
<div class="book_name">
<span class="sp1">书名:</span>
<span class="sp2">${book.name}</span>
</div>
<div class="book_author">
<span class="sp1">作者:</span>
<span class="sp2">${book.author}</span>
</div>
<div class="book_price">
<span class="sp1">价格:</span>
<span class="sp2">${book.price}</span>
</div>
<div class="book_sales">
<span class="sp1">销量:</span>
<span class="sp2">${book.sales}</span>
</div>
<div class="book_amount">
<span class="sp1">库存:</span>
<span class="sp2">${book.stock}</span>
</div>
<div class="book_add">
<button>加入购物车</button>
</div>
</div>
</div>
</c:forEach>
将之前做好的 分页条 拷贝到 index.jsp 页面中,并把 /manager/bookServlet 修改为:/client/bookServlet
5.8、分页条的抽取
在前台和后台进行分页处理的时候,我们发现只有这个 请求地址不同。其他的都一样,那么我们就可以将 这个请求地址 在 Servlet 中设置好。然后将分页条的代码封装起来。
首先在 Page实体类中 设置一个 url 属性,用来设置 请求地址。
分别在 BookServlet 和 ClientServlet 中设置 前台和后台的 请求地址:
使用 ${requestScope.page.url } 代替请求地址。并且将 前台和后台的分页条代码封装到 /common/page_ngv.jsp 中:
在页面中 分别使用静态包含引入:
5.9、价格区间搜索
修改 index.jsp 页面 价格搜索的表单。
点击查询之后,会将 min,max 请求参数发送给 ClientServlet
x向ClientServlet 中增加 getPagePrice 方法,处理用户价格搜索并且分页:
//价格搜索分页
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<Book> page = bookService.pageByPrice(pageNo, pageSize, min, max);
//将 min.max 追加到分页条的 请求地址 中.防止价格搜索分页时,点击页码会出现错乱的情况.
String minStr = request.getParameter("min");
String maxStr = request.getParameter("max");
String url = "client/bookServlet?action=getPageByPrice&min=" + minStr + "&max=" + maxStr;
//设置前台分页的请求地址
page.setUrl(url);
//将 page 对象保存到 request 域中
request.setAttribute("page", page);
//转发 到book_manager.jsp页面
request.getRequestDispatcher("/pages/client/index.jsp").forward(request, response);
}
BookService 接口:
/**
* 根据价格区间 获取数据并进行分页
* @param pageNo
* @param pageSize
* @param min
* @param max
* @return 返回 page 对象
*/
Page<Book> pageByPrice(int pageNo, int pageSize, int min, int max);
BookService 实现类:
@Override
public Page<Book> pageByPrice(int pageNo, int pageSize, int min, int max) {
Page<Book> page = new Page<>();
//设置每页显示数量
page.setPageSize(pageSize);
//根据价格区间,获取总记录数.
int pageTotalCount = bookDao.queryForPageTotalCountByPrice(min,max);
//设置总记录数
page.setPageTotalCount(pageTotalCount);
//获取 总页码 pageTotal
int pageTotal = pageTotalCount / pageSize ;
//如果 还有余数,总页码要进行加一
if (pageTotalCount % pageSize > 0){
pageTotal++;
}
//设置总页码
page.setPageTotal(pageTotal);
//页码边界检查
if (pageNo < 1){
pageNo = 1 ;
}
if (pageNo > pageTotal){
pageNo = pageTotal ;
}
//设置当前页码
page.setPageNo(pageNo);
//获取每页显示数据
int begin = (page.getPageNo() -1) * pageSize ;
List<Book> items = bookDao.queryForItemsByPrice(begin,pageSize,min,max);
//设置每页显示的数据
page.setItem(items);
return page;
}
BookDao:
/**
* 根据价格区间,获取总记录数
* @param min
* @param max
* @return
*/
int queryForPageTotalCountByPrice(int min, int max);
/**
* 根据价格区间搜索,并进行分页处理
* @param begin
* @param pageSize
* @param min
* @param max
* @return
*/
List<Book> queryForItemsByPrice(int begin, int pageSize, int min, int max);
BookDao实现类:
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<Book> queryForItemsByPrice(int begin, int pageSize, int min, int max) {
String sql = "select `id` , `name` , `author` , `price` , `sales` , `stock` , `img_path` " +
"from t_book where price between ? and ? order by price limit ?,?";
return BaseDao.queryForList(Book.class,sql,min,max,begin,pageSize);
}
第六阶段:登录用户信息显示
6.1、登录后显示 欢迎信息
在登录成功后,显示 : 欢迎 用户名。
只需要在登陆成功的时候,将用户名保存到 session 域中。在 jsp 页面中取出来即可。
取出session中的数据:
在登录成功 和 不登录的时候,主页面的不同:
登陆成功后:
未登录时:
在 index.jsp 页面处,判断 session域中是否有 username的值:
6.2、注销登录
1、清除session域
2、重定向到 主页 或者 登录页面
/*处理注销业务*/ protected void loginOut(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 删除 session request.getSession().invalidate(); // 重定向到 主页 response.sendRedirect(request.getContextPath()); }
修改注销请求地址:
<a href="user?action=loginOut">注销</a>
6.3、表单重复提交问题
这三种问题都会重复提交表单情况
解决办法:使用验证码
使用谷歌验证码:
1、增加 jar 包
2、在 web.xml文件中配置 KaptchaServlet 类 。这个类是jar包中人家写好的类。
<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>
3、在表单中使用 img 标签,使用验证码。
4、在 loginServlet 中 获取session中的验证码并用变量保存起来,删除 session 中的验证码,并验证 表单中的验证码 与 session 中是否一致。
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("不要重复提交表单");
}
}
session 域中 的 KEY :com.google.code.kaptcha.Constants类中的KAPTCHA_SESSION_KEY 变量
session 域中取出验证码后,一定要删除。千万别忘记!!!!!!
6.4、将验证码增加到书城项目中
1、引进 jar 包
2、配置 web.xml 文件
3、使用 img 标签并使用验证码
<%--使用 谷歌验证码--%>
<img alt="" src="kaptcha.jpg" style="float: right; margin-right: 45px ;width: 120px;height: 40px"
id="code_img" >
4、Servlet 中获取 验证码并进行判断。
动态获取验证码,。每点击一次验证码图片就更换验证码:
给 验证码图片增加一个点击事件,每次单击都发送 验证码 链接:
由于浏览器中都有缓存机制,访问相同的路径会增加到缓存当中。缓存一般由:请求地址+参数 决定,所以我们可以增加一个 可变的参数。每次点击图片都更改参数。
/*给图片一个点击事件*/ $("#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<CartItems> items = new ArrayList<CartItems>() ;
public Cart() {
}
public Cart(Integer totalCount, BigDecimal totalPrice, List<CartItems> items) {
this.totalCount = totalCount;
this.totalPrice = totalPrice;
this.items = items;
}
@Override
public String toString() {
return "Cart{" +
"totalCount=" + totalCount +
", totalPrice=" + totalPrice +
", items=" + items +
'}';
}
public Integer getTotalCount() {
return totalCount;
}
public void setTotalCount(Integer totalCount) {
this.totalCount = totalCount;
}
public BigDecimal getTotalPrice() {
return totalPrice;
}
public void setTotalPrice(BigDecimal totalPrice) {
this.totalPrice = totalPrice;
}
public List<CartItems> getItems() {
return items;
}
public void setItems(List<CartItems> items) {
this.items = items;
}
}
7.2、 购物车功能的实现
由于购物车 是使用 session 实现的,不需要与数据库交互,不用写Dao和Service 层。
/*购物车模型*/
public class Cart {
//总商品数量
//由于不需要人为更改,不需要使用全局变量
// private Integer totalCount ;
//总商品价格
// private BigDecimal totalPrice ;
// 购物车商品
//使用 map 集合。 key:商品id, value:商品信息。
private Map<Integer,CartItems> items = new LinkedHashMap<>() ;
// 往购物车增加商品
public void addItem(CartItems cartItem){
// 直接增加是不行的,因为如果相同ID时,想增加的是商品的数量以及价格。而不是又增加一个商品
// items.put();
// 所以我们需要先判断 购物车里面是否有和 cartItem 相同的商品。ID 是唯一的。
CartItems item = this.items.get(cartItem.getId());
if (item == null){
// 说明没有相同的商品,直接增加到购物车
items.put(cartItem.getId(),cartItem);
}else{
//说明有相同的商品,让商品的数量+1,并且增加商品的总价格
item.setCount(item.getCount()+1);
item.setTotalPrice(item.getTotalPrice().add(cartItem.getTotalPrice()));
}
}
// 删除商品
public void deleteItem(int id){
items.remove(id);
}
// 清空购物车
public void clear(){
items.clear();
}
/**
* 根据id修改商品的数量
* @param id 修改商品的id
* @param count 修改的数量
*/
public void updateCount(int id,int count){
// 首先判断购物车里面是否有相同id的商品
CartItems item = this.items.get(id);
if (item != null){
// 有相同 id 的商品,直接修改 count。
item.setCount(count);
//并且修改商品的总价格.商品数量 * 商品价格
//multiply : BigDecimal 中的乘法。
item.setTotalPrice(item.getPrice().multiply(new BigDecimal(count)));
}
}
// 动态获取商品总数量
public Integer getTotalCount() {
Integer totalCount = 0;
// 遍历 购物车 中商品信息,累加每种商品的数量
//values:获取map集合中所有的value
for (CartItems item : items.values()) {
//累加
totalCount += item.getCount();
}
return totalCount;
}
//由于商品数量不能人为修改,他是根据每个商品的数量动态获取的,所以不需要这个set方法
// public void setTotalCount(Integer totalCount) {
// this.totalCount = totalCount;
// }
public BigDecimal getTotalPrice() {
//初始化
BigDecimal totalPrice = new BigDecimal(0) ;
for (CartItems item : items.values()) {
//遍历 购物车中的商品,获取每种商品的总价格并进行累加
// add:BigDecimal 类型中的加法。
totalPrice = totalPrice.add(item.getTotalPrice());
}
return totalPrice;
}
//由于商品总价格不能人为修改,他是根据每个商品的数量及价格动态获取的,所以不需要这个set方法
// public void setTotalPrice(Integer totalPrice) {
// this.totalPrice = totalPrice;
// }
public Map<Integer,CartItems> getItems() {
return items;
}
public void setItems(Map<Integer,CartItems> items) {
this.items = items;
}
public Cart() {
}
public Cart( Map<Integer,CartItems> items) {
// this.totalCount = totalCount;
// this.totalPrice = totalPrice;
this.items = items;
}
@Override
public String toString() {
return "Cart{" +
// 遍历时,动态获取购物车中所有商品的总数量
"totalCount=" + getTotalCount() +
//便利时,动态获取购物车中所有商品的价格
", totalPrice=" + getTotalPrice() +
", items=" + items +
'}';
}
}
7.3、增加购物车功能的实现
为 加入购物车 按钮 绑上点击事件,只要单击它就发送请求信息到 CartServlet 中。
注意:这里不要加 ID 绑上点击事件,因为这个按钮再forEach循环里,ID是唯一的,class可以为多个按钮榜单击击事件
<script type="text/javascript">
$(function () {
//为加入购物车按钮绑上点击事件。
$(".addItem").click(function () {
//获取到 图书的id
//this 表示正在响应的DOM对象。就是 button 标签对象
var id = $(this).attr("bookId");
var bookname = $(this).attr("bookname");
//跳转到 cartServlet
window.location = "http://localhost:8080/book/cartServlet?action=addItem&id="+id;
confirm("已经将【"+bookname+"】这本书增加到了购物车")
});
});
</script>
在 CartServlet 中创建 addItem 方法:增加图书到购物车。
1、获取图书的ID
2、根据ID,bookService.queryById 查询图书的信息。
3、将 book 转换为 CartItem 。
4、创建购物车,。并且自始至终只用这一个购物车。
5、将 图书 增加到购物车
6、重定向。
//实现购物车模块的一些功能
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、展示购物车
购物车中的信息都保存在了 session域中,所以直接从 session 域中取出数据就行了。
cart.jsp 页面:
<div id="main">
<table>
<tr>
<td>商品名称</td>
<td>数量</td>
<td>单价</td>
<td>金额</td>
<td>操作</td>
</tr>
<%--如果购物车为空,显示下面信息--%>
<c:if test="${ empty sessionScope.cart.items}">
<tr>
<td colspan="5"><a href="index.jsp">购物车竟然是空的~</a> </td>
</tr>
</c:if>
<%--从 session 域中取出数据
cart.items:是个map集合,value才是商品信息
--%>
<c:forEach items="${sessionScope.cart.items}" var="cart">
<tr>
<td>${cart.value.name}</td>
<td>${cart.value.count}</td>
<td>${cart.value.price}</td>
<td>${cart.value.totalPrice}</td>
<td><a href="#">删除</a></td>
</tr>
</c:forEach>
</table>
<%--如果购物车不为空,显示下面信息--%>
<c:if test="${not empty sessionScope.cart.items}">
<div class="cart_info">
<span class="cart_span">购物车中共有<span class="b_count">${sessionScope.cart.totalCount}</span>件商品</span>
<span class="cart_span">总金额<span class="b_price">${sessionScope.cart.totalPrice}</span>元</span>
<span class="cart_span"><a href="#">清空购物车</a></span>
<span class="cart_span"><a href="pages/cart/checkout.jsp">去结账</a></span>
</div>
</c:if>
</div>
7.5、删除商品
<%--发送删除请求--%>
<td><a href="cartServlet?action=deleteItem&id=${cart.value.id}" class="deleteItem">删除</a></td>
<script type="text/javascript">
$(function () {
$(".deleteItem").click(function () {
/*删除提示信息*/
return confirm("确定删除【"+$(this).parent().parent().find("td:first").text()+"】这本书码?");
});
});
</script>
CartServlet 中增加删除商品的方法:
//删除商品操作
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、清空购物车
点击 清空购物车 时要进行提示:确定要清除购物车吗?
cart.jsp 页面:
<span class="cart_span"><a href="cartServlet?action=clear" class="clear">清空购物车</a></span>
$(".clear").click(function () {
return confirm("清空购物车吗?");
});
CartServlet :
//清空购物车
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、修改购物车商品数量
为 购物车中的 商品数量 加一个文本框:
使用 change 事件,当文本框中 value 发生变化时,发送请求给 CartServlet
<td>
<%--bookId:自定义的标签。--%>
<input type="text" class="updateCount"
bookId="${cart.value.id}" value="${cart.value.count}" style="width: 60px">
</td>
<script>
// 为 数量框 绑定 change 事件,只要文本框的value发生变化,就会执行这个事件
$(".updateCount").change(function () {
//获取修改商品的名字
var name = $(this).parent().parent().find("td:first").text();
//获取修改的数量
var count = this.value;
//获取修改商品的id
var id = $(this).attr("bookId");
if (confirm("确定将【"+name+"】这本书的商品的数量修改为:【"+count+"】吗?")) {
//确认修改后,将 id 和 count 的参数发送给 Servlet
window.location = "http://localhost:8080/book/cartServlet?action=updateCount&id="+id+"&count="+count;
}else{
// 点击取消后,修改为原来的值。
this.value = this.defaultValue ;
}
});
</script>
CartServlet:
//修改商品数量
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域中保存最后一个增加商品的信息。
index.jsp 页面:
<div style="text-align: center">
<%--如果购物车商品信息为空,显示下列信息--%>
<c:if test="${empty sessionScope.cart.items}">
<div>
<span style="color: red">购物车竟然是空的!</span>
</div>
</div>
</c:if>
<%--如果购物车商品信息不为空,显示下列信息--%>
<c:if test="${not empty sessionScope.cart.items}">
<%--获取商品数量--%>
<span>您的购物车中有${sessionScope.cart.totalCount}件商品</span>
<div>
<%--从 sessio 域中取出最后一次增加的商品名称--%>
您刚刚将<span style="color: red" id="tishi">${sessionScope.lastItem}</span>加入到了购物车中
</div>
</div>
</c:if>
在 CartServlet 的 addItem 方法中增加:
// 将最后一次放到购物车中的商品 增加到 session 域中
request.getSession().setAttribute("lastItem",cartItems.getName());
第八阶段:订单模块
8.1、订单模型创建分析
订单:就是页面中的内容。包日期,金额,状态等信息。用户编号是用于区分该订单是属于哪个用户的。
订单项:当我们点击 查看详情 时,会出现类似于购物车中的内容,有商品名称,数量...等等一些属性,orderId:用于区分查看的哪个订单。
订单中有些功能管理员和用户是不一样的。管理员:查看所有订单,负责发货,查看订单详情。用户:查看订单详情,查看我的订单,签收订单。
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 层:
OrderDao 和 OrderItemDao:一个实体类对应一个Dao
public interface OrderDao {
//保存订单
int saveOrder(Order order);
}
public interface OrderItemDao {
//保存订单项
int saveOrderItem(OrderItem orderItem);
}
OrderDaoImpl 和 OrderItemImpl :
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 层:
OrderService :负责生成订单。一个模块对应一个 service ,模块中有几个功能,,模块就对应几个实现的方法。
public interface OrderService {
/**
* 生成订单
* @param cart 购物车
* @param userId 用户id
* @return 返回订单号
*/
String createOrder(Cart cart,Integer userId);
}
Service 中不仅要实现生成订单的功能,还要实现对商品的销量和库存动态的修改。
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<Integer, CartItems> items = cart.getItems();
//遍历商品信息
for (CartItems item : items.values()) {
//将 CartItems 转换为 OrderItem
OrderItem orderItem = new OrderItem(null, item.getName(), item.getCount(), item.getPrice(), item.getTotalPrice(), orderId);
//将订单项保存到数据库中
orderItemDao.saveOrderItem(orderItem);
//通过商品id查找商品信息
Book book = bookDao.queryForByID(item.getId());
//修改库存和销量
// 现有销量 = 原有销量 + 订单中商品的数量
book.setSales(book.getSales() + item.getCount());
// 现有库存 = 原有库存 - 订单中商品的数量
book.setStock(book.getStock() - item.getCount());
//在数据库进行修改
bookDao.updateBook(book);
}
//生成订单后,要清空购物车。
cart.clear();
return orderId;
}
}
Web 层:
OrderServlet :
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");
}
}
修改页面跳转的路径:点击 去结账,生成订单,。
<span class="cart_span"><a href="orderServlet?action=createOrder">去结账</a></span>
在 checkout.jsp 页面显示订单号:
<h1>你的订单已结算,订单号为${sessionScope.orderId}</h1>
第九阶段:使用 Filter 和 ThreadLocal 完善系统
9.1、使用Filter拦截器:
使用拦截器对 manager目录下的所有页面进行拦截,只有登录后才能访问。
拦截操作:
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() {
}
}
配置文件:
<filter>
<filter-name>ManagerFilter</filter-name>
<filter-class>yangzhaoguang.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>
9.2、使用 ThreadLocal:
目前程序中的bug:
假设我们在生成订单的时候,正好在生成完订单,即将保存订单项的时候。出现了一个异常,那么在数据库中有订单的信息,但是并没有订单项的信息。 所以我们希望使用JDBC手动提交的方式操作数据库。
分析如下:
我们的编写程序时,并没有创建线程,所以整个项目都是在一个线程中完成的。
下面进行修改:
JdbcUtils :
确保我们整个项目中的 Jdbc 操作都使用一个 Connection 连接对象。
public class JdbcUtils {
private static DruidDataSource source;
// 使用 ThreadLocal
private static ThreadLocal<Connection> conns = new ThreadLocal<>();
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;
//从 ThreadLocal 中取出 conn
conn = conns.get();
if (conn == null){
try {
//从数据库连接池中获取连接
conn = source.getConnection();
//将 Connection 保存到 ThreadLocal 中
conns.set(conn);
// 设置手动提交事务
conn.setAutoCommit(false);
} catch (SQLException e) {
e.printStackTrace();
}
}
return conn ;
// try {
// conn = source.getConnection();
// } catch (SQLException e) {
// e.printStackTrace();
// }
// return conn;
}
// 提交事务并且释放资源
public static void commitAndClose(){
Connection conn = conns.get() ;
if (conn != null){
try {
conn.commit(); //提交事务
} catch (SQLException e) {
e.printStackTrace();
}finally {
try {
conn.close(); //释放资源
} catch (SQLException e) {
e.printStackTrace();
}
}
}
//最终使用完,要清除 线程本地变量池
conns.remove();
}
//回滚事务并且释放资源
public static void rollbackAndClose(){
Connection conn = conns.get() ;
if (conn != null){
try {
conn.rollback(); //回滚事务
} catch (SQLException e) {
e.printStackTrace();
}finally {
try {
conn.close(); //释放资源
} catch (SQLException e) {
e.printStackTrace();
}
}
}
//最终使用完,要清除 线程本地变量池
conns.remove();
}
/*释放资源*/
// public static void closeConnection(Connection conn) {
// if (conn != null) {
// try {
// conn.close();
// } catch (SQLException e) {
// e.printStackTrace();
// }
// }
// }
}
BaseDao:
BaseDao 中 一定要记得抛出异常,将异常抛给 Servlet 中处理。
/*使用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 <T> T queryForOne(Class<T> tClass, String sql, Object... args) {
BeanHandler<T> beanHandler = new BeanHandler<T>(tClass);
Connection conn = JdbcUtils.getConnection();
T t = null;
try {
t = runner.query(conn, sql, beanHandler, args);
} catch (SQLException e) {
e.printStackTrace();
//并且在这里要往外面抛出异常
throw new RuntimeException(e);
// }finally {
// JdbcUtils.closeConnection(conn);
}
return t;
}
/*返回多条记录*/
public static <T> List<T> queryForList(Class<T> tClass, String sql, Object... args) {
BeanListHandler<T> beanList = new BeanListHandler<>(tClass);
Connection conn = JdbcUtils.getConnection();
List<T> list = new ArrayList<>();
try {
list = runner.query(conn, sql, beanList, args);
} catch (SQLException e) {
e.printStackTrace();
//并且在这里要往外面抛出异常
throw new RuntimeException(e);
// }finally {
// JdbcUtils.closeConnection(conn);
}
return list;
}
/*查询特殊值*/
public static Object queryForSingle(String sql, Object... args) {
ScalarHandler<Object> handler = new ScalarHandler<>();
Connection conn = JdbcUtils.getConnection();
Object data = null;
try {
data = runner.query(conn, sql, handler,args);
} catch (SQLException e) {
e.printStackTrace();
//并且在这里要往外面抛出异常
throw new RuntimeException(e);
// }finally {
// JdbcUtils.closeConnection(conn);
}
return data;
}
}
OrderServlet:
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
我们不仅仅是给订单的业务加上try...catch ,我们希望给所有的业务都加上 try'...catch,那么有没有一种方法可以一次性的给所有的业务都加上Servlet ?
可以的,使用 Filter 过滤器。我们知道 Filter 的作用就是:调用下一个Filter 或者 资源,【Servlet 也是资源】,那么如果在 Servlet 中出现异常的话,他会继续抛给它的上级 Filter,所以我们就可以在 Filter 中统一捕捉异常。
当我们在 Filter 中加上try..catch后, 当 service 层出现异常后,并没有回滚事务,而是提交事务,这就出现问题了。这是为什么呢?
原因是:在 Servlet 中继承了 BaseServlet,当出现异常后,他会抛给BaseServlet,但是呢,BaseServlet 却是可以捕捉异常,那么在 Filter 中就会捕捉不到异常了。当然会出问题了,所以我们在 BaseServlet 中也要把异常抛给 Filter。
现在出现异常就会回滚事务,不会提交了。
Filter :
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() {
}
}
web.xml:
<filter>
<filter-name>TransactionFilter</filter-name>
<filter-class>yangzhaoguang.filter.TransactionFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>TransactionFilter</filter-name>
<!--/* 表示拦截本工程下所有的资源-->
<url-pattern>/*</url-pattern>
</filter-mapping>
9.4、使用 Tomcat 统一管理异常,展示友好的错误页面
出现异常时,我们虽然在后台处理了,不会影响数据。但是在页面中,并没有展示出来,还是一片空白,用户看见了根本不知道是啥,所以我们希望能够展示错误页面提示用户。
准备好俩个错误页面,一个用于500提示,一个用于 404提示。
错误页面可以再web.xml 文件中配置:
<error-page> <!--错误类型--> <error-code>500</error-code> <!--错误页面路径--> <location>/pages/error/error500.jsp</location> </error-page> <error-page> <!--错误类型--> <error-code>404</error-code> <!--错误页面路径--> <location>/pages/error/error404.jsp</location> </error-page>
最后一定要在 TransactionFilter 中把异常抛出去,不然服务器接收不到异常,就不会跳转错误页面。
第十阶段:使用 Ajax 验证用户名是否可用
UserServlet :
// 使用 Ajax 验证用户名是否可用
protected void ajaxExistUsername(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//获取参数
String username = request.getParameter("username");
boolean existsUsername = userService.existsUsername(username);
// 将结果封装成 Map 集合
Map<String, Object> jsonMap = new HashMap<>();
jsonMap.put("existsUsername",existsUsername);
Gson gson = new Gson();
// 将 map 转换成 json 字符串
String json = gson.toJson(jsonMap);
//响应到浏览器中
response.getWriter().write(json);
}
regist.jsp 页面:
// 使用 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 修改增加购物车:
CartServlet :
//使用 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<String,Object> jsonMap = new HashMap<>();
//一般是页面需要什么,就将数据通过数据流返回给页面
jsonMap.put("totalCount",cart.getTotalCount());
jsonMap.put("lastItem",cartItems.getName());
Gson gson = new Gson();
String mapToJsonString = gson.toJson(jsonMap);
response.getWriter().write(mapToJsonString);
// 将最后一次放到购物车中的商品 增加到 session 域中
request.getSession().setAttribute("lastItem",cartItems.getName());
}
index.jsp 页面:
<script type="text/javascript">
$(function () {
//为加入购物车按钮绑上点击事件。
$(".addItem").click(function () {
//获取到 图书的id
//this 表示正在响应的DOM对象。就是 button 标签对象
var id = $(this).attr("bookId");
//跳转到 cartServlet
// window.location = "cartServlet?action=addItem&id=" + id;
// 使用 Ajax 实现增加购物车
$.getJSON("cartServlet","action=ajaxAddItem&id="+id,function (data) {
$(".lastName").text(data.lastItem);
$("#cartTotalCount").text("您的购物车中有"+ data.totalCount +"件商品");
});
});
});
</script>
更多推荐
所有评论(0)