JavaWeb书城项目
尚硅谷书城项目:自己整理的笔记以及全部实现过程,原理。
链接: 点击获取资源
提取码: 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目录下。
包名设置:
![](/assets/blank.gif)
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 对象中。
![](/assets/blank.gif)
![](/assets/blank.gif)
问题:每个项目中每块业务中的请求参数是不一样的,有的项目可能有十几,几十个请求参数,如果一个个都 request.getParameter() 是非常麻烦的。解决方法:BeanUtils工具类中的 BeanUtils.copyProperties(注入的对象,Map集合) 方法可以将一个 map 集合 注入 到一个对象中。正好 请求参数 也是一个 map 集合,我们可以利用这一特征将 请求参数注入到 JavaBean 对象中
BeanUtils.copyProperties() 方法 测试:
![](/assets/blank.gif)
相同的道理:每块业务可能都需要获取请求参数,所以将 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>
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 {//商品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书城项目相关推荐
- JavaWeb书城项目(一)
书城项目(一) 1.表单验证的实现 代码 2.用户注册和登陆 JavaEE 项目的三层架构 2.1.数据库层 2.1.1.创建数据库 2.1.2.定义 JavaBean 类 2.1.3.编写工具类 J ...
- JavaWeb网上书城项目总结(初步1.0)
JavaWeb网上书城项目总结 目录 项目背景与目标 成员组成 模块划分 数据库设计 功能分析+源码 经验总结 (逐步放上博客,先总结) 成员组成 组长:林俊豪(本人) 组员:温尧皓.麦乙迪.邓梓鹏. ...
- JAVAWEB之小说书城项目
一,项目目的 1.熟悉网站开发的基本流程. 2.将以学习的知识进行复习总结. 3,明确自身知识薄弱区. 二,项目内容 使用所学习对jsp,HTML,Mysql,css等知识制作一个小型的网页,初步实现 ...
- JavaWeb 尚硅谷书城项目
书城项目第一阶段:表单验证 需求: 验证用户名:必须由字母,数字下划线组成,并且长度为 5 到 12 位 验证密码:必须由字母,数字下划线组成,并且长度为 5 到 12 ...
- java web网上书城_javaweb网上书城项目
[实例简介] javaweb网上书城项目,采用ssh框架,mysql数据库. [实例截图] [核心代码] bookstore └── ssh_book ├── WebContent │ ├── M ...
- 【Java - 项目开发】网上书城项目
网上书城项目 创作日期:2021-12-23 第一阶段 登录注册的验证(表单验证) 技术方法: 使用 jQuery 技术对登录中的用户名.密码进行非空验证 使用 jQuery 技术和正则表达式对注册中 ...
- Java项目:网上电子书城项目(java+SSM+JSP+maven+Mysql)
源码获取:博客首页 "资源" 里下载! 项目描述: spring mvc +jsp实现的简单书城项目,可以在支付宝沙箱内实现支付 运行环境: jdk8+tomcat9+mysql+ ...
- 网上书城java负责_网上书城项目总结(servlet_jsp+javaBean)
网上书城项目总结 1 项目大纲设计: 需求分析 系统设计 详细设计 权限设计 2 技术选型: Servlet+jsp+javaBean Listener+Filter+jstl+fileupload+ ...
- JavaWeb完整项目要用到的专业技能
完成JavaWeb项目用到哪些专业技能?在经典的JavaWeb的开发模式中,我们使用Jsp技术来作为展现层的实现,其实也就是所谓的前端.Web开发中经典的MVC模式,Model-View-Contro ...
最新文章
- 我从阿里面试回来,想和Java程序猿谈一谈
- linux vnc的小黑点和鼠标不同步_vnc连接windows,推荐三款非常好用的vnc连接windows软件...
- plsql查询中补入空行--做报表分页挺有用
- 2018年,牛客网小白月赛5
- python recv_[Python]关于socket.recv()的非阻塞用法
- readmemh函数引用的txt格式_verilog的系统函数$readmemh的使用
- Oracle01877,Cognos错误:RQP-DEF-0177 执行操作“sqlOpenResult”(状态为“-28”)时出错...
- 使用 json.tool 格式化 JSON字符串
- 添加proc文件,控制sctp的debug输出
- 对抗攻击与防御 (2):对抗样本的反制策略
- 雷达图分析法(转载)
- 【观察】百度搜索开放平台
- 大漠穷秋:全面解读Angular 4.0核心特性
- 基于Python的个人足迹地图绘制设计
- MatLab实现的ftt大数乘法
- Windows窗口消息介绍
- 第一课:QT Quick项目架构说明
- 一些写英文简历的词汇
- 「 Luogu P2657 」 windy数
- 用51单片机点亮流水灯