1、熟悉书城业务需求

  1. 用户登录、用户注册(涉及到验证码、正则表达式);
  2. 主界面上展示了图书的基本信息,并且分页展示
  3. 用户可以点击添加图书,添加到购物车里,然后购物车可以对图书数量进行加减、展示总金额、删除,用户点击去结账之后生成一个订单;
  4. 订单界面可以看到,订单编号、订单日期、订单金额、订单数量、订单状态等;
  5. 后台管理人员登录管理员账号之后,可以有订单详情界面,也可以对图书进行管理(这里要给用户设定一栏角色栏)。

注意:

  • 文中的静态页面已经提供好的,如果没有去尚硅谷公众号下载,直接使用其样式和静态页面即可。

2、数据库设计

2.1 抽取实体

  • 图书 Book
  • 用户 User
  • 订单 OrderBean
  • 订单详情 OrderItem
  • 购物车项 CartItem

2.2 分析其中的属性

  • 图书 : 书名、作者、价格、销量、库存、封面、状态
  • 用户 : 用户名、密码、邮箱
  • 订单 : 订单编号、订单日期、订单金额、订单数量、订单状态、用户(给用户看的)
  • 订单详情 : 图书、数量、所属订单(后台管理)
  • 购物车项 : 图书、数量、所属用户(用户)

2.3 分析实体之间的关系

  • 用户:购物车项 1:N(一个用户可以有多个购物车项)
  • 订单:订单详情 1:N(一个订单有多个订单详情,订单详情表示购物车的一项)
  • 用户:订单 1: N(一个用户可以有多个订单)
  • 图书:订单详情 1:N(一本图书会在多本订单图书中出现)
  • 图书:购物车项 1:N(一本图书可以出现在多个购物车项中)
    在这里插入图片描述

3、根据数据库的表新建 pojo 类(ORM编程思想)

ORM 编程思想:(object relational mapping),有点万事万物皆对象那种意思

  • 一个数据表对应一个 java 类
  • 表中的一条记录对应 java 类的一个对象
  • 表中的一个字段对应 java 类的一个属性

3.1 数据库表

从尚硅谷的官方公众号去获取资料,导入的数据库表如下图所示:
在这里插入图片描述

  • t_book:图书表
  • t_cart_item:购物车项(表示购物车的某一行,某一项,对应订单详情中的某一项)
  • t_order:订单表
  • t_order_item:订单详情表(当购物车点击结账之后,每一项购物车项生成对应的一项订单详情)
  • t_user:用户表

3.2 创建 pojo 类

  1. 这里没有特别展示的都需要创建每个属性的 get/set 方法;
  2. java 类中的属性名字尽量和数据库的列名相同,如果确实不相同的话,后面写 sql 语句的时候,记得起列的别名。

3.2.1 Book 类

在这里插入图片描述

public class Book {
    private Integer id ;
    private String bookImg ;
    private String bookName ;
    private Double price ;
    private String author ;
    private Integer saleCount ; //销量
    private Integer bookCount ; //库存
    private Integer bookStatus = 0 ;        //0:正常  -1:无效

    public Book(){}
    public Book(Integer id) {
        this.id = id;
    }
}

3.2.2 CartItem 项

在这里插入图片描述

public class CartItem {
    private Integer id ;
    private Book book ;
    private Integer buyCount ;
    private User userBean ;

    public CartItem(){}
    public CartItem(Integer id) {
        this.id = id;
    }    
}

3.2.3 OrderBean 类

在这里插入图片描述

public class OrderBean {
    private Integer id ;
    private String orderNo ;
    private LocalDateTime orderDate;
    private User orderUser ;
    private Double orderMoney ;
    private Integer orderStatus;

    private List<OrderItem> orderItemList ;     //1:N

    public OrderBean(){}
    public OrderBean(Integer id){
        this.id= id;
    }
}

3.2.4 OrderItem 类

在这里插入图片描述

public class OrderItem {
    private Integer id ;
    private Book book ;                 //M:1
    private Integer buyCount ;
    private OrderBean orderBean ;       //M:1

    public OrderItem(){}
    public OrderItem(Integer id) {
        this.id = id;
    }
}

3.2.5 User 类

在这里插入图片描述

public class User {
    private Integer id ;
    private String uname ;
    private String pwd ;
    private String email;
    private Integer role ;

    private List<OrderBean> orderList ;     //1:N

    public User(){}
    public User(Integer id) {
        this.id = id;
    }
}

4、前期配置

  1. 将静态页面粘贴到 WEB-INF 界面下,不让别人随便访问,但是我们可以通过设置让别人访问到。
  2. 添加包的依赖关系,配置 Tomcat,这里不仔细讲,前面说过,需要添加的 jar 包有数据库连接的包、thymeleaf 的包、Durid 数据库连接池的包,如果你把 ssm 打包成了 jar 包,还需要加入那个 jar 包。
  3. 配置文件:
    配置文件 applicationContext.xml 和 jdbc.properties

applicationContext.xml 文件配置,上面 !DOCTYPE 这部分可写可不写,写了后面少依赖关系,会报错提醒,并且放置在 src 目录下(这里将所有后面所有要用到的配置都写上来,后面就不专门写配置文件这一步了):

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE beans [
    <!ELEMENT beans (bean*)>
    <!ELEMENT bean (property*)>
    <!ELEMENT property (#PCDATA)>

    <!ATTLIST bean id ID #REQUIRED>
    <!ATTLIST bean class CDATA #IMPLIED>
    <!ATTLIST property name CDATA #IMPLIED>
    <!ATTLIST property ref IDREF #IMPLIED>
]>

<beans>
    <bean id="page" class="com.atguigu.myssm.myspringmvc.PageController"/>
    
    <!-- DAO -->
    <bean id="userDAO" class="com.atguigu.book.dao.impl.UserDAOImpl"/>
    <bean id="bookDAO" class="com.atguigu.book.dao.impl.BookDAOImpl"/>
    <bean id="cartItemDAO" class="com.atguigu.book.dao.impl.CartItemDAOImpl"/>
    <bean id="orderDAO" class="com.atguigu.book.dao.impl.OrderDAOImpl"/>
    <bean id="orderItemDAO" class="com.atguigu.book.dao.impl.OrderItemDAOImpl"/>

    <!-- service -->
    <bean id="userService" class="com.atguigu.book.service.impl.UserServiceImpl">
        <property name="userDAO" ref="userDAO"/>
    </bean>
    <bean id="bookService" class="com.atguigu.book.service.impl.BookServiceImpl">
        <property name="bookDAO" ref="bookDAO"/>
    </bean>
    <bean id="cartItemService" class="com.atguigu.book.service.impl.CartItemServiceImpl">
        <property name="cartItemDAO" ref="cartItemDAO"/>
        <property name="bookService" ref="bookService"/>
    </bean>
    <bean id="orderService" class="com.atguigu.book.service.impl.OrderServiceImpl">
        <property name="orderDAO" ref="orderDAO"/>
        <property name="orderItemDAO" ref="orderItemDAO"/>
        <property name="cartItemDAO" ref="cartItemDAO"/>
    </bean>

    <!-- controller -->
    <bean id="user" class="com.atguigu.book.controller.UserController">
        <property name="userService" ref="userService"/>
        <property name="cartItemService" ref="cartItemService"/>
    </bean>
    <bean id="book" class="com.atguigu.book.controller.BookController">
        <property name="bookService" ref="bookService"/>
    </bean>
    <bean id="cart" class="com.atguigu.book.controller.CartController">
        <property name="cartItemService" ref="cartItemService"/>
    </bean>
    <bean id="order" class="com.atguigu.book.controller.OrderController">
        <property name="orderService" ref="orderService"/>
    </bean>
</beans>

jdbc.properties 数据库连接配置,这里使用的是 Durid 数据库连接池:

driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/bookdb?useUnicode=true&characterEncoding=utf-8&useSSL=false
username=root
password=abc123

# 初始化时建立物理连接的个数
initialSize=5
# 最大连接池数量
maxActive=10
# 最大等待时间
maxWait=3000

配置文件 web.xml ,因为我们将静态页面粘贴到 WEB-INF 界面下,所以文件中视图前缀改成 /WEB-INF/pages/,:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

    <context-param>
        <param-name>view-prefix</param-name>
        <param-value>/WEB-INF/pages/</param-value>
    </context-param>
    <context-param>
        <param-name>view-suffix</param-name>
        <param-value>.html</param-value>
    </context-param>
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>applicationContext.xml</param-value>
    </context-param>
</web-app>

5、登录验证

5.1 修改 login.html 界面

  1. 将静态页面所有引入 css 样式的文件路径改成 th:xxxx 格式,例如:th:href="@{/static/css/style.css}"
  2. 用户名和密码的 form 表单改成 th:action="@{/user.do}",并且要实现一个登陆方法 <input type="hidden" name="operate" value="login"/>
  3. 其中表单要传两个参数,name="uname"name="pwd"
<div class="form">
    <form th:action="@{/user.do}" method="post">
        <input type="hidden" name="operate" value="login"/>
        <label>用户名称:</label>
        <input class="itxt" type="text" placeholder="请输入用户名" autocomplete="off" tabindex="1"
               name="uname" id="username" value="lina"/>
        <br/>
        <br/>
        <label>用户密码:</label>
        <input class="itxt" type="password" placeholder="请输入密码" autocomplete="off" tabindex="1"
               name="pwd" id="password" value="ok"/>
        <br/>
        <br/>
        <input type="submit" value="登录" id="sub_btn"/>
    </form>
    <div class="tit">
        <a th:href="@{/page.do?operate=page&page=user/regist}">立即注册</a>
    </div>
</div>

5.2 新建 UserController 控制器

  1. 实现 login 方法,需要 uname 和 pwd 两个参数,登陆成功之后将 user 保存到 session 作用域中,需要新增一个 session 参数
public class UserController {

    private UserService userService ;
    private CartItemService cartItemService ;

    //用户登陆验证
    public String login(String uname , String pwd , HttpSession session){
        User user = userService.login(uname, pwd);
        if(user!=null){
            session.setAttribute("currUser",user);
            return "redirect:book.do";
        }
        //如果没有登陆成功,重新回到login登录页面
        return "user/login";
    }
}

解释:

  • 首先调用 userService 层中的登陆方法进行登陆验证;
  • 登陆成功之后将获取到的 user 类设置到 session 作用域中,方便之后调用。

5.3 新建 UserService 层

实现登陆验证 login 方法:

public class UserServiceImpl implements UserService {
    private UserDAO userDAO ;

    @Override
    public User login(String uname, String pwd) {
        return userDAO.getUser(uname,pwd);
    }
}

5.4 新建 UserDAO 层

实现 getUser 方法从数据库中获取用户名和密码信息:

public class UserDAOImpl extends BaseDAO<User> implements UserDAO {
    @Override
    public User getUser(String uname, String pwd) {
        return load("select * from t_user where uname like ? and pwd like ? " , uname , pwd );
    }
}

6、主页面图书显示

  1. 上一步登录成功之后,return "redirect:book.do"; 重定向到 bookController 控制器中去做图书的相关控制;
  2. 在中央控制器中,如果没有发送 operate 值,默认 index 方法,所以我们在 bookController 控制器中新建 index 方法展示图书列表;

6.1 修改 index.html 页面

  1. 将静态页面所有引入 css 样式的文件路径改成 th:xxxx 格式;
  2. 遍历每一本书:th:each="book : ${session.bookList}"
<div class="list-content">
    <div href="" class="list-item" th:each="book : ${session.bookList}" th:object="${book}">
        <img th:src="@{|/static/uploads/*{bookImg}|}" alt="">
        <p th:text="|书名:*{bookName}|">书名:活着</p>
        <p th:text="|作者:*{author}|">作者:余华</p>
        <p th:text="|价格:¥*{price}|">价格:¥66.6</p>
        <p th:text="|销量:*{saleCount}|">销量:230</p>
        <p th:text="|库存:*{bookCount}|">库存:1000</p>
        <button th:onclick="|addCart(*{id})|">加入购物车</button>
    </div>
</div>

解释:

  • 表示这个块中引用的都是 book 这个对象,下面就不用再 book.xxx 来调用它的属性了:th:object="${book}"
  • 下面我调用该对象的 某个属性只需要 *{} 就可以了,比如调用 book 中的图片属性:th:src="@{|/static/uploads/*{bookImg}|}"

6.2 新建 BookController 控制器

  1. 实现 index 方法,调用 BookService 层的方法获取图书列表;
  2. 并且将获取到的图书列表 set 进 session 作用域中;
public class BookController {
    private BookService bookService ;

    //在中央控制器中,如果没有发送operate默认index方法
    public String index(HttpSession session){
        List<Book> bookList = bookService.getBookList();
        session.setAttribute("bookList",bookList);
        return "index";
    }
}

6.3 新建 BookService 层

public class BookServiceImpl implements BookService {
    private BookDAO bookDAO ;

    @Override
    public List<Book> getBookList() {
        return bookDAO.getBookList();
    }
}

6.4 新建 BookDAO 层

public class BookDAOImpl extends BaseDAO<Book> implements BookDAO {
    @Override
    public List<Book> getBookList() {
        return executeQuery("select * from t_book where bookStatus=0");
    }
}

7、添加到购物车按钮功能

7.1 修改index界面

  1. 点击某一本图书加入购物车:th:onclick="|addCart(*{id})|"
<button th:onclick="|addCart(*{id})|">加入购物车</button>

解释;

  • 这里的 *{id} 是和上面衔接起来的,因为我们在整个图书列表的 div 块上定义了 th:object="${book}"
  1. 需要补一个 addCart 的 js 方法:
function addCart(bookId){
    window.location.href='cart.do?operate=addCart&bookId='+bookId;
}

7.2 功能分析

  1. 既然上面跳转到 cartController 控制器中的 addCart 方法,所以我们需要根据这个 bookId 实现一个添加功能
  2. 将指定图书添加到当前用户的购物车中,判断当前用户的购物车中是否有这本书的 CartItem,有的话对这一购物车项进行 update,没有的话需要新增一个购物车项即 add;
    1. 如果当前用户的购物车中已经存在这个图书了,那么将购物车中这本图书的数量+1;
    2. 否则,在我的购物车中新增一个这本图书的 CartItem,数量是1;

7.2.1 新建 CartItemDAO 层

  1. 需要 CartItemDAO 中新增购物车项,将购物车中没有的图书添加进来;
  2. 需要在 CartItemDAO 中修改特定的购物车项,如果图书在购物车中已经存在;
public class CartItemDAOImpl extends BaseDAO<CartItem> implements CartItemDAO {
    @Override
    public void addCartItem(CartItem cartItem) {
        executeUpdate("insert into t_cart_item values(0,?,?,?)",cartItem.getBook().getId(),cartItem.getBuyCount(),cartItem.getUserBean().getId());
    }

    @Override
    public void updateCartItem(CartItem cartItem) {
        executeUpdate("update t_cart_item set buyCount = ? where id = ? " , cartItem.getBuyCount(),cartItem.getId()) ;
    }
}

7.2.2 新建购物车 pojo 类

  1. 要判断购物车中是否有某一图书项,购物车在哪里?新建购物车 pojo 类,这个和数据库没啥关系,只是用于逻辑判断、页面展示;
  2. 修改 getTotalMoney 方法、getTotalCount 方法、getTotalBookCount 方法(而且这方法是只读属性,没有set方法);
  3. 建完这个 pojo 类再去实现 CartItemService 层的方法;

Cart 类的实现,其中存放着购物车项,用来和其他类进行连接:

public class Cart {
    private Map<Integer,CartItem> cartItemMap;      //购物车中购物车项的集合 , 这个Map集合中的key是Book的id
    private Double totalMoney ;                     //购物车的总金额
    private Integer totalCount ;                    //购物车中购物项的数量,总共有多少项,而不是物品总件数
    private Integer totalBookCount ;                //购物车中书本的总数量,而不是购物车项的总数量

    public Cart(){}

    public Map<Integer, CartItem> getCartItemMap() {
        return cartItemMap;
    }
    public void setCartItemMap(Map<Integer, CartItem> cartItemMap) {
        this.cartItemMap = cartItemMap;
    }
    public Double getTotalMoney() {
        totalMoney = 0.0;
        //如果购物车中有东西,将购物车每一项从map中取出来,根据cartItem确定数的价格和购买的数量
        if(cartItemMap!=null && cartItemMap.size()>0){
            Set<Map.Entry<Integer, CartItem>> entries = cartItemMap.entrySet();
            for(Map.Entry<Integer,CartItem> cartItemEntry : entries){
                CartItem cartItem = cartItemEntry.getValue();
                totalMoney = totalMoney + cartItem.getBook().getPrice() * cartItem.getBuyCount() ;
            }
        }
        return totalMoney;
    }
    public Integer getTotalCount() {
        totalCount = 0 ;
        if(cartItemMap!=null && cartItemMap.size()>0){
            totalCount = cartItemMap.size() ;
        }
        return totalCount;
    }
    public Integer getTotalBookCount() {
        totalBookCount = 0 ;
        if(cartItemMap!=null && cartItemMap.size()>0){
            for (CartItem cartItem : cartItemMap.values()){
                totalBookCount = totalBookCount + cartItem.getBuyCount() ;//每一购物车项它的数量
            }
        }
        return totalBookCount;
    }
}

注意:

  • 这里的 cartItemMap 表示购物车中购物车项的集合 , 这个 Map 集合中的 key 是 Book 的 id ,也就是将图书和购物车项进行关联,最后放置在一个 map 中,如果我们又添加了一本书,那我们就看 map 中是否能找到对应的 key,没有的话就进行新增,有的话,将对应的 value 值取出来,然后将其中的数量属性 +1 之后 set 回去;
  • 对于 totalMoney 、totalCount 、totalBookCount 属性,我们只需要它们的 get 方法,因为它们是根据购物车项算出来的,而不能我们自己进行设置;
  • 即如果购物车中有东西,我们把每一个购物车项取出来,然后根据 cartItem 获取书的金额和目前购物车项的数量进行一个计算

7.2.3 新建 CartItemService 层

  1. addOrUpdateCartItem 方法,判断购物车中到底有没有某一本图书,这里需要将购物车 cart 作为一个参数传进来;
  2. 实现添加购物车项功能即 addCartItem 方法;
  3. 实现更新购物车项的功能即 updateCartItem 方法;
public class CartItemServiceImpl implements CartItemService {
    private CartItemDAO cartItemDAO ;
    private BookService bookService ;

    @Override
    public void addCartItem(CartItem cartItem) {
        cartItemDAO.addCartItem(cartItem);
    }

    @Override
    public void updateCartItem(CartItem cartItem) {
        cartItemDAO.updateCartItem(cartItem);
    }

    @Override
    public void addOrUpdateCartItem(CartItem cartItem, Cart cart) {
        //判断当前用户的购物车中是否有这本书的CartItem,有->update , 无->add
        if (cart != null) {
            Map<Integer, CartItem> cartItemMap = cart.getCartItemMap();
            //如果购物车中的购物车项为空,新建一个map
            if (cartItemMap == null) {
                cartItemMap = new HashMap<>();
            }
            //1.如果当前用户的购物车中已经存在这个图书了,那么将购物车中这本图书的数量+1
            if (cartItemMap.containsKey(cartItem.getBook().getId())) {
                CartItem cartItemTemp = cartItemMap.get(cartItem.getBook().getId());
                cartItemTemp.setBuyCount(cartItemTemp.getBuyCount() + 1);
                updateCartItem(cartItemTemp);
            } else {
                //2.否则,在我的购物车中新增一个这本图书的CartItem,数量是1
                addCartItem(cartItem);
            }
        } else {  //连购物车都没有的情况
            addCartItem(cartItem);
        }
    }
}

解释:

  • 如果购物车中的购物车项为空,新建一个 map,防止下面遍历 map 的时候发生空指针错误;
  • 这里就能看出来购物车中将 Book 的 Id 和购物车项封装成一个 map 的好处了,我们判断购物车中有没有某一本书,根据 Book 的 Id 来进行查询即可,cartItemMap.containsKey(cartItem.getBook().getId())
  • 如果当前用户的购物车中存在这本书,将购物车中对应这本书的那一项取出来,将它的数量 +1 之后再 set 回去,最后更新购物车项;
  • 如果购物车中不存在这本书,我们直接新增购物车项。

7.2.4 新建 CartController 控制器

  1. 我们要实现 addCart 的话要调用 CartItemService 层的 addOrUpdateCartItem 方法,那么我们需要两参数,这两参数怎么获取呢?
  2. 我们登录成功的时候,应该已经加载了当前用户的购物车信息(下一个功能实现),所以我们有当前用户的购物车信息;
  3. 要新建一个 cartItem 项(用三参数构造器),其中 book 可以根据 bookId 来构造,buyCount 设置为1,user 就是当前用户;
public class CartController {
    private CartItemService cartItemService ;

    //加书到购物车中
    public String addCart(Integer bookId , HttpSession session){
        User user = (User)session.getAttribute("currUser");
        CartItem cartItem = new CartItem(new Book(bookId),1,user);
        //将指定的图书添加到当前用户的购物车中
        cartItemService.addOrUpdateCartItem(cartItem,user.getCart());

        //这里我们不要点击一本书就跳转到购物车,所以将这里的重定向页面改成index页面
//        return "redirect:cart.do";
        return "index";
    }
}

7.2.5 cartItem 类中新建构造器

  1. 新建一个三参数构造器:
public CartItem(Book book, Integer buyCount, User userBean) {
    this.book = book;
    this.buyCount = buyCount;
    this.userBean = userBean;
}

7.2.6 错误排查

  1. script 不能用单标签,只能用双标签!
  2. 将老师的项目粘贴过来的话,会打不开 URL,IDEA启动项目报错:Cannot open URL.Please check this URL is correct,参考文献:IDEA启动项目报错:Cannot open URL.Please check this URL is correct,原因是过滤器部分的设置,先将那个包删掉。
  3. java.lang.NoSuchMethodException: java.lang.Double.<init>(java.lang.Integer);发现是在 BaseDAO 中判断是否 isNotMyType 的时候,没有将 Double 类型设置上去,所以它默认会去找 Double 类型的构造器,要把它封装成一个 Double 对象,将这句话添加上去。

BaseDAO 修改:

private static boolean isNotMyType(String typeName) {
    return "java.lang.Integer".equals(typeName)
            || "java.lang.String".equals(typeName)
            || "java.util.Date".equals(typeName)
            || "java.sql.Date".equals(typeName)
            || "java.time.LocalDateTime".equals(typeName)
            || "java.lang.Double".equals(typeName);
}

8、登录成功之后首页上显示欢迎语和购物车数量

  1. 购物车项在用户登陆之后进行绑定,而不是在 index 展示页面进行购物车绑定,万一人家想浏览你的首页但是还没注册登录呢。

8.1 修改 index.html 界面

  1. 判断如果没有登陆显示某一个登录块:th:if="${session.currUser==null}"
  2. 如果已经登陆显示欢迎xxx已经登陆:th:text="${session.currUser.getUname()}"
  3. 购物车数量改成真实的:th:text="${session.currUser.cart.getTotalCount()}"
<div class="topbar-right" th:if="${session.currUser==null}">
    <a th:href="@{/page.do?operate=login&page=user/login}" class="login">登录</a>
    <a th:href="@{/page.do?operate=login&page=user/regist}" class="register">注册</a>
    <a href="cart/cart.html" class="cart iconfont icon-gouwuche">
        购物车
        <div class="cart-num">3</div>
    </a>
    <a href="manager/book_manager.html" class="admin">后台管理</a>
</div>
<div class="topbar-right" th:unless="${session.currUser==null}">
    <!--登录后风格-->
    <span>欢迎你<b th:text="${session.currUser.getUname()}">张总</b></span>
    <a href="#" class="register">注销</a>
    <a th:href="@{/page.do(operate='page',page='cart/cart')}" class="cart iconfont icon-gouwuche">
        购物车
        <div class="cart-num" th:text="${session.currUser.cart.totalCount}">3</div>
    </a>
    <a href="./pages/manager/book_manager.html" class="admin">后台管理</a>
</div>

解释:

  • 这里展示购物车数量的时候,既可以用 th:text="${session.currUser.cart.getTotalCount()}",也可以用 th:text="${session.currUser.cart.totalCount}",因为 thymeleaf 作为一个后端的组件,在调用这个属性的时候会自动的调用其中给的 get 方法。

8.2 CartController 控制器

  1. 加载当前用户的购物车信息:index方法
//加载当前用户的购物车信息
public String index(HttpSession session){
    User user =(User)session.getAttribute("currUser");
    Cart cart = cartItemService.getCart(user);
    user.setCart(cart);
    session.setAttribute("currUser",user);
    return "cart/cart";
}

解释:

  • 我们先获取当前登录用户信息;
  • 然后调用 CartItemService 层的方法获取到该用户关联的购物车信息(从数据库中获取数据);
  • 并且将购物车信息 set 进用户的 cart 属性中,所以我们要在 user 类中新增 cart 属性;
  • 之后再将用户保存到 session 作用域中;最后进行视图渲染即可更新购物车信息。

8.3 UserController 类

  1. 登录方法中,登陆成功之后,获取用户关联的购物车:cartItemService.getCart(user),因此用户要有一个购物车;
  2. user 新增属性:private Cart cart ,并且给它设置 get/set 方法。
//用户登陆验证
public String login(String uname , String pwd , HttpSession session){

    User user = userService.login(uname, pwd);
    if(user!=null){
        Cart cart = cartItemService.getCart(user);
        user.setCart(cart);
        session.setAttribute("currUser",user);
        return "redirect:book.do";
    }
    //如果没有登陆成功,重新回到login登录页面
    return "user/login";
}

8.4 CartItemService 层

  1. 加载特定用户的购物车信息:getCart(User user)方法,依赖于用户信息的;
@Override
public Cart getCart(User user) {
    //拿到购物车项,我们需要将每一项放到一个map中,然后封装到购物车中
    List<CartItem> cartItemList = cartItemDAO.getCartItemList(user);
    Map<Integer, CartItem> cartItemMap = new HashMap<>();
    for (CartItem cartItem : cartItemList) {
        cartItemMap.put(cartItem.getBook().getId(), cartItem);
    }
    //如果当前用户的购物车项为空,那么我们cart也不为空,只不过map为空
    Cart cart = new Cart();
    cart.setCartItemMap(cartItemMap);

    return cart;
}

解释:

  • 拿到购物车项,我们需要将每一项放到一个 map 中,然后封装到购物车中;
  • 如果当前用户的购物车项为空,那么我们新建一个,保证用户的 cart 不为空,只不过 map 为空。

8.5 CartItemDAO 层

  1. 获取当前登录用户的所有购物车项:getCartItemList(User user)方法
@Override
public List<CartItem> getCartItemList(User user) {
    return executeQuery("select * from t_cart_item where userBean = ? " , user.getId());
}

9、展示购物车详情

9.1 CartController 控制器

  1. 新建 index 方法,加载当前用户的购物车信息,调用 CartItemService 层的 getCart(User user)方法,我们之前已经创建过这个方法了;
  2. 加载完购物车信息之后,跳转到 cart.html 这个界面,注意这里不是静态页面,要经过 thymeleaf 渲染;
//加载当前用户的购物车信息
public String index(HttpSession session){
    User user =(User)session.getAttribute("currUser");
    Cart cart = cartItemService.getCart(user);
    user.setCart(cart);
    session.setAttribute("currUser",user);
    return "cart/cart";
}

9.2 修改 cart.html 页面

  1. 迭代显示购物车项:th:each="cartItem : ${session.currUser.cart.cartItemMap.values()}",表示从当前用户绑定的购物车中的购物车项 map 中获取它的 values 即那些 cartItem 项;
  2. 显示购物车项的图片:th:src="@{|/static/uploads/${cartItem.book.bookImg}|}"
  3. 书名:th:text="${cartItem.book.bookName}"
  4. 购物车中该本书的数量:th:value="${cartItem.buyCount}"
  5. 共多少件商品(所有书的数目的总和):th:text="${session.currUser.cart.totalBookCount}"
  6. 总金额:th:text="${session.currUser.cart.totalMoney}"
<table>
    <tbody>
    <tr th:each="cartItem : ${session.currUser.cart.cartItemMap.values()}">
        <td>
            <img th:src="@{|/static/uploads/${cartItem.book.bookImg}|}" alt=""/>
        </td>
        <td th:text="${cartItem.book.bookName}">活着</td>
        <td>
            <span class="count" >-</span>
            <input class="count-num" type="text" value="1" th:value="${cartItem.buyCount}"/>
            <span class="count">+</span>
        </td>
        <td th:text="${cartItem.book.price}">36.8</td>
        <td><a href="">删除</a></td>
    </tr>
    </tbody>
</table>
<div class="footer">
    <div class="footer-right">
        <div><span th:text="${session.currUser.cart.totalBookCount}">3</span>件商品</div>
        <div class="total-price">总金额<span th:text="${session.currUser.cart.totalMoney}">99.9</span></div>
        <a class="pay" th:href="@{/order.do(operate='checkout')}">去结账</a>
    </div>
</div>

9.3 错误排查

  1. totalMoney 查询不到,因为 CartItemService 层在 Cart getCart(User user); 这个方法的时候,调用了 cartItemDAO.getCartItemList(user); 这个方法的时候,我们只从 t_cart_item 这个表中获取到了所有 book 的 Id,还没有将 cartItem 和 book 关联起来,也就是还没有将 book 属性 set 进 cartItem 这一项中。
    在这里插入图片描述

解决:

  • 修改 CartItemService 层:
    1. 获取指定用户的所有购物车项列表(需要注意的是,这个方法内部查询的时候,会将book的详细信息设置进去)
    2. 调用 bookService 层的获取书的详细信息的方法,然后将得到的书 set 进去;
@Override
public List<CartItem> getCartItemList(User user) {
    List<CartItem> cartItemList = cartItemDAO.getCartItemList(user);
    for (CartItem cartItem : cartItemList) {
        Book book = bookService.getBook(cartItem.getBook().getId());
        cartItem.setBook(book);
    }
    return cartItemList;
}
  • 修改 bookService 层:
    1. 实现根据 Id 获取每一本书的详细信息的方法:
@Override
public Book getBook(Integer id) {
    return bookDAO.getBook(id);
}
Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐