JavaWeb第(4)部分:书城项目

272 阅读16分钟

书城项目

1.用户模块

1.表单验证

image-20211015165727556

    <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.项目结构

image-20211023192745097

image-20211023192700522

3.创建数据库

image-20211023195453191

image-20211023195503594

在pojo包下 编写javaBean类 : user

image-20211023195842708

4.JDBCUtils 工具类和测试

关于JDBCUtils类加载器的介绍:

关于InputStream in = JDBCUtil.class.getClassLoader().getResourceAsStream("jdbc.properties")的介绍

blog.csdn.net/dragon901/a…

image-20211023202937526

image-20211023202948480

image-20211023202957126

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();
            }
        }
    }


//
}
测试

image-20211023203557403

由于数据库连接池就是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

image-20211023214920137

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.实现用户注册功能

image-20211024182215124

如果代码修改后页面还是不变就需要删除网页软件的缓存  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);
        }
    }
}

image-20211024181702597

9.用户登录功能

image-20211024182237257

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

image-20211028172701459

2.抽取页面的公共部分

image-20211028182934659

image-20211028182946643

image-20211028182958681

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.表单提交的错误回显

保存错误信息,回显

image-20211028192402331

image-20211028192410022

前端页面

image-20211028192337763

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程序实现抽象

image-20211028205231547

image-20211028205250253

image-20211028205258552

3.BeanUtils工具类的使用

image-20211028210706293

image-20211028210717848

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里面的信息

image-20211028211558465

问题:
javaWeb有三层 :DaoServletWeb层

在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概念

image-20211031142712086

2.编写图书数据库

image-20211031142748569

3.编写图书JavaBean

image-20211031142819604

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.图书列表显示

image-20211031090045887

image-20211031095047461

创建一个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进行赋值可以执行查询功能

image-20211031095347033

主要问题: 标签的点击是doGet方法  可是我们的servlet都是doPost方法  则我们需要在base里面进行对doGet方法的转换

image-20211031095554439

7.前后台的介绍

image-20211031100002842

8.添加图书功能

image-20211031100818027

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刷新时会列表重复提交导致添加多个重复数据
所以说使用重定向

image-20211021170232163

9.删除图书

image-20211031103244825

前端代码

image-20211031103302510

image-20211031103312782

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.修改图书

image-20211031104246507

image-20211031104431992

image-20211031104414897

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);
    }
点击提交时 : 出现了新旧两本书

image-20211031104309522

改进:

是因为这时没有实现修改方法,所以说还要在请求时跳转到后端的update
可是这时的前端页面已经有了add的请求所以说
需要进行判断,如果有id就是修改
没有就是add

image-20211031110441622

    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");

    }

image-20211031110225968

5.图书列表的分页

1.实现分析

显示

image-20211031144847480

逻辑分析

image-20211031144839564

代码层次

image-20211031145600722

2.页码JavaBean

image-20211031151016648

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.逻辑初步实现(框架的搭建)

image-20211031164508675

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 

image-20211031164628637

4.首页、上一页等的实现

image-20211101151126179

<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.分页条页码的输出

image-20211101153300560

大于5

image-20211101153653875


  <%--页码输出的开始--%>
   <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.解决修改分页对之前增删改查功能的影响

image-20211103145604291

就是吧这几个方法的请求转发后面再添加一个pageNo信息

8.前台分页的初步实现

就是模仿后台的一样
当用户访问index时,请求重定向到clientBookServlet再进行处理

image-20211103154452973

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属性获取

image-20211103154654435

10.价格区间的搜索并分页的分析

image-20211103194645365

image-20211103194658925

11.价格查询的实现

image-20211103200030511

与分页查询类似。就是加了min和max 的 price
前端层

image-20211103200214539

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.解决带上价格后分页条问题

image-20211103201238182

image-20211103201244246

 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> &nbsp;&nbsp;
				</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>&nbsp;&nbsp;
				</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.表单重复提交问题

image-20211106150405680

后两者的解决办法:    验证码

2.验证码解决表单重复提交的问题

image-20211106151119101

//        获取谷歌验证码
        String token = (String) request.getSession().getAttribute(KAPTCHA_SESSION_KEY);
//        获取立刻删除
        request.getSession().removeAttribute(KAPTCHA_SESSION_KEY);
        
        if(token!=null&&token.equalsIgnoreCase(code)){
        。。。。。。
        }

3.验证码的切换刷新

image-20211106161300523

//	给验证码绑定点击事件
$("#code_img").click(function () {
	this.src = "${basePath}kaptcha.jpg?d="+new Date();
});

8.购物车模块

image-20211106163434324

image-20211106163441773

1.javaBean的Cart创建

image-20211106163917683

image-20211106163928565

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"));
    }

image-20211108164742683

4.购物车里的商品显示

image-20211108172551054

image-20211108172633495

image-20211108172648469

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.修改购物车的商品数量

image-20211109095223483

image-20211109101155938

image-20211109101143262

<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());

image-20211109101430823

9.订单模块

1.分析

image-20211109101909144

image-20211109102952630

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.编写数据模型

image-20211109104210204

image-20211109104227368

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组合事务

image-20211119191507343

使用一个Map,将每个线程的名字和要储存的数关联,线程的名字具有key的唯一性
当线程调用别的程序时,再另外一个程序调用Map,依然可以得到value

image-20211119193442234

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存储数据

image-20211119194730172

image-20211119194800590

3.进行数据库的事务操作

image-20211119195804269

可以发现,均使用一个线程

image-20211119200020662

执行的方法

image-20211119200439107

4.使用Filter来统一管理Service事务

image-20211120151325831

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>

5.使用Tomcat统一管理异常页面

image-20211120155151019