尚硅谷书城项目:自己整理的笔记以及全部实现过程,原理。

链接: 点击获取资源

提取码: 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 位

验证确认密码:和密码相同

邮箱验证: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目录下。

包名设置:

web层                项目名.web/servlet/controller
service层           项目名.service                                         Service接口包:实现业务功能

                          项目名.service.impl                                  Service接口实现类:通过 dao 中的方法,实现业务功能。
dao持久层          项目名.dao                                              Dao接口包:针对于某一张表的操作

                           项目名.dao.impl                                      Dao接口实现类:实现接口的操作,一般需要继承BaseDao,通过调用里面的方法,实现接口中的方法,
实体bean对象     项目名.pojo/entity/domain/bean      JavaBean类
测试包               项目名.test/junit                            用于测试【需要导入harmcrest和Junit的jar包】
工具类              项目名.utils

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() {}@Overridepublic 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 {@Overridepublic 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());}@Overridepublic 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);}@Overridepublic 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 注册的信息*/@Overridepublic void saveInfo(User user) {userDao.saveInfo(user);}/**** @param userName 用户名* @return true:用户名重复 false:用户名可用*/@Overridepublic boolean queryByUserName(String userName) {User user = userDao.queryByUserName(userName);if (user != null){//不等于 null,说明查到了用户名,则用户名重复不可用。return true ;}//相反,没有查找说明用户名可用return false;}/**** @param userName 用户名* @param password 密码* @return 返回 User 信息*/@Overridepublic 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();@Overrideprotected 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();@Overrideprotected 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 页面访问。并且回显usernamerequest.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 页面访问。并且回显usernamerequest.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);}}@Overrideprotected 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);}}
}

解决第二个问题:

    @Overrideprotected 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 {@Overrideprotected 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 对象中。

BeanUtils 工具类,经常用于把 Map 中的值注入到 JavaBean 中,或者是对象属性值的拷贝操
作。
提前导入 俩个 jar 包:
问题:每个项目中每块业务中的请求参数是不一样的,有的项目可能有十几,几十个请求参数,如果一个个都 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";@Overridepublic 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 {@Overridepublic 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());}@Overridepublic void delBook(int id) {String sql = "delete from t_book where id=?";BaseDao.update(sql, id);}@Overridepublic 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());}@Overridepublic 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);}@Overridepublic 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();@Overridepublic int addBook(Book book) {return bookDao.addBook(book);}@Overridepublic void update(Book book) {bookDao.updateBook(book);}@Overridepublic void delBook(int id) {bookDao.delBook(id);}@Overridepublic Book queryForByID(int id) {return bookDao.queryForByID(id);}@Overridepublic 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;}@Overridepublic 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 返回总记录条数*/@Overridepublic Integer queryForPageTotalCount() {String sql = "select count(*) from t_book";Number count = (Number) BaseDao.queryForSingle(sql);return count.intValue();}/*** * @param begin 开始的索引* @param pageSize 显示条数* @return 返回每页的数据*/@Overridepublic 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 获取总记录数*/@Overridepublic int queryForPageTotalCount() {int count = bookDao.queryForPageTotalCount();return count;}/*** 在 Service 层为 page对象中的属性赋值* @param pageNo* @param pageSize* @return page 对象*/@Overridepublic 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);//获取 总页码 pageTotalint 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 实现类:

 @Overridepublic 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);//获取 总页码 pageTotalint 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根据价格高低进行排序。*/@Overridepublic 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 {// 删除 sessionrequest.getSession().invalidate();// 重定向到 主页response.sendRedirect(request.getContextPath());}

修改注销请求地址:

<a href="user?action=loginOut">注销</a>&nbsp;&nbsp;

6.3、表单重复提交问题

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

  这三种问题都会重复提交表单情况

 解决办法:使用验证码

 使用谷歌验证码:

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 {//商品idprivate 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;}@Overridepublic 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;}@Overridepublic 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集合中所有的valuefor (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;}@Overridepublic 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");//跳转到 cartServletwindow.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、获取图书的idint id = WebUtils.parseInt(request.getParameter("id"),0);//2、根据 id 查询图书的信息Book book = bookService.queryForByID(id);//3、将图书信息转换为 CartItemsCartItems 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;//获取修改商品的idvar id = $(this).attr("bookId");if (confirm("确定将【"+name+"】这本书的商品的数量修改为:【"+count+"】吗?")) {//确认修改后,将 id 和 count 的参数发送给 Servletwindow.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;}@Overridepublic 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() {}@Overridepublic 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*/@Overridepublic 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*/@Overridepublic 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 转换为 OrderItemOrderItem 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 域中取出购物车和useridCart 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 {@Overridepublic void init(FilterConfig filterConfig) throws ServletException {}@Overridepublic 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);}}@Overridepublic 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;// 使用 ThreadLocalprivate 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 中取出 connconn = 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 域中取出购物车和useridCart 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 {@Overridepublic void init(FilterConfig filterConfig) throws ServletException {}@Overridepublic 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();}}@Overridepublic 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、获取图书的idint id = WebUtils.parseInt(request.getParameter("id"), 0);//2、根据 id 查询图书的信息Book book = bookService.queryForByID(id);//3、将图书信息转换为 CartItemsCartItems 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>

JavaWeb书城项目相关推荐

  1. JavaWeb书城项目(一)

    书城项目(一) 1.表单验证的实现 代码 2.用户注册和登陆 JavaEE 项目的三层架构 2.1.数据库层 2.1.1.创建数据库 2.1.2.定义 JavaBean 类 2.1.3.编写工具类 J ...

  2. JavaWeb网上书城项目总结(初步1.0)

    JavaWeb网上书城项目总结 目录 项目背景与目标 成员组成 模块划分 数据库设计 功能分析+源码 经验总结 (逐步放上博客,先总结) 成员组成 组长:林俊豪(本人) 组员:温尧皓.麦乙迪.邓梓鹏. ...

  3. JAVAWEB之小说书城项目

    一,项目目的 1.熟悉网站开发的基本流程. 2.将以学习的知识进行复习总结. 3,明确自身知识薄弱区. 二,项目内容 使用所学习对jsp,HTML,Mysql,css等知识制作一个小型的网页,初步实现 ...

  4. JavaWeb 尚硅谷书城项目

    书城项目第一阶段:表单验证 需求:         验证用户名:必须由字母,数字下划线组成,并且长度为 5 到 12 位         验证密码:必须由字母,数字下划线组成,并且长度为 5 到 12 ...

  5. java web网上书城_javaweb网上书城项目

    [实例简介] javaweb网上书城项目,采用ssh框架,mysql数据库. [实例截图] [核心代码] bookstore └── ssh_book ├── WebContent │   ├── M ...

  6. 【Java - 项目开发】网上书城项目

    网上书城项目 创作日期:2021-12-23 第一阶段 登录注册的验证(表单验证) 技术方法: 使用 jQuery 技术对登录中的用户名.密码进行非空验证 使用 jQuery 技术和正则表达式对注册中 ...

  7. Java项目:网上电子书城项目(java+SSM+JSP+maven+Mysql)

    源码获取:博客首页 "资源" 里下载! 项目描述: spring mvc +jsp实现的简单书城项目,可以在支付宝沙箱内实现支付 运行环境: jdk8+tomcat9+mysql+ ...

  8. 网上书城java负责_网上书城项目总结(servlet_jsp+javaBean)

    网上书城项目总结 1 项目大纲设计: 需求分析 系统设计 详细设计 权限设计 2 技术选型: Servlet+jsp+javaBean Listener+Filter+jstl+fileupload+ ...

  9. JavaWeb完整项目要用到的专业技能

    完成JavaWeb项目用到哪些专业技能?在经典的JavaWeb的开发模式中,我们使用Jsp技术来作为展现层的实现,其实也就是所谓的前端.Web开发中经典的MVC模式,Model-View-Contro ...

最新文章

  1. 我从阿里面试回来,想和Java程序猿谈一谈
  2. linux vnc的小黑点和鼠标不同步_vnc连接windows,推荐三款非常好用的vnc连接windows软件...
  3. plsql查询中补入空行--做报表分页挺有用
  4. 2018年,牛客网小白月赛5
  5. python recv_[Python]关于socket.recv()的非阻塞用法
  6. readmemh函数引用的txt格式_verilog的系统函数$readmemh的使用
  7. Oracle01877,Cognos错误:RQP-DEF-0177 执行操作“sqlOpenResult”(状态为“-28”)时出错...
  8. 使用 json.tool 格式化 JSON字符串
  9. 添加proc文件,控制sctp的debug输出
  10. 对抗攻击与防御 (2):对抗样本的反制策略
  11. 雷达图分析法(转载)
  12. 【观察】百度搜索开放平台
  13. 大漠穷秋:全面解读Angular 4.0核心特性
  14. 基于Python的个人足迹地图绘制设计
  15. MatLab实现的ftt大数乘法
  16. Windows窗口消息介绍
  17. 第一课:QT Quick项目架构说明
  18. 一些写英文简历的词汇
  19. 「 Luogu P2657 」 windy数
  20. 用51单片机点亮流水灯

热门文章

  1. window10下手动安装php7
  2. 适配Android7.0调取相机拍照并返回照片
  3. 哇哦,弹幕居然是这么弄出来的,一文学会如何用js制作一个弹幕效果
  4. 形式化验证的原理和过程
  5. Angular使用bootstrap
  6. 手机可用熵_什么是熵?
  7. Unity 游戏实例开发集合 之 打砖块 休闲小游戏快速实现
  8. kernel logo到开机卡通片之间闪现黑屏(android 5.X)
  9. 「Linux」- 安装 Opera 浏览器 @20210124
  10. 联想小新air13重装win10系统