我们的购物车采用的是在服务端,即:使用session来存储。这样做的
缺点
:无法永久存储,当服务端关闭的时候,会销毁。
优点
:不用在本地数据库建相应的表。
在开始之前我们得明白一些概念:在Java里每个实体都对应一个Java类。所以我们这里的购物车
就是一个类,每个用户的购物车就是一个具体的对象
。而购物车里边的每个商品就是一个购物项
。
我这里的购物车类是Car
,购物项类ShopCarItem
。
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page import="java.util.HashMap" %>
<%@ page import="com.entity.ShopCarItem" %>
<%@ page import="java.util.List" %>
<%@ page import="com.entity.User" %>
<%@ page import="com.myUtil.ProcessUtil" %>
<%@ page import="com.entity.Cart" %><%--
Created by IntelliJ IDEA.
User: huawei
Date: 2022/10/18
Time: 13:47
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>购物车</title>
<script type="text/javascript" src="script/jquery-3.6.0.min.js"></script>
<!-- 新 Bootstrap5 核心 CSS 文件 -->
<link rel="stylesheet" href="https://cdn.staticfile.org/twitter-bootstrap/5.1.1/css/bootstrap.min.css">
<!-- popper.min.js 用于弹窗、提示、下拉菜单 -->
<script src="https://cdn.staticfile.org/popper.js/2.9.3/umd/popper.min.js"></script>
<!-- 最新的 Bootstrap5 核心 JavaScript 文件 -->
<script src="https://cdn.staticfile.org/twitter-bootstrap/5.1.1/js/bootstrap.min.js"></script>
<script type="text/javascript">
$(function () {
// 清空购物车
$("#removeAll").click(
function () {
if (confirm("是否清空购物车") == true) {
$.post(
"/MyProject/cartProcessServlet",
{
method: "removeAll"
},
function (data) {
if ("success" == data){
location.reload(true);
}
},
"json"
)
} else {
return false;
}
}
)
// 数量加按钮
$("button[id^='numSum_']").click(
function (e) {
var goodId = e.target.id;
// 获取对应的数量值
var $number = $(document.getElementById("number_" + goodId.split("_")[1]));
$number.val(parseInt($number.val()) + 1);
var val = $number.val();
// 将修改后的值写入数据库
sendAjax(goodId,val);
}
)
// 数量减按钮
$("button[id^='numSub_']").click(
function (e) {
var goodId = e.target.id;
// 获取对应的数量值
var $number = $(document.getElementById("number_" + goodId.split("_")[1]));
if ((parseInt($number.val()) -1) >= 1){
$number.val((parseInt($number.val()) -1));
sendAjax(goodId,$number.val());
}
}
)
// 数值框内容变化监听
$("input[id^='number_']").blur(
function(e){
var id = e.target.id;
var val = $(document.getElementById("number_" + e.target.id.split("_")[1])).val();
if (!((/^[1-9]*[1-9][0-9]*$/).test(val))){ //如果不是是正整数
$(document.getElementById("number_" + e.target.id.split("_")[1])).val(1)
}
sendAjax(id,val);
})
// 发送Ajax请求(用于更改购物项信息)
var sendAjax = function (goodId,count) {
$.post(
"/MyProject/cartProcessServlet",
{
method: "updateShopCartItem",
goodId: goodId,
number: count
},
function (data) {
if ("success" == data){
location.reload(true);
}
},
"json"
)
}
// 删除购物项
$("button[id^='delete_']").click(
function (e) {
var goodId = e.target.id.split("_")[1];
$.post(
"/MyProject/cartProcessServlet",
{
method: "removeShopItem",
goodId: goodId
},
function (data) {
if ("success" == data){
location.reload(true);
}
},
"json"
)
}
)
// 结算
$("#settlement").click(
function () {
$.post(
"/MyProject/orderProcessServlet",
{
method: ""
},
function (data) {
if ("success" == data){
alert("订单生成成功!");
location.reload(true);
}
},
"json"
)
}
)
})
</script>
</head>
<body>
<%
session.setAttribute("totalPrice",0.0);
%>
<%--头部导航栏--%>
<nav class="navbar-expand-lg navbar navbar-dark bg-primary">
<div class="container-fluid ">
<a class="navbar-brand" href="#">购物车</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNavDropdown" aria-controls="navbarNavDropdown" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNavDropdown">
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link active" aria-current="page" href="/MyProject/index.jsp">Home</a>
</li>
</ul>
</div>
</div>
</nav>
<c:if test="${empty sessionScope.cart.items}">
<div class="container" >
<div class="card position-relative" style="margin: 50px;height: 280px;background: #ffffff url(img/CartBackground.png) no-repeat; background-position: center left;">
<div class="position-absolute top-50 start-50 translate-middle">
<h7>
您的购物车还是空的,赶紧行动吧!您可以:
</h7><br>
<a class="btn btn-primary btn-lg" href="/MyProject/index.jsp">购物</a>
</div>
</div>
</div>
</c:if>
<c:if test="${!empty sessionScope.cart.items}">
<div class="container">
<div class="card">
<table class="table table-hover text-center">
<tr>
<td>商品</td>
<td>单价</td>
<td>商品数量</td>
<td>总价</td>
<td>操作</td>
</tr>
<c:forEach items="${sessionScope.cart.items}" var="shopCarItem">
<tr style="vertical-align: middle !important;text-align: center;">
<td>
<img style="width: 100px;height: 100px" src="${shopCarItem.value.img}"/>
<span>${shopCarItem.value.goodsName}</span>
</td>
<td>${shopCarItem.value.price}</td>
<td class="col-lg-2">
<div class="input-group">
<span class="input-group-btn">
<button class="btn btn-default" type="button" id="numSub_${shopCarItem.value.goodsId}">-</button>
</span>
<input type="text" class="form-control text-center" id="number_${shopCarItem.value.goodsId}" value="${shopCarItem.value.number}">
<span class="input-group-btn">
<button class="btn btn-default" type="button" id="numSum_${shopCarItem.value.goodsId}">+</button>
</span>
</div>
</td>
<td>${shopCarItem.value.totalPrice}</td>
<%--计算总价格--%>
<c:set scope="session" value="${sessionScope.totalPrice + shopCarItem.value.totalPrice}" var="totalPrice"></c:set>
<td><button class="btn btn-danger" id="delete_${shopCarItem.value.goodsId}">删除</button></td>
</tr>
</c:forEach>
</table>
<div class="row justify-content-between">
<div class="col-4 m-auto">
<a class="btn btn-primary btn-lg" href="/MyProject/index.jsp">
继续购物
</a>
</div>
<div class="col-4">
<button type="button" class="btn btn-warning btn-lg" id="removeAll">清空购物车</button>
<span>
<span class="fs-3">
合计
</span>
<span class="fs-3 fw-bold" id="total" style="color: red">
${sessionScope.totalPrice}
<button type="button" class="btn btn-dark " id="settlement">结算</button>
</span>
</span>
</div>
</div>
</div>
</div>
</c:if>
<div class="container">
<%--footer--%>
<div class="row align-items-end">
<footer class="py-5" >
<div class="row">
<div class="col-2">
<h5>Section</h5>
<ul class="nav flex-column">
<li class="nav-item mb-2"><a href="#" class="nav-link p-0 text-muted">Home</a></li>
<li class="nav-item mb-2"><a href="#" class="nav-link p-0 text-muted">Features</a></li>
<li class="nav-item mb-2"><a href="#" class="nav-link p-0 text-muted">Pricing</a></li>
<li class="nav-item mb-2"><a href="#" class="nav-link p-0 text-muted">FAQs</a></li>
<li class="nav-item mb-2"><a href="#" class="nav-link p-0 text-muted">About</a></li>
</ul>
</div>
<div class="col-2">
<h5>Section</h5>
<ul class="nav flex-column">
<li class="nav-item mb-2"><a href="#" class="nav-link p-0 text-muted">Home</a></li>
<li class="nav-item mb-2"><a href="#" class="nav-link p-0 text-muted">Features</a></li>
<li class="nav-item mb-2"><a href="#" class="nav-link p-0 text-muted">Pricing</a></li>
<li class="nav-item mb-2"><a href="#" class="nav-link p-0 text-muted">FAQs</a></li>
<li class="nav-item mb-2"><a href="#" class="nav-link p-0 text-muted">About</a></li>
</ul>
</div>
<div class="col-2">
<h5>Section</h5>
<ul class="nav flex-column">
<li class="nav-item mb-2"><a href="#" class="nav-link p-0 text-muted">Home</a></li>
<li class="nav-item mb-2"><a href="#" class="nav-link p-0 text-muted">Features</a></li>
<li class="nav-item mb-2"><a href="#" class="nav-link p-0 text-muted">Pricing</a></li>
<li class="nav-item mb-2"><a href="#" class="nav-link p-0 text-muted">FAQs</a></li>
<li class="nav-item mb-2"><a href="#" class="nav-link p-0 text-muted">About</a></li>
</ul>
</div>
</div>
<div class="d-flex justify-content-between py-4 my-4 border-top">
<p>© 2021 Company, Inc. All rights reserved.</p>
<ul class="list-unstyled d-flex">
<li class="ms-3"><a class="link-dark" href="#"><svg class="bi" width="24" height="24"><use xlink:href="#twitter"/></svg></a></li>
<li class="ms-3"><a class="link-dark" href="#"><svg class="bi" width="24" height="24"><use xlink:href="#instagram"/></svg></a></li>
<li class="ms-3"><a class="link-dark" href="#"><svg class="bi" width="24" height="24"><use xlink:href="#facebook"/></svg></a></li>
</ul>
</div>
</footer>
</div>
</div>
</body>
</html>
这一部分,我们放到下一章节讲主页
部分的时候在详细讲,因为商品购买是在主页。
这里的参数,goodId
也就是购物像的ID
,count
是当前购物项
的数量,如图:
// 发送Ajax请求(用于更改购物项信息)
var sendAjax = function (goodId,count) {
$.post(
"/MyProject/cartProcessServlet",
{
method: "updateShopCartItem",
goodId: goodId,
number: count
},
function (data) {
if ("success" == data){
location.reload(true);
}
},
"json"
)
}
这里我们因为都是局部数据的刷新,所以用到了大量的Ajax
。废话不多说,我们上js代码解释:
// 数量减按钮
$("button[id^='numSub_']").click(
function (e) {
var goodId = e.target.id;
// 获取对应的数量值
var $number = $(document.getElementById("number_" + goodId.split("_")[1]));
if ((parseInt($number.val()) -1) >= 1){
$number.val((parseInt($number.val()) -1));
sendAjax(goodId,$number.val());
}
}
)
我们采用的思路是:获取对应购物项(每个商品是一个购物项)
的唯一ID
(这里ID
的唯一性是通过商品的ID
来确定的),然后删除购物车对象中的购物项。同样的,和上一章节留言板
中删除留言一样,这里也是通过numSub_
这样的前缀来标识这是一个“-”按钮。如下:
<span class="input-group-btn">
<button class="btn btn-default" type="button" id="numSub_${shopCarItem.value.goodsId}">
-
button>
span>
补充:我们在减去购物车中购物项的数量时,购物项的数量不能为0,后边通过输入改变
也是同样的道理。
“+”按钮和“-”,差不多
// 数量加按钮
$("button[id^='numSum_']").click(
function (e) {
var goodId = e.target.id;
// 获取对应的数量值
var $number = $(document.getElementById("number_" + goodId.split("_")[1]));
$number.val(parseInt($number.val()) + 1);
var val = $number.val();
// 将修改后的值写入数据库
sendAjax(goodId,val);
}
)
// 数值框内容变化监听
$("input[id^='number_']").blur(
function(e){
var id = e.target.id;
var val = $(document.getElementById("number_" + e.target.id.split("_")[1])).val();
if (!((/^[1-9]*[1-9][0-9]*$/).test(val))){ //如果不是是正整数
$(document.getElementById("number_" + e.target.id.split("_")[1])).val(1)
}
sendAjax(id,val);
})
到这里我们会发现,有关购物项数量的改变(“+”、“-”、“键盘修改”),count
购物项数量的获取都是通过输入框中的数据来获取的。
和前面讲的留言删除
功能的思路差不多,都是通过Ajax
将对应购物项的ID发送给后端,后端来删除对应的购物项。和留言删除
部分不同的是,这里是通过removeAll()方法
来删除Cart
对象Map
属性中的对应的购物项
。
这里使用Ajax
来向后端发送请求,后端在Controller层
来调用相应的方法,先获取session域
中的Cart购物车
对象,清空所有购物项,然后必须将处理后的Cart
对象重新放入session域
中。这里贴出相关代码:
// 清空购物车
protected void removeAll(HttpServletRequest request,HttpServletResponse response){
HttpSession session = request.getSession();
Cart cart = (Cart)session.getAttribute("cart");
if (cart!=null){
cart.removeAll();
session.setAttribute("cart",cart);
try {
response.getWriter().write(new Gson().toJson("success"));
} catch (IOException e) {
e.printStackTrace();
}
}
}
我们这里的思路就是在jsp
页面渲染的时候,我们在for-Each
循环之中,每次将每个购物项
的总价相加,最后保存到session
域中。
这里说明一下,有两个总价格。
<td>${shopCarItem.value.totalPrice}td>
<c:set scope="session" value="${sessionScope.totalPrice + shopCarItem.value.totalPrice}" var="totalPrice">
c:set>
这里我们还得在页面的开头将购物车的总价格
在session
域中的值重置为0,防止每次刷新,在原来购物车的总价格上相加。
package com.controller;
import com.entity.Cart;
import com.entity.Goods;
import com.entity.ShopCarItem;
import com.google.gson.Gson;
import com.service.GoodsService;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.HashMap;
public class CartProcessServlet extends BasicServlet {
private GoodsService goodsService = new GoodsService();
// 添加购物车
protected void addShopCartItem(HttpServletRequest request,HttpServletResponse response){
// 得到商品id
String goodId = request.getParameter("goodId").split("_")[1];
// 得到商品信息
Goods good = goodsService.getGoodById(goodId);
// 将商品信息写入购物项
ShopCarItem item =
new ShopCarItem(Integer.parseInt(goodId),good.getImg(),good.getName(),good.getPrice(),1,good.getPrice());
// 从session中获取购物车
HttpSession session = request.getSession();
Cart cart = (Cart)session.getAttribute("cart");
if (cart == null){ // 还未创建购物车
cart = new Cart();
session.setAttribute("cart",cart);
}
cart.addShopCartItem(item);
try {
response.getWriter().write(new Gson().toJson("success"));
} catch (IOException e) {
e.printStackTrace();
}
}
// 修改购物项信息
protected void updateShopCartItem(HttpServletRequest request,HttpServletResponse response){
// 得到商品id
String goodId = request.getParameter("goodId").split("_")[1];
// 得到商品数量
String number = request.getParameter("number");
// 从session中获取购物车
HttpSession session = request.getSession();
Cart cart = (Cart)session.getAttribute("cart");
ShopCarItem item = cart.getItems().get(Integer.parseInt(goodId));
// 修改数量
item.setNumber(Integer.parseInt(number));
// 修改总价格
item.setTotalPrice(item.getPrice().multiply(new BigDecimal(Integer.parseInt(number))));
// 更新购物车
cart.getItems().put(Integer.parseInt(goodId),item);
try {
response.getWriter().write(new Gson().toJson("success"));
} catch (IOException e) {
e.printStackTrace();
}
}
// 清空购物车
protected void removeAll(HttpServletRequest request,HttpServletResponse response){
HttpSession session = request.getSession();
Cart cart = (Cart)session.getAttribute("cart");
if (cart!=null){
cart.removeAll();
session.setAttribute("cart",cart);
try {
response.getWriter().write(new Gson().toJson("success"));
} catch (IOException e) {
e.printStackTrace();
}
}
}
// 根据购物项Id(商品Id)删除购物项
protected void removeShopItem(HttpServletRequest request,HttpServletResponse response){
String goodId = request.getParameter("goodId");
HttpSession session = request.getSession();
Cart cart = (Cart)session.getAttribute("cart");
if (cart!=null){
HashMap<Integer, ShopCarItem> items = cart.getItems();
items.remove(Integer.parseInt(goodId));
cart.setItems(items);
session.setAttribute("cart",cart);
try {
response.getWriter().write(new Gson().toJson("success"));
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
因为我们没有连接数据库,所以这里没有Service
和Dao
这两层。
Cart类对应购物车对象,购物车有
购物项集合
和总商品数
两个属性。
这里需要注意的是:购物项属性是一个HashMap
,它的Key是购物项的ID
(这里是将商品的ID来作为购物项ID的),Value
是对应的购物项。
除此之外还提供了:
- 添加购物项方法
- 删除购物项方法(根据其对应的Id)
- 清空购物车方法
- 获取商品总数方法
- 获得总价格方法
package com.entity;
import java.math.BigDecimal;
import java.util.HashMap;
public class Cart {
private HashMap<Integer,ShopCarItem> items = new HashMap<>();
private Integer totalCount;
public Cart() {
}
// 添加购物项
public void addShopCartItem(ShopCarItem shopCarItem){
ShopCarItem item = items.get(shopCarItem.getGoodsId());
if (item == null){ // 购物车没有相同的商品
items.put(shopCarItem.getGoodsId(),shopCarItem);
}else { // 如果购物车有相同的商品
Integer number = item.getNumber();
number++;
item.setNumber(number);
item.setTotalPrice(item.getPrice().multiply(new BigDecimal(number)));
items.put(shopCarItem.getGoodsId(),item);
}
}
// 删除特定的Id购物项
public void removeShopCartItem(String goodId){
this.items.remove(goodId);
}
// 清空购物车
public void removeAll(){
this.items.clear();
}
// 获得总价格
public BigDecimal getTotalPrice(){
BigDecimal totalPrice = new BigDecimal(0);
for (Integer integer : items.keySet()) {
ShopCarItem item = items.get(integer);
totalPrice = totalPrice.add(item.getTotalPrice());
}
return totalPrice;
}
// 获得商品的总数
public Integer getTotalCount(){
totalCount = 0;
for (Integer integer : items.keySet()) {
ShopCarItem item = this.items.get(integer);
totalCount += item.getNumber();
}
return totalCount;
}
public HashMap<Integer, ShopCarItem> getItems() {
return items;
}
public void setItems(HashMap<Integer, ShopCarItem> items) {
this.items = items;
}
}
ShopCartItem类对应购物项对象,购物项的属性有:
- 商品编号
- 商品图片
- 商品名称
- 商品单价
- 商品数量
- 购物项的总价格
package com.entity;
import java.math.BigDecimal;
/**
* 此类对应 shopcar表
*/
public class ShopCarItem {
// 商品编号
private Integer goodsId;
// 商品图片
private String img;
// 商品名称
private String goodsName;
// 单价
private BigDecimal price;
// 总量
private Integer number;
// 总价格
private BigDecimal totalPrice;
public ShopCarItem() {
}
public ShopCarItem(Integer goodsId, String img, String goodsName, BigDecimal price, Integer number, BigDecimal totalPrice) {
this.goodsId = goodsId;
this.img = img;
this.goodsName = goodsName;
this.price = price;
this.number = number;
this.totalPrice = totalPrice;
}
public Integer getGoodsId() {
return goodsId;
}
public void setGoodsId(Integer goodsId) {
this.goodsId = goodsId;
}
public String getImg() {
return img;
}
public void setImg(String img) {
this.img = img;
}
public String getGoodsName() {
return goodsName;
}
public void setGoodsName(String goodsName) {
this.goodsName = goodsName;
}
public BigDecimal getPrice() {
return price;
}
public void setPrice(BigDecimal price) {
this.price = price;
}
public Integer getNumber() {
return number;
}
public void setNumber(Integer number) {
this.number = number;
}
public BigDecimal getTotalPrice() {
return totalPrice;
}
public void setTotalPrice(BigDecimal totalPrice) {
this.totalPrice = totalPrice;
}
@Override
public String toString() {
return "ShopCarItem{" +
"goodsId=" + goodsId +
", img='" + img + '\'' +
", goodsName='" + goodsName + '\'' +
", price=" + price +
", number=" + number +
", totalPrice=" + totalPrice +
'}';
}
}
这里我们的结算功能使用了
事务
,在保证有库存的情况下生成订单,否则进行回滚
。这一块内容我们放到后边的章节来讲解。