书城项目
1.用户模块
1.表单验证
<script type="text/javascript" src="../../../script/jquery-1.7.2.js"></script>
<script type="text/javascript">
<!-- 写js代码-->
$(function (){
$("#sub_btn").click(function () {
// 进行对注册的验证
// 1.获取姓名
let username1 = $("#username").val();
// 进行正则匹配,字母数字,下划线,5-12
let rus = RegExp(/^\w{5,12}$/)
if(!rus.test(username1)){
$("span.errorMsg").text("用户名不合法")
return false;
}
// 2.获取密码
let password1 = $("#password").val();
// 进行正则匹配,字母数字,下划线,5-12
let rus2 = RegExp(/^\w{5,12}$/)
if(!rus2.test(password1)){
$("span.errorMsg").text("密码不合法")
return false;
}
//3.确认密码
let password2 = $("#repwd").val();
if(password1 != password2){
$("span.errorMsg").text("确认密码错误")
return false;
}
//4.确认邮箱
let email1 = $("#email").val();
let rus3 = RegExp(/[a-z\d]+(\.[a-z\d]+)*@([\da-z](-[\da-z])?)+(\.{1,2}[a-z]+)+$/)
if(!rus3.test(email1)){
$("span.errorMsg").text("邮箱不合法")
return false;
}
//5.验证码
let code1 = $("#code").val()
//去掉空格
code1 = $.trim(code1)
if(code1 == null || code1 == ""){
$("span.errorMsg").text("验证码不能为空")
return false;
}
//如果有的地方为空
if((password2||password1||username1)==null){
$("span.errorMsg").text("请输入完整")
return false;
}
//合法就去掉
$("span.errorMsg").text("")
})
})
</script>
2.项目结构
3.创建数据库
在pojo包下 编写javaBean类 : user类
4.JDBCUtils 工具类和测试
关于JDBCUtils类加载器的介绍:
关于InputStream in = JDBCUtil.class.getClassLoader().getResourceAsStream("jdbc.properties")的介绍
package Web.Utils;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
public class JDBCUtils {
private static DruidDataSource druidDataSource;
static {
try {
//创建配置文件
Properties properties = new Properties();
//从jdbc配置文件中创建 流
InputStream inputStream = JDBCUtils.class.getClassLoader().getResourceAsStream("jdbc.properties");
//从流中加载到配置文件
properties.load(inputStream);
druidDataSource = (DruidDataSource) DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
e.printStackTrace();
}
}
// 获取数据库连接池
public static Connection getConnect(){
// 返回null就是失败
// 右就是成功
Connection connection = null;
try {
connection=druidDataSource.getConnection();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
return connection;
}
// 关闭
public static void Close(Connection connection){
if(connection!=null){
try {
connection.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
//
}
测试
由于数据库连接池就是10个所以说只能同时创建10个 , 如果在每次创建之后及时释放,则可以无限创建
5.编写BaseDao
public Object query(String sql, Object[] params, ResultSetHandler rsh) 的说明
sql参数:sql语句 params参数:是给sql语句传递的参数,有的时候需要有的时候不需要,具体看你的sql语句是什么。例如你的sql语句是“SELECT * FROM tb_user”,那这时候就不需要params参数,而如果你的sql语句是“SELECT * FROM tb_user WHERE username=? AND userid = ?”,那这个时候就需要params参数了,
params参数是一个Object类型的数组,数组长度取决于你sql语句中的“?”个数,例如上面那个sql语句,有两个“?”,那你就要放两个参数在这个数组里,并且顺序要匹配上sql语句中的“?”的顺序,例如上面那个语句,你的params就应该写成:Object[] params = { 用户名称,用户ID }。
rsh参数:这是一个ResultSetHandler类型,一般都是创建一个该类型的子类对象然后传进去,该类型有五个常用子类,而传递的子类对象会决定query这个方法的返回值。
五个常用子类对象分别是:BeanHandler、BeanListHandler、MapHandler、MapListHandler、ScalarHandler。
传递这五个子类对象后query的返回值分别是:
一个JavaBean对象、一个装有多个JavaBean对象的List集合对象、一个装有一行结果集的Map对象(也就是一个Map,Map装着的是一行结果集)、一个装有多个一行结果集的Map的List集合对象(也就是List里有多个Map,每个Map都是一行结果集)、一个Object类型(这种一般运用在查询结果只有一行一列的情况)
QueryRunner是dbutils提供的类
说参数:
先说第四个 第一个是sql语句 但是这sql里头有? 不是标准的sql语句
-他底层是preparestatement 具体操作是这样:
-第四个参数是一个数组 QueryRunner会将里面下标为i的数据赋给第i+1个问号
-比如这里就是 type_id = params[0] 生成的sql语句就变成了
-select * from topic where type_id= [typeld的值] order by time desc
-有几个问号就要数组就要有几个数据 不能多也不能少
第三个参数是Handler 因为JDBC返回的是Result对象 但是你却可以用Topic类的引用来接受query的返回值 所以肯定处理了 至于怎么处理 就是通过Handler
//抽象类
public abstract class BaseDao {
// 使用DbUtils操作数据库
private QueryRunner queryRunner = new QueryRunner();
// 增删改
public int update(String sql , Object ... args){
Connection connection = JDBCUtils.getConnect();
try {
return queryRunner.update(connection,sql,args);
} catch (SQLException throwables) {
throwables.printStackTrace();
}finally {
JDBCUtils.Close(connection);
}
return -1;
}
// 查1个对象,返回一个对象的方法
/**
*
* @param type 返回的对象类型
* @param sql 执行的sql语句
* @param args sql对应的参数值
* @param <T> 返回的类型的泛型
*/
public<T> T queryForOne(Class<T> type , String sql,Object ... args){
Connection connection = JDBCUtils.getConnect();
try {
return queryRunner.query(connection,sql,new BeanHandler<T>(type),args);
} catch (SQLException throwables) {
throwables.printStackTrace();
}finally {
JDBCUtils.Close(connection);
}
return null;
}
// 查询返回多个对象的情况
/**
*
* @param type 返回的对象类型
* @param sql 执行的sql语句
* @param args sql对应的参数值
* @param <T> 返回的类型的泛型
* BeanListHandler
* <T>List<T>
*/
public<T>List<T> queryForList(Class<T> type , String sql,Object ... args){
Connection connection = JDBCUtils.getConnect();
try {
return queryRunner.query(connection,sql,new BeanListHandler<T>(type),args);
} catch (SQLException throwables) {
throwables.printStackTrace();
}finally {
JDBCUtils.Close(connection);
}
return null;
}
// 返回单行单列结果
public Object queryForValue(String sql , Object ... args){
Connection connection = JDBCUtils.getConnect();
try {
return queryRunner.query(connection,sql,new ScalarHandler(),args);
} catch (SQLException e) {
e.printStackTrace();
}finally {
JDBCUtils.Close(connection);
}
return null;
}
}
6.编写UserDao和测试
创建接口
public interface UserDao {
// 根据用户名查询用户信息
public User queryUserByUsername(String username);
// 保存用户信息
public int saveUser(User user);
// 根据用户名和密码查询用户信息
public User queryUserByU_P(String username , String password);
}
创建实现类
public class UserDaoImpl extends BaseDao implements UserDao {
@Override
public User queryUserByUsername(String username) {
String sql = "select id,username,password,email from user where username = ?";
return queryForOne(User.class,sql,username);
}
@Override
public User queryUserByU_P(String username, String password) {
String sql = "select id,username,password,email from user where username = ? and password = ?";
return queryForOne(User.class,sql,username,password);
}
@Override
public int saveUser(User user) {
String sql = "insert into user(username,password,email) values(?,?,?)";
return update(sql,user.getUsername(),user.getPassword(),user.getEmail());
}
}
在继承的接口里面使用 ctrl+shift+t快速生成test类
public class UserDaoTest {
@Test
public void queryUserByUsername() {
UserDao userDao = new UserDaoImpl();
if((userDao.queryUserByUsername("adminsss"))==null){
System.out.println("不存在");
}
else System.out.println("已经存在");
}
@Test
public void saveUser() {
UserDao userDao = new UserDaoImpl();
System.out.println(userDao.saveUser(new User(null,"web123","123456","web@qq.com")));
}
@Test
public void queryUserByU_P() {
UserDao userDao = new UserDaoImpl();
if((userDao.queryUserByU_P("admin","123456"))==null){
System.out.println("失败");
}
else System.out.println("成功");
}
}
7.编写UserService和测试
接口
public interface UserService {
// 注册用户
public void regisUser(User user);
// 登录
public User login(User user);
// 检查用户是否存在
public boolean existsUsername(String username);
}
实现类
public class UserServiceImpl implements UserService {
// 要操作数据库所以说要UserDao对象
private UserDao userDao = new UserDaoImpl();
@Override
public void regisUser(User user) {
userDao.saveUser(user);
}
@Override
public User login(User user) {
return userDao.queryUserByU_P(user.getUsername(), user.getPassword());
}
@Override
public boolean existsUsername(String username) {
if(userDao.queryUserByUsername(username)==null){
// 没查到
return false;
}
else{
return true;
}
}
}
测试类
public class UserServiceTest {
UserService userService = new UserServiceImpl();
@Test
public void regisUser() {
userService.regisUser(new User(null,"abc123","123456","qwe@qq.com"));
}
@Test
public void login() {
System.out.println(userService.login(new User(null,"3423c123","1234456",null)));
}
@Test
public void existsUsername() {
if(userService.existsUsername("abc123")){
System.out.println("存在");
}else{
System.out.println("不存在");
}
}
}
8.实现用户注册功能
如果代码修改后页面还是不变就需要删除网页软件的缓存 ctrl+shift+delet
在页面使用base标签来进行相对路径的实现,同时要更改页面静态资源的src。
<!--写base标签,永远固定相对路径跳转的结果-->
<base href="http://localhost:8080/BookStop/">
例子:
<link type="text/css" rel="stylesheet" href="static/css/style.css" >
注册服务servlet
public class RegistHttpServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
UserService userService = new UserServiceImpl();
String username = req.getParameter("username");
String password = req.getParameter("password");
String email = req.getParameter("email");
String code = req.getParameter("code");
// 校验验证码
if("abcde".equals(code)){
// 检查用户名是否可用
if(userService.existsUsername(username)){
// 不可用
System.out.println("用户名["+username+"]已存在");
// 调回注册界面
req.getRequestDispatcher("/pages/user/regist.html").forward(req,resp);
}else{
// 可用
userService.regisUser(new User(null,username,password,email));
}
}else{
System.out.println("验证码["+code+"]错误");
req.getRequestDispatcher("/pages/user/regist.html").forward(req,resp);
}
}
}
9.用户登录功能
public class LoginServlet extends HttpServlet {
private UserService userService = new UserServiceImpl();
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String username = request.getParameter("username");
String password = request.getParameter("password");
User login = userService.login(new User(null, username, password, null));
if(login == null){
// 失败
request.getRequestDispatcher("/pages/user/login.html").forward(request,response);
}else{
// 成功
request.getRequestDispatcher("/pages/user/login_success.html").forward(request,response);
}
}
}
2.页面修改
1.替换所有的html为jsp
ctrl+shift+r
2.抽取页面的公共部分
3.动态的base标签值
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
String basePath = request.getScheme()/* http */
+ "://"
+request.getServerName()/* localhost */
+":"
+request.getServerPort()/* 8080*/
+request.getContextPath()/*/book*/
+"/";
%>
<!--写base标签,永远固定相对路径跳转的结果-->
<base href=<%=basePath%>>
4.表单提交的错误回显
保存错误信息,回显
前端页面
3.Servlet服务优化
1.合并注册和登录为UserService
如何实现:
在前端页面设置隐藏属性,然后value设置login或者regist
在后端进行获取parameter值进行判断即可
再使用反射来避免过多的使用if判断
getDeclaredMethod(String name , class......) == 返回单个成员方法对象的数组
invoke == 调用对象的成员方法
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String action = request.getParameter("action");
try {
Method method = this.getClass().getDeclaredMethod(action, HttpServletRequest.class, HttpServletResponse.class);
method.invoke(this,request,response);
} catch (Exception e) {
e.printStackTrace();
}
}
2.BaseServlet程序实现抽象
3.BeanUtils工具类的使用
User user = new User();
System.out.println("注入之前"+user);
try {
BeanUtils.populate(user,request.getParameterMap());
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
System.out.println("注入之后"+user);
parameterMap里面的信息
问题:
javaWeb有三层 :Dao层
Servlet层
Web层
在WebUtils工具类中是Servlet层和Dao层的使用
这时候如果传入一个 request 就破坏的层次结构
那么就需要回归本源 ,直接传入一个Map 然后在工具类里赋值给 Bean
WebUtils.copyParamToBean(request.getParameterMap(),user);
public static void copyParamToBean(Map value , Object bean){
System.out.println("注入之前"+bean);
try {
BeanUtils.populate(bean,value);
完整代码
public class WebUtils {
public static<T>T copyParamToBean(Map value , T bean){
System.out.println("注入之前"+bean);
try {
BeanUtils.populate(bean,value);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
System.out.println("注入之后"+bean);
return bean;
}
}
4.图书模块
1.MVC概念
2.编写图书数据库
3.编写图书JavaBean
4.编写图书Dao
接口
// 添加
public int addBook(Book book);
// 删除
public int deleteBook(Integer id);
// 修改
public int updateBook(Book book);
// 查询
public Book queryBookById(Integer id);
// 集合
public List<Book> queryBooks();
实现
public class BookDaoImpl extends BaseDao implements BookDao {
@Override
public int addBook(Book book) {
String sql = "insert into book(`name`,`author`,`price`,`sales`,`stock`,`img_path`) values(?,?,?,?,?,?)";
return update(sql,book.getName(),book.getAuthor(),book.getPrice(),book.getSales(),book.getStock(),book.getImgPath());
}
@Override
public int deleteBook(Integer id) {
String sql = "delete from book where id = ?";
return update(sql, id);
}
@Override
public int updateBook(Book book) {
String sql = "update book set `name`=?,`author`=?,`price`=?,`sales`=?,`stock`=?,`img_path`=? where id = ?";
return update(sql,book.getName(),book.getAuthor(),book.getPrice(),book.getSales(),book.getStock(),book.getImgPath(),book.getId());
}
@Override
public Book queryBookById(Integer id) {
String sql = "select `id` , `name` , `author` , `price` , `sales` , `stock` , `img_path` imgPath from book where id = ?";
return queryForOne(Book.class, sql,id); //Book.class Book对象
}
@Override
public List<Book> queryBooks() {
String sql = "select `id` , `name` , `author` , `price` , `sales` , `stock` , `img_path` imgPath from book";
return queryForList(Book.class, sql);
}
}
5.编写图书service
接口
public interface BookService {
// 添加
public int addBook(Book book);
// 删除
public int deleteBook(Integer id);
// 修改
public int updateBook(Book book);
// 查询
public Book queryBookById(Integer id);
// 集合
public List<Book> queryBooks();
}
实现
public class BookServiceImpl implements BookService {
BookDao bookDao = new BookDaoImpl();
@Override
public int addBook(Book book) {
return bookDao.addBook(book);
}
@Override
public int deleteBook(Integer id) {
return bookDao.deleteBook(id);
}
@Override
public int updateBook(Book book) {
return bookDao.updateBook(book);
}
@Override
public Book queryBookById(Integer id) {
return bookDao.queryBookById(id);
}
@Override
public List<Book> queryBooks() {
return bookDao.queryBooks();
}
}
6.图书列表显示
创建一个bookServlet来继承baseServlet
book列表里有增删改查四个方法
我们先需要把所有数据查出来
则先写查
public void list(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//获取所有图书信息
List<Book> books = bookService.queryBooks();
// 保存到request域中
request.setAttribute("books",books);
// 请求转发
request.getRequestDispatcher("/pages/manager/book_manager.jsp").forward(request,response);
}
对bookServlet进行配置路径
对前端图书管理标签进行路径修改 : 就是在页面跳转的时候就对action进行赋值可以执行查询功能
主要问题: 标签的点击是doGet方法 可是我们的servlet都是doPost方法 则我们需要在base里面进行对doGet方法的转换
7.前后台的介绍
8.添加图书功能
protected void add(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 获取请求参数封装成book对象
Book book = WebUtils.copyParamToBean(request.getParameterMap(), new Book());
// 调用bookService的add方法
bookService.addBook(book);
// 调回图书列表页面(使用重定向)
response.sendRedirect(request.getContextPath()+"/manager/bookServlet?action=list");
}
一个小的陷阱就是调回图书列表页面时,用户按F5刷新时会列表重复提交导致添加多个重复数据
所以说使用重定向
9.删除图书
前端代码
service
protected void delete(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 获取图书编号
Integer id = Integer.valueOf( request.getParameter("id"));
// 删除图书
bookService.deleteBook(id);
// 调回图书列表页面(使用重定向)
response.sendRedirect(request.getContextPath()+"/manager/bookServlet?action=list");
}
10.修改图书
protected void getBook(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 获取图书编号
Integer id = Integer.valueOf( request.getParameter("id"));
// 删除图书
Book book = bookService.queryBookById(id);
request.setAttribute("book",book);
// 请求转发
request.getRequestDispatcher("/pages/manager/book_edit.jsp").forward(request,response);
}
点击提交时 : 出现了新旧两本书
改进:
是因为这时没有实现修改方法,所以说还要在请求时跳转到后端的update
可是这时的前端页面已经有了add的请求所以说
需要进行判断,如果有id就是修改
没有就是add
protected void update(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 获取请求参数封装成book对象
Book book = WebUtils.copyParamToBean(request.getParameterMap(), new Book());
bookService.updateBook(book);
// 调回图书列表页面(使用重定向)
response.sendRedirect(request.getContextPath()+"/manager/bookServlet?action=list");
}
5.图书列表的分页
1.实现分析
显示
逻辑分析
代码层次
2.页码JavaBean
public class Page<T> {
public static final Integer PAGE_SIZE = 4;
// 当前页码
private Integer pageNo;
// 总页码
private Integer pageTotal;
// 当前页显示数量
private Integer pageSize = PAGE_SIZE;
// 总记录数
private Integer pageTotalCount;
// 当前页数据
private List<T> items;
// 分页条的请求地址
private String url;
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public Integer getPageNo() {
return pageNo;
}
public void setPageNo(Integer pageNo) {
/* 数据边界的有效检查 */
if (pageNo < 1) {
pageNo = 1;
}
if (pageNo > pageTotal) {
pageNo = pageTotal;
}
this.pageNo = pageNo;
}
public Integer getPageTotal() {
return pageTotal;
}
public void setPageTotal(Integer pageTotal) {
this.pageTotal = pageTotal;
}
public Integer getPageSize() {
return pageSize;
}
public void setPageSize(Integer pageSize) {
this.pageSize = pageSize;
}
public Integer getPageTotalCount() {
return pageTotalCount;
}
public void setPageTotalCount(Integer pageTotalCount) {
this.pageTotalCount = pageTotalCount;
}
public List<T> getItems() {
return items;
}
public void setItems(List<T> items) {
this.items = items;
}
@Override
public String toString() {
return "Page{" +
"pageNo=" + pageNo +
", pageTotal=" + pageTotal +
", pageSize=" + pageSize +
", pageTotalCount=" + pageTotalCount +
", items=" + items +
", url='" + url + '\'' +
'}';
}
3.逻辑初步实现(框架的搭建)
Web层:BookServlet的page方法
protected void page(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 1.获取请求的参数 pageNo 和 pageSize
int pageNo = WebUtils.parseInt(request.getParameter("pageNo"), 1);
int pageSize = WebUtils.parseInt(request.getParameter("pageSize"), Page.getPageSize());
// 2.调用BookService.page(pageNo 和 pageSize) 返回 Page对象
Page<Book> page = bookService.page(pageNo, pageSize);
// 3.保存page对象到request中
request.setAttribute("page",page);
// 4.请求转发
request.getRequestDispatcher("/pages/manager/book_manager.jsp").forward(request,response);
}
Service层:
public Page<Book> page(int pageNo, int pageSize) {
Page<Book> page = new Page<>();
// 赋值
// 当前页码
page.setpageNo(pageNo);
// 单页显示数
page.setPageSize(pageSize);
// 总行数,(有多少本书)
Integer pageTotalCount = bookDao.queryForPageTotalCount();
page.setPageTotalCount(pageTotalCount);
// 总页码
Integer pageTotal = pageTotalCount/pageSize;
if(pageTotalCount%pageSize>0){
++pageTotal;
}
page.setPageTotal(pageTotal);
// 求当前页数据
int begin = (page.getpageNo() - 1)*pageSize;
List<Book> items = bookDao.queryForPageItems(begin,pageSize);
page.setItems(items);
return page;
}
Dao层:
@Override
public Integer queryForPageTotalCount() {
String sql = "select count(*) from book";
Number count = (Number) queryForValue(sql);
return count.intValue();
}
@Override
public List<Book> queryForPageItems(int begin, int pageSize) {
String sql = "select `id` , `name` , `author` , `price` , `sales` , `stock` , `img_path` imgPath from book limit ?,?";
return queryForList(Book.class,sql,begin,pageSize);
}
这时要对前端的列表显示进行修改,因为这时列表的显示不是所有book , 而是部分 , 这部分就保存在
page里面的items
4.首页、上一页等的实现
<div id="page_nav">
<%--页码大于1才显示首页和上一页--%>
<c:if test="${requestScope.page.pageNo>1}">
<a href="manager/bookServlet?action=page&pageNo=1">首页</a>
<a href="manager/bookServlet?action=page&pageNo=${requestScope.page.pageNo-1}">上一页</a>
</c:if>
<a href="#">3</a>
【${requestScope.page.pageNo}】
<a href="#">5</a>
<%--如果页码是最后一页就不显示下一页和末页--%>
<c:if test="${requestScope.page.pageNo<requestScope.page.pageTotal}">
<a href="manager/bookServlet?action=page&pageNo=${requestScope.page.pageNo+1}">下一页</a>
<a href="manager/bookServlet?action=page&pageNo=${requestScope.page.pageTotal}">末页</a>
</c:if>
共${requestScope.page.pageTotal}页,${requestScope.page.pageTotalCount}条记录 到第
<input value="4" name="pn" id="pn_input"/>页
<input type="button" value="确定">
</div>
5.页面跳转
<input value="${param.pageNo}" name="pn" id="pn_input"/>页
<input id="searchButton" type="button" value="确定">
<%-- 页面跳转--%>
<script type="text/javascript">
$(function () {
$("#searchButton").click(function () {
let pageval = $("#pn_input").val();
let pageTotal = ${requestScope.page.pageTotal};
if(pageval>pageTotal || pageval<1){
return false;
}else{
//location实现地址栏的链接书写
location.href = "${requestScope.basePath}manager/bookServlet?action=page&pageNo="+pageval;
}
})
})
</script>
location将链接字符串输入到地址栏
pageval是获取到输入框的value就是用户要跳转的。
并且对用户输入的页码进行边界判断
6.分页条页码的输出
大于5
<%--页码输出的开始--%>
<c:choose>
<%--情况1:如果总页码小于等于5的情况,页码的范围是:1-总页码--%>
<c:when test="${ requestScope.page.pageTotal <= 5 }">
<c:set var="begin" value="1"/>
<c:set var="end" value="${requestScope.page.pageTotal}"/>
</c:when>
<%--情况2:总页码大于5的情况--%>
<c:when test="${requestScope.page.pageTotal > 5}">
<c:choose>
<%--小情况1:当前页码为前面3个:1,2,3的情况,页码范围是:1-5.--%>
<c:when test="${requestScope.page.pageNo <= 3}">
<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}">
<c:set var="begin" value="${requestScope.page.pageTotal-4}"/>
<c:set var="end" value="${requestScope.page.pageTotal}"/>
</c:when>
<%--小情况3:4,5,6,7,页码范围是:当前页码减2 - 当前页码加2--%>
<c:otherwise>
<c:set var="begin" value="${requestScope.page.pageNo-2}"/>
<c:set var="end" value="${requestScope.page.pageNo+2}"/>
</c:otherwise>
</c:choose>
</c:when>
</c:choose>
<%--对结果的总结,进行页面跳转--%>
<c:forEach begin="${begin}" end="${end}" var="i">
<c:if test="${i == requestScope.page.pageNo}">
【${i}】
</c:if>
<c:if test="${i != requestScope.page.pageNo}">
<a href="manager/bookServlet?action=page&pageNo=${i}">${i}</a>
</c:if>
</c:forEach>
<%--页码输出的结束--%>
7.解决修改分页对之前增删改查功能的影响
就是吧这几个方法的请求转发后面再添加一个pageNo信息
8.前台分页的初步实现
就是模仿后台的一样
当用户访问index时,请求重定向到clientBookServlet再进行处理
public class ClientBookServlet extends BaseServlet{
private BookService bookService = new BookServiceImpl();
protected void page(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//1 获取请求的参数 pageNo 和 pageSize
int pageNo = WebUtils.parseInt(req.getParameter("pageNo"), 1);
int pageSize = WebUtils.parseInt(req.getParameter("pageSize"), Page.PAGE_SIZE);
//2 调用BookService.page(pageNo,pageSize):Page对象
Page<Book> page = bookService.page(pageNo,pageSize);
page.setUrl("client/bookServlet?action=page");
//3 保存Page对象到Request域中
req.setAttribute("page",page);
//4 请求转发到pages/manager/book_manager.jsp页面
req.getRequestDispatcher("/pages/client/index.jsp").forward(req,resp);
}
}
9.实现对分页公共部分的抽取
前台和后台的唯一区别就是地址不同
所以我们给page一个url属性,用来保存地址
page.setUrl("manager/bookServlet?action=page");
page.setUrl("client/bookServlet?action=page");
抽取分页部分为page,里面的地址就使用page对象的url属性获取
10.价格区间的搜索并分页的分析
11.价格查询的实现
与分页查询类似。就是加了min和max 的 price
前端层
Web层
protected void pageByPrice(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//1 获取请求的参数 pageNo 和 pageSize
int pageNo = WebUtils.parseInt(req.getParameter("pageNo"), 1);
int pageSize = WebUtils.parseInt(req.getParameter("pageSize"), Page.PAGE_SIZE);
int min = WebUtils.parseInt(req.getParameter("min"),0);
int max = WebUtils.parseInt(req.getParameter("max"),Integer.MAX_VALUE);
//2 调用BookService.pageByPrice(pageNo,pageSize):Page对象
Page<Book> page = bookService.pageByPrice(pageNo,pageSize,min,max);
page.setUrl("client/bookServlet?action=pageByPrice");
//3 保存Page对象到Request域中
req.setAttribute("page",page);
//4 请求转发到pages/manager/book_manager.jsp页面
req.getRequestDispatcher("/pages/client/index.jsp").forward(req,resp);
}
Servlet层
public Page<Book> pageByPrice(int pageNo, int pageSize, int min, int max) {
Page<Book> page = new Page<Book>();
// 设置每页显示的数量
page.setPageSize(pageSize);
// 求总记录数根据最小最大区间查
Integer pageTotalCount = bookDao.queryForPageTotalCountByPrice(min,max);
// 设置总记录数
page.setPageTotalCount(pageTotalCount);
// 求总页码
Integer pageTotal = pageTotalCount / pageSize;
if (pageTotalCount % pageSize > 0) {
pageTotal+=1;
}
// 设置总页码
page.setPageTotal(pageTotal);
// 设置当前页码
page.setPageNo(pageNo);
// 求当前页数据的开始索引
int begin = (page.getPageNo() - 1) * pageSize;
// 求当前页数据
List<Book> items = bookDao.queryForPageItemsByPrice(begin,pageSize,min,max);
// 设置当前页数据
page.setItems(items);
return page;
}
Dao层
@Override
public Integer queryForPageTotalCountByPrice(int min, int max) {
String sql = "select count(*) from book where price between ? and ?";
Number count = (Number) queryForValue(sql,min,max);
return count.intValue();
}
@Override
public List<Book> queryForPageItemsByPrice(int begin, int pageSize, int min, int max) {
String sql = "select `id` , `name` , `author` , `price` , `sales` , `stock`" +
" , `img_path` imgPath from book where price between ? and ? order by price limit ?,?";
return queryForList(Book.class,sql,min,max,begin,pageSize);
}
12.解决带上价格后分页条问题
StringBuilder stringBuilder = new StringBuilder("client/bookServlet?action=pageByPrice");
// 如果有最小价格,就追加
if(req.getParameter("min")!=null){
stringBuilder.append("&min=").append(req.getParameter("min"));
}
if(req.getParameter("max")!=null){
stringBuilder.append("&max=").append(req.getParameter("max"));
}
page.setUrl(stringBuilder.toString());
6.用户信息模块
1.显示用户信息
后端把信息保存到session域中:
成功
request.getSession().setAttribute("user",login);
前台显示:
判断一下:没登录就显示登录,登录了就显示信息
<c:if test="${empty sessionScope.user}">
<a href="pages/user/login.jsp">登录</a> |
<a href="pages/user/regist.jsp">注册</a>
</c:if>
<c:if test="${not empty sessionScope.user}">
<span>欢迎<span class="um_span">${sessionScope.user.username}
<a href="../order/order.jsp">我的订单</a>
<a href="../../index.jsp">注销</a>
</c:if>
2.注销登录
后台逻辑:
protected void logout(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException{
// 销毁用户信息
request.getSession().invalidate();
// 重定向到首页
response.sendRedirect(request.getContextPath());
}
前台跳转:
<a href="userServlet?action=logout">注销</a>
7.验证码问题
1.表单重复提交问题
后两者的解决办法: 验证码
2.验证码解决表单重复提交的问题
// 获取谷歌验证码
String token = (String) request.getSession().getAttribute(KAPTCHA_SESSION_KEY);
// 获取立刻删除
request.getSession().removeAttribute(KAPTCHA_SESSION_KEY);
if(token!=null&&token.equalsIgnoreCase(code)){
。。。。。。
}
3.验证码的切换刷新
// 给验证码绑定点击事件
$("#code_img").click(function () {
this.src = "${basePath}kaptcha.jpg?d="+new Date();
});
8.购物车模块
1.javaBean的Cart创建
2.购物车方法的实现
public class Cart {
private Integer totalCount;
private BigDecimal totalPrice;
private Map<Integer,CartItem> items = new HashMap<>();
// 添加商品
public void addCart(CartItem cartItem){
CartItem item = items.get(cartItem.getId());
if(item==null){
items.put(cartItem.getId(),cartItem);
}else{
// 数量累加
item.setCount(item.getCount()+1);
// 总金额
item.setTotalPrice(item.getPrice().multiply(new BigDecimal(item.getCount())));
}
}
// 删除
public void deleteCart(Integer id){
items.remove(id);
}
// 清空
public void clear(){
items.clear();
}
// 修改商品数量
public void updateCount(Integer id , Integer count){
CartItem item = items.get(id);
if(item !=null){
// 数量累加
item.setCount(item.getCount()+1);
// 总金额
item.setTotalPrice(item.getPrice().multiply(new BigDecimal(item.getCount())));
}
}
public Integer getTotalCount(){
totalCount = 0;
for(Map.Entry<Integer,CartItem> entry : items.entrySet()){
totalCount += entry.getValue().getCount();
}
return totalCount;
}
public BigDecimal getTotalPrice() {
return totalPrice;
}
}
3.添加商品到购物车功能的实现
前台的响应事件
<script type="text/javascript">
$(function (){
$("button.addToCart").click(function () {
let bookId = $(this).attr("bookId");
location.href = "http://localhost:8080/book/carServlet?action=addItem&id="+bookId;
})
})
</script>
后台数据处理
protected void addItem(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 获取请求的参数 商品编号
int id = WebUtils.parseInt(req.getParameter("id"), 0);
// 调用bookService.queryBookById(id):Book得到图书的信息
Book book = bookService.queryBookById(id);
// 把图书信息,转换成为CartItem商品项
CartItem cartItem = new CartItem(book.getId(),book.getName(),1,book.getPrice(),book.getPrice());
// 调用Cart.addItem(CartItem);添加商品项
Cart cart = (Cart) req.getSession().getAttribute("cart");
if (cart == null) {
cart = new Cart(); //不要自己重新创建
//Cart cartNew = .... 这是错的
req.getSession().setAttribute("cart",cart);
}
cart.addItem(cartItem);
System.out.println(cart);
System.out.println("请求头Referer的值:" + req.getHeader("Referer"));
// 最后一个添加的商品名称
req.getSession().setAttribute("lastName", cartItem.getName());
// 重定向回原来商品所在的地址页面
resp.sendRedirect(req.getHeader("Referer"));
}
4.购物车里的商品显示
5.删除购物车商品项
<script type="text/javascript">
$(function (){
$("a.deleteItem").click(function () {
return confirm("你确定要删除【" + $(this).parent().parent().find("td:first").text()+"】吗?");
})
})
</script>
<td><a class="deleteItem" href="cartServlet?action=deleteItem&id=${entry.value.id}">删除</a></td>
protected void deleteItem(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 获取请求的参数 商品编号
int id = WebUtils.parseInt(req.getParameter("id"), 0);
Cart cart = (Cart)req.getSession().getAttribute("cart");
if(cart!=null){
cart.deleteItem(id);
// 重定向回原来商品所在的地址页面
resp.sendRedirect(req.getHeader("Referer"));
}
}
6.清空购物车
$("a.clear").click(function () {
return confirm("你确定要清空购物车 吗?");
})
<span class="cart_span"><a class="clear" href="cartServlet?action=clear">清空购物车</a></span>
protected void clear(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
Cart cart = (Cart)req.getSession().getAttribute("cart");
if(cart!=null){
cart.clear();
// 重定向回原来商品所在的地址页面
resp.sendRedirect(req.getHeader("Referer"));
}
}
7.修改购物车的商品数量
<script type="text/javascript">
$(function () {
// 给输入框绑定 onchange内容发生改变事件
$(".updateCount").change(function () {
// 获取商品名称
var name = $(this).parent().parent().find("td:first").text();
var id = $(this).attr('bookId');
// 获取商品数量
var count = this.value;
if ( confirm("你确定要将【" + name + "】商品修改数量为:" + count + " 吗?") ) {
//发起请求。给服务器保存修改
location.href = "http://localhost:8080/book/cartServlet?action=updateCount&count="+count+"&id="+id;
} else {
// defaultValue属性是表单项Dom对象的属性。它表示默认的value属性值。
this.value = this.defaultValue;
}
}
rotected void updateCount(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 获取请求的参数 商品编号
int id = WebUtils.parseInt(req.getParameter("id"), 0);
int count = WebUtils.parseInt(req.getParameter("count"), 1);
Cart cart = (Cart)req.getSession().getAttribute("cart");
if(cart!=null){
cart.updateCount(id,count);
// 重定向回原来商品所在的地址页面
resp.sendRedirect(req.getHeader("Referer"));
}
}
8.首页购物车数据回显
// 最后一个添加的商品名称
req.getSession().setAttribute("lastName", cartItem.getName());
9.订单模块
1.分析
2.创建数据库
create table t_order(
`order_id` varchar(50) primary key,
`create_time` datetime not null,
`price` decimal(11,2) not null,
`status` int not null default 0,
`user_id` int not null,
foreign key (`user_id`) references user(`id`)
);
create table t_order_item(
`id` int primary key auto_increment,
`name` varchar(30) not null,
`price` decimal(11,2),
`total_price` decimal(11,2),
`count` int not null,
`order_id` varchar(50) not null,
foreign key (`order_id`) references t_order(`order_id`)
);
3.编写数据模型
4.编写订单模块创建的Dao
接口
public interface OrderDao {
public int saveOrder(Order order);
}
实现
public class OrderDaoImpl extends BaseDao implements OrderDao {
@Override
public int saveOrder(Order order) {
String sql = "insert into t_order(`order_id`,`create_time`,`price`,`status`,`user_id`) values(?,?,?,?,?)";
return update(sql,order.getOrderId(),order.getCreateTime(),order.getPrice(),order.getStatus(),order.getUserId());
}
}
接口
public interface OrderItemDao {
public int saveOrderItem(OrderItem orderItem);
}
实现
public class OrderItemDaoImpl extends BaseDao implements OrderItemDao {
@Override
public int saveOrderItem(OrderItem orderItem) {
String sql = "insert into t_order_item(`name`,`count`,`price`,`total_price`,`order_id`) values(?,?,?,?,?)";
return update(sql,orderItem.getName(),orderItem.getCount(),orderItem.getPrice(),orderItem.getTotalPrice(),orderItem.getOrderId());
}
}
5.订单模块的service层
接口
public interface OrderService {
public String createOrder(Cart cart, Integer userId);//获取购物车和用户id
}
实现
public class OrderServiceImpl implements OrderService {
private OrderDao orderDao = new OrderDaoImpl();
private OrderItemDao orderItemDao = new OrderItemDaoImpl();
private BookDao bookDao = new BookDaoImpl();
@Override
public String createOrder(Cart cart, Integer userId) {
// 订单号===唯一性
String orderId = System.currentTimeMillis()+""+userId;
// 创建一个订单对象
Order order = new Order(orderId,new Date(),cart.getTotalPrice(), 0,userId);
// 保存订单
orderDao.saveOrder(order);
// 遍历购物车中每一个商品项转换成为订单项保存到数据库
for (Map.Entry<Integer, CartItem>entry : cart.getItems().entrySet()){
// 获取每一个购物车中的商品项
CartItem cartItem = entry.getValue();
// 转换为每一个订单项
OrderItem orderItem = new OrderItem(null,cartItem.getName(),cartItem.getCount(),cartItem.getPrice(),cartItem.getTotalPrice(), orderId);
// 保存订单项到数据库
orderItemDao.saveOrderItem(orderItem);
// 更新库存和销量
Book book = bookDao.queryBookById(cartItem.getId());
book.setSales( book.getSales() + cartItem.getCount() );
book.setStock( book.getStock() - cartItem.getCount() );
bookDao.updateBook(book);
}
// 清空购物车
cart.clear();
return orderId;
}
}
10.拦截器权限配置,线程管理
1.对后台管理进行拦截
当用户名不为空,并且用户名是管理员时,才往下走
public class ManagerFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
User user = (User) httpServletRequest.getSession().getAttribute("user");
if(user == null){
httpServletRequest.getRequestDispatcher("/pages/user/login.jsp").forward(servletRequest,servletResponse);
}else{
if("admin".equals(user.getUsername())){
filterChain.doFilter(servletRequest,servletResponse);
}else{
httpServletRequest.getRequestDispatcher("/pages/user/login.jsp").forward(servletRequest,servletResponse);
}
}
}
}
2.使用Filter和ThreadLocal组合事务
使用一个Map,将每个线程的名字和要储存的数关联,线程的名字具有key的唯一性
当线程调用别的程序时,再另外一个程序调用Map,依然可以得到value
public class ThreadTest {
public static Map<Object,Integer> data = new Hashtable<>();
public static Random random = new Random();
public static class Task implements Runnable{
@Override
public void run() {
Integer integer = random.nextInt(1000);
String name = Thread.currentThread().getName();
System.out.println("线程["+name+"]启动,生成的随机数是:"+integer);
data.put(name,integer);
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Object object = data.get(name);
System.out.println("线程["+name+"]结束,取出关联数据:"+object);
}
}
public static void main(String[] args) {
for (int i = 0; i < 3; i++) {
new Thread(new Task()).start();
}
}
}
使用ThreadLocal存储数据
3.进行数据库的事务操作
可以发现,均使用一个线程
执行的方法
4.使用Filter来统一管理Service事务
public class TransactionFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
try {
filterChain.doFilter(servletRequest,servletResponse);
JDBCUtils.commitAndClose();//提交事务
} catch (Exception e) {
JDBCUtils.rollbackAndClose();//出错回滚
e.printStackTrace();
}
}
@Override
public void destroy() {
}
}
<filter><!--对所有事务,项目下的所有程序都加上事务管理-->
<filter-name>TransactionFilter</filter-name>
<filter-class>Web.filter.TransactionFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>TransactionFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>