# day11 书城项目第四阶段

# 第一章 添加商品进购物车

# 1 创建购物车模型

images

# 1.1 购物项详情类

images

public class CartItem {

package com.atguigu.bean;

/**
 * 包名:com.atguigu.bean
 *
 * @author Leevi
 * 日期2021-05-17  09:06
 */
public class CartItem {
    private Integer bookId;
    private String bookName;
    private String imgPath;
    /**
     * 商品的单价
     */
    private Double price;
    private Integer count;
    /**
     * 购物项的金额
     */
    private Double amount;

    public CartItem() {
    }

    @Override
    public String toString() {
        return "CartItem{" +
                "bookId=" + bookId +
                ", bookName='" + bookName + '\'' +
                ", imgPath='" + imgPath + '\'' +
                ", price=" + price +
                ", count=" + count +
                ", amount=" + amount +
                '}';
    }

    public CartItem(Integer bookId, String bookName, String imgPath, Double price, Integer count, Double amount) {
        this.bookId = bookId;
        this.bookName = bookName;
        this.imgPath = imgPath;
        this.price = price;
        this.count = count;
        this.amount = amount;
    }

    public Integer getBookId() {
        return bookId;
    }

    public void setBookId(Integer bookId) {
        this.bookId = bookId;
    }

    public String getBookName() {
        return bookName;
    }

    public void setBookName(String bookName) {
        this.bookName = bookName;
    }

    public String getImgPath() {
        return imgPath;
    }

    public void setImgPath(String imgPath) {
        this.imgPath = imgPath;
    }

    public Double getPrice() {
        return price;
    }

    public void setPrice(Double price) {
        this.price = price;
    }

    public Integer getCount() {
        return count;
    }

    public void setCount(Integer count) {
        this.count = count;
    }

    /**
     * 这个方法获取总价:要通过计算才能获取
     * @return
     */
    public Double getAmount() {
        return this.price*this.count;
    }

    public void setAmount(Double amount) {
        this.amount = amount;
    }

    /**
     * 数量+1
     */
    public void countIncrease(){
        this.count ++;
    }

    /**
     * 数量-1
     */
    public void countDecrease(){
        this.count --;
    }
}

# 1.2 购物车类:Cart

package com.atguigu.bean;

import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Map;

/**
 * 包名:com.atguigu.bean
 *
 * @author Leevi
 * 日期2021-05-17  09:09
 */
public class Cart {
    /**
     * 存储购物车中的购物项
     * 以购物项中的书的ID作为key,以购物项作为value
     */
    private Map<Integer,CartItem> cartItemMap = new HashMap<>();

    /**
     * 将书添加进购物车
     * @param book
     */
    public void addBookToCart(Book book){
        //1. 判断购物车中是否已经有这件商品
        if (cartItemMap.containsKey(book.getBookId())) {
            //2. 如果购物车中已经有这件商品了,那么就对其数量+1
            itemCountIncrease(book.getBookId());
        }else {
            //3. 如果购物车中还没有这件商品,那么我们就新增一个购物项
            //第一次添加商品进购物车,那么数量count肯定是1,那么总价就是单价
            CartItem cartItem = new CartItem(book.getBookId(),book.getBookName(),book.getImgPath(),book.getPrice(),1,book.getPrice());
            cartItemMap.put(cartItem.getBookId(),cartItem);
        }
    }

    @Override
    public String toString() {
        return "Cart{" +
            "cartItemMap=" + cartItemMap +
            '}';
    }

    /**
     * 显示购物车信息,为了在Thymeleaf中便于使用OGNL表达式获取购物车的信息,
     * 我们的方法名要叫作getCartItemMap(),那么我们在Thymeleaf中就可以使用 .cartItemMap获取
     * @return
     */
    public Map<Integer,CartItem> getCartItemMap(){
        return cartItemMap;
    }

    /**
     * 将购物车中的某个购物项的数量+1
     * @param bookId
     */
    public void itemCountIncrease(Integer bookId){
        cartItemMap.get(bookId).countIncrease();
    }

    /**
     * 将购物车中的某个购物项的数量-1
     * @param bookId
     */
    public void itemCountDecrease(Integer bookId){
        CartItem cartItem = cartItemMap.get(bookId);
        //1.先将当前购物项的数量-1
        cartItem.countDecrease();
        //2.判断当前购物项的count是否等于0
        if (cartItem.getCount() == 0) {
            //说明要将当前购物项从购物车中移除
            removeCartItem(bookId);
        }
    }

    /**
     * 从购物车中移除购物项
     * @param bookId
     */
    public void removeCartItem(Integer bookId){
        cartItemMap.remove(bookId);
    }

    /**
     * 修改某个购物项的数量
     * @param bookId
     */
    public void updateItemCount(Integer bookId,Integer newCount){
        cartItemMap.get(bookId).setCount(newCount);
    }

    /**
     * 获取购物车中的购物项中的商品的总数
     * 就是将所有购物项的count进行累加
     * @return
     */
    public Integer getTotalCount(){
        //遍历出每一个CartItem的count然后累加
        //方式一: 采用JDK1.8的新特性
        /*AtomicReference<Integer> totalCount = new AtomicReference<>(0);
        cartItemMap.forEach((k,cartItem) -> {
            totalCount.updateAndGet(v -> v + cartItem.getCount());
        });

        return totalCount.get();*/

        //方式二: 使用原生的entrySet遍历Map
        Integer totalCount = 0;
        for (Map.Entry<Integer, CartItem> cartItemEntry : cartItemMap.entrySet()) {
            totalCount += cartItemEntry.getValue().getCount();
        }
        return totalCount;
    }

    /**
     * 获取总金额
     * 就是遍历出每个购物项的金额再累加
     * @return
     */
    public Double getTotalAmount(){
        //解决精度问题的核心: 就是将要进行运算的数据转成BigDecimal类型之后再计算
        //声明一个总金额
        Double totalAmount = 0.0;
        for (Map.Entry<Integer, CartItem> cartItemEntry : cartItemMap.entrySet()) {
            //cartItemEntry.getValue().getAmount()是获取每一个购物项的金额

            //使用总金额加上遍历出来的购物项的金额
            totalAmount += cartItemEntry.getValue().getAmount();
        }
        return totalAmount;
    }
}

# 2. 目标

在首页点击『加入购物车』将该条图书加入『购物车』

# 3. 思路

首页→加入购物车→CartServlet.addCartItem()→执行添加操作→回到首页

# 4. 代码实现

# 4.1 创建CartServlet

images

public class CartServlet extends ModelBaseServlet {
    private BookService bookService = new BookServiceImpl();
    /**
     * 添加商品进购物车
     * @param request
     * @param response
     */
    public void addCartItem(HttpServletRequest request, HttpServletResponse response){
        try {
            //1. 获取请求参数id的值
            Integer id = Integer.valueOf(request.getParameter("id"));
            //2. 调用bookService的方法根据id查询book信息
            Book book = bookService.getBookById(id);
            //3. 尝试从会话域中获取购物车
            HttpSession session = request.getSession();
            Cart cart = (Cart) session.getAttribute("cart");
            //4. 判断之前是否已经添加过购物车了
            if (cart == null) {
                //说明这是第一次添加购物车
                //那么就要新创建一个购物车对象
                cart = new Cart();
                //然后将当前book加入到这个购物车
                cart.addBookToCart(book);
                //然后将cart存入到session
                session.setAttribute("cart",cart);
            }else {
                //说明不是第一次添加购物车
                //那么就直接用之前的购物车,添加当前book就行
                cart.addBookToCart(book);
            }
			
            //跳转到首页
            response.sendRedirect(request.getContextPath()+"/index.html");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

# 4.2 index.html页面

购物车数量显示:

<!--登录前的风格-->
<div class="topbar-right" th:if="${session.loginUser == null}">
    <a href="user?method=toLoginPage" class="login">登录</a>
    <a href="user?method=toRegisterPage" class="register">注册</a>
    <a
       href="#"
       class="cart iconfont icon-gouwuche
              "
       >
        购物车
        <div class="cart-num" th:if="${session.cart != null}" th:text="${session.cart.totalCount}">3</div>
    </a>
    <a href="admin?method=toManagerPage" class="admin">后台管理</a>
</div>
<!--登录后风格-->
<div class="topbar-right" th:unless="${session.loginUser == null}">
    <span>欢迎你<b th:text="${session.loginUser.username}">张总</b></span>
    <a href="user?method=logout" class="register">注销</a>
    <a
       href="#"
       class="cart iconfont icon-gouwuche
              ">
        购物车
        <div class="cart-num" th:if="${session.cart != null}" th:text="${session.cart.totalCount}">3</div>
    </a>
    <a href="admin?method=toManagerPage" class="admin">后台管理</a>
</div>

加入购物车:

<a th:href="@{/cart(method='addCartItem',id=${book.bookId})}">加入购物车</a>

# 4.3 CSS样式

images

.books-list .list-content .list-item a {
  display: block;
  width: 80px;
  height: 30px;
  border: none;
  line-height: 30px;
  background-color: #39987c;
  margin-top: 5px;
  outline: none;
  color: #fff;
  cursor:pointer;
  font-size:12px;
  text-align:center;
}

如果修改完成后页面效果没有改变,可以使用Ctrl+F5强制刷新。

# 第二章 显示购物车页面

# 1目标

把购物车信息在专门的页面显示出来

# 2思路

首页→购物车超链接→CartServlet.showCart()→cart.html

# 3代码实现

# 3.1 购物车超链接

登录状态和未登录状态

<div class="topbar-right" th:if="${session.loginUser == null}">
    <a href="user?method=toLoginPage" class="login">登录</a>
    <a href="user?method=toRegisterPage" class="register">注册</a>
    <a
       href="cart?method=toCartPage"
       class="cart iconfont icon-gouwuche
              "
       >
        购物车
        <div class="cart-num" th:if="${session.cart != null}" th:text="${session.cart.totalCount}">3</div>
    </a>
    <a href="admin?method=toManagerPage" class="admin">后台管理</a>
</div>
<!--登录后风格-->
<div class="topbar-right" th:unless="${session.loginUser == null}">
    <span>欢迎你<b th:text="${session.loginUser.username}">张总</b></span>
    <a href="user?method=logout" class="register">注销</a>
    <a
       href="cart?method=toCartPage"
       class="cart iconfont icon-gouwuche
              ">
        购物车
        <div class="cart-num" th:if="${session.cart != null}" th:text="${session.cart.totalCount}">3</div>
    </a>
    <a href="admin?method=toManagerPage" class="admin">后台管理</a>
</div>

# 3.2 CartServlet

/**
     * 跳转到显示购物车列表的页面
     * @param request
     * @param response
     */
public void toCartPage(HttpServletRequest request,HttpServletResponse response) throws IOException {
    processTemplate("cart/cart",request,response);
}

# 3.3 cart.html

<table>
    <thead>
        <tr>
            <th>图片</th>
            <th>商品名称</th>

            <th>数量</th>
            <th>单价</th>
            <th>金额</th>
            <th>操作</th>
        </tr>
    </thead>
    <tbody th:if="${session.cart == null}">
        <tr>
            <td th:colspan="6">
                <a href="index.html">购物车空空如也,请抓紧购物吧!!!!</a>
            </td>
        </tr>
    </tbody>
    <tbody th:if="${session.cart != null}">
        <tr th:each="cartItemEntry : ${session.cart.cartItemMap}">
            <td>
                <img th:src="${cartItemEntry.value.imgPath}" alt="" />
            </td>
            <td th:text="${cartItemEntry.value.bookName}">活着</td>
            <td>
                <a class="count" th:href="#">-</a>
                <input class="count-num" type="text" th:value="${cartItemEntry.value.count}" value="1" />
                <input type="hidden" th:value="${cartItemEntry.value.bookId}"/>
                <a class="count" th:href="#">+</a>
            </td>
            <td th:text="${cartItemEntry.value.price}">36.8</td>
            <td th:text="${cartItemEntry.value.amount}">36.8</td>
            <td><a th:href="#">删除</a></td>
        </tr>
    </tbody>
</table>
<div>
    <div class="footer" th:if="${session.cart != null}">
        <div class="footer-left">
            <a href="#" class="clear-cart">清空购物车</a>
            <a href="#">继续购物</a>
        </div>
        <div class="footer-right">
            <div><span th:text="${session.cart.totalCount}">3</span>件商品</div>
            <div class="total-price">总金额<span th:text="${session.cart.totalAmount}">99.9</span></div>
            <a class="pay" href="pages/cart/checkout.html">去结账</a>
        </div>
    </div>
</div>

# 第三章 清空购物车

# 1 目标

当用户确定点击清空购物车,将Session域中的Cart对象移除。

# 2 思路

cart.html→清空购物车超链接→绑定单击响应函数→confirm()确认→确定→CartServlet.clearCart()→从Session域移除Cart对象

# 3 代码实现

# 3.1 清空购物车超链接

<!--在head里面引入vue-->
<script src="static/script/vue.js" type="text/javascript" charset="utf-8"></script>

<!--修改清空购物车的a标签的访问路径,并且绑定点击事件-->
<a href="cart?method=cleanCart" @click="cleanCart" class="clear-cart">清空购物车</a>

<script>
    /**
    * 注意设置table的外层的div的id为app
    * <div class="list" id="app">
    *  <div class="w">
    *    <table>
    */
    var vue = new Vue({
        "el":"#app",
        "methods":{
            cleanCart(){
                //弹出确认框
                if (!confirm("你确定要清空购物车吗?")) {
                    //不需要清空购物车,阻止a标签跳转
                    event.preventDefault()
                }
            }
        }
    });
</script>

# 3.2 CartServlet.cleanCart()

/**
     * 清空购物车
     * @param request
     * @param response
     * @throws IOException
     */
public void cleanCart(HttpServletRequest request,HttpServletResponse response) throws IOException {
    //就是移除掉session中的cart就行了
    request.getSession().removeAttribute("cart");
    //跳转到购物车展示页面
    processTemplate("cart/cart",request,response);
}

# 第四章 减号

# 1. 目标

  • 在大于1的数值基础上-1:执行-1的逻辑
  • 在1的基础上-1:执行删除item的逻辑

# 2. 思路

减号a标签绑定单击响应函数→获取文本框中的数据:当前数量→检查数量是否等于1,如果它等于1的话,则弹出一个确认框,询问其是否想删除?如果不想删除,则阻止a标签的事件

# 3. 后端代码

CartServlet.countDecrease()方法

/**
     * 购物车中某个一个购物项的数量-1
     * @param request
     * @param response
     * @throws IOException
     */
public void countDecrease(HttpServletRequest request,HttpServletResponse response) throws IOException {
    //1. 获取到要-1的书的id
    Integer id = Integer.valueOf(request.getParameter("id"));
    //2. 从session中获取购物车信息
    Cart cart = (Cart) request.getSession().getAttribute("cart");
    //3. 调用购物车的-1方法
    cart.itemCountDecrease(id);
    //4. 跳转回到购物车页面
    processTemplate("cart/cart",request,response);
}

# 4. 前端代码

HTML代码给“减号”设置访问路径以及绑定点击事件:

需要注意:只能使用onclick绑定不能使用Vue绑定(通过实践得出的)

<a onclick="cartItemCountDecrease()" class="count" th:href="@{/cart(method='countDecrease',id=${cartItemEntry.value.bookId})}">-</a>

js代码:

<script>
function cartItemCountDecrease(){
    //判断:输入框的内容是否是1
    //输入框就是当前点击的a标签的下一个兄弟: event.target.nextElementSubling
    if (event.target.nextElementSibling.value == '1') {
        var bookName = event.target.parentElement.parentElement.getElementsByTagName("td")[1].innerText;
        //则弹出提示框问你是否要删除
        if (!confirm(bookName + "的数量已经是1了,你确定还要减少吗?")) {
            //阻止事件发生
            event.preventDefault()
        }
    }
}
.....
</script>

# 第四章 删除

# 1. 目标

点击删除超链接后,把对应的CartItem从Cart中删除

# 2. 思路

cart.html→删除超链接→CartServlet.removeCartItem()→回到cart.html

需要注意:删除之前要确认

# 3. 代码实现

# 3.1 后端代码

CartServlet.removeCartItem()

/**
     * 删除购物项
     * @param request
     * @param response
     * @throws IOException
     */
public void removeCartItem(HttpServletRequest request,HttpServletResponse response) throws IOException {
    //1. 获取要删除的购物项的书的id
    Integer id = Integer.valueOf(request.getParameter("id"));
    //2.从session中获取购物车
    Cart cart = (Cart) request.getSession().getAttribute("cart");
    //3. 调用cart的删除购物项的方法
    cart.removeCartItem(id);
    //4. 跳转回到购物车页面
    processTemplate("cart/cart",request,response);
}

# 3.2 前端代码

HTML代码:

<td><a th:href="@{/cart(method='removeCartItem',id=${cartItemEntry.value.bookId})}" @click="deleteCartItem">删除</a></td>

Vue代码:

var vue = new Vue({
    "el":"#app",
    "methods":{
       
        deleteCartItem(){
            //想办法使用JavaScript获取要删除的购物项的书名
            //event.target就表示获取当前事件的标签
            var bookName = event.target.parentElement.parentElement.getElementsByTagName("td")[1].innerText;
            if (!confirm("你确定要删除这个"+bookName+"吗?")) {
                event.preventDefault()
            }
        }
    }
});

# 第五章 文本框修改

# 1. 目标

用户在文本框输入新数据后,根据用户输入在Session中的Cart中修改CartItem中的count

# 2. 思路

cart.html→前端数据校验→CartServlet.updateCartItemCount()→回到cart.html

# 3. 代码实现

# 3.1 后端代码

CartServlet.updateCartItemCount()

/**
     * 修改购物项的数量
     * @param request
     * @param response
     * @throws IOException
     */
public void updateCartItemCount(HttpServletRequest request,HttpServletResponse response) throws IOException {
    //1. 获取请求参数:id,newCount
    Integer id = Integer.valueOf(request.getParameter("id"));
    Integer newCount = Integer.valueOf(request.getParameter("newCount"));
    //2. 从session中获取购物车信息
    Cart cart = (Cart) request.getSession().getAttribute("cart");
    //3. 调用cart中跟新数量的方法
    cart.updateItemCount(id,newCount);

    //4. 跳转到购物车页面
    processTemplate("cart/cart",request,response);
}

# 3.2 前端代码

HTML代码:

<input @change="updateCartItemCount" class="count-num" type="text" th:value="${cartItemEntry.value.count}" value="1" />
<input type="hidden" th:value="${cartItemEntry.value.bookId}"/>

Vue代码:

var vue = new Vue({
    "el":"#app",
    "methods":{
        updateCartItemCount(){
            //获取bookId
            var bookId = event.target.nextElementSibling.value;
            //获取newCount
            var newCount = event.target.value;

            //校验newCount的格式是否正确
            var reg = /^[1-9][0-9]*$/

            if (reg.test(newCount)) {
                //发送请求携带请求参数
                location.href = "cart?method=updateCartItemCount&id="+bookId+"&newCount="+newCount

            }else {
                alert("请输入正确的数量")
            }
        }
    }
});

# 第六章 加号

# 1. 目标

告诉Servlet将Session域中Cart对象里面对应的CartItem执行count+1操作

# 2. 思路

加号span绑定单击响应函数→获取文本框中的数据:当前数量→CartServlet.countIncrease()→回到cart.html

# 3. 代码实现

# 3.1 后端代码

CartServlet.countIncrease()

/**
     * 购物车中某个一个购物项的数量+1
     * @param request
     * @param response
     * @throws IOException
     */
public void countIncrease(HttpServletRequest request,HttpServletResponse response) throws IOException {
    //1. 获取到要-+1的书的id
    Integer id = Integer.valueOf(request.getParameter("id"));
    //2. 从session中获取购物车信息
    Cart cart = (Cart) request.getSession().getAttribute("cart");
    //3. 调用购物车的+1方法
    cart.itemCountIncrease(id);
    //4. 跳转回到购物车页面
    processTemplate("cart/cart",request,response);
}

# 3.2 前端代码

HTML代码:

<a class="count" th:href="@{/cart(method='countIncrease',id=${cartItemEntry.value.bookId})}">+</a>

# 第七章 Double数据运算过程中精度调整

# 1. 问题现象

images

# 2. 解决方案

  • 使用BigDecimal类型来进行Double类型数据运算
  • 创建BigDecimal类型对象时将Double类型的数据转换为字符串

Cart类:

/**
     * 获取总金额
     * @return
     */
public Double getTotalAmount(){
    //解决精度问题的核心: 就是将要进行运算的数据转成BigDecimal类型之后再计算
    //声明一个总金额
    BigDecimal bigDecimalTotalAmount = new BigDecimal("0.0");
    for (Map.Entry<Integer, CartItem> cartItemEntry : cartItemMap.entrySet()) {
        //cartItemEntry.getValue().getAmount()是获取每一个购物项的金额

        //使用总金额加上遍历出来的购物项的金额
        bigDecimalTotalAmount = bigDecimalTotalAmount.add(new BigDecimal(cartItemEntry.getValue().getAmount() + ""));
    }
    return bigDecimalTotalAmount.doubleValue();
}

CartItem类:

/**
     * 这个方法获取总价:要通过计算才能获取
     * @return
     */
public Double getAmount() {
    //1. 将price和count封装成BigDecimal类型
    BigDecimal bigDecimalPrice = new BigDecimal(price + "");
    BigDecimal bigDecimalCount = new BigDecimal(count + "");

    //2. 使用bigDecimal的方法进行乘法
    return bigDecimalCount.multiply(bigDecimalPrice).doubleValue();
}