购物车
- 🌳购物车
- 🍆显示购物车
- 🍆更改商品数量
- 🍆清空购物车&&删除商品
- 🌳生成订单
🌳购物车
需求分析
1.会员登陆后, 可以添加家居到购物车
2.完成购物车的设计和实现
3.每添加一个家居,购物车的数量+1, 并显示
程序框架图
1.新建src/com/zzw/furns/entity/CartItem.java
, CartItem-家居项模型
/**
* 购物车的一项就是某个家居数据
* @author 赵志伟
* @version 1.0
*/
@SuppressWarnings({"all"})
public class CartItem {
private Integer id;//编号
private String name;//家居名
private Integer count;//数量
private BigDecimal price;//单价
private BigDecimal totalPrice;//总价
private String imagePath;//家具图片
public CartItem() {
}
//有参构造器, getter, setter方法
}
2.Cart数据模型
3.新建src/com/zzw/furns/entity/Cart.java
, 这里默认添加的数量是1
//Cart就是购物车, 包含多个CartItem
public class Cart {
//使用HashMap来保存
private Map<Integer, CartItem> items = new HashMap<>();
public boolean isEmpty() {
return items.size() == 0;
}
//Cart表示购物车, items表示购物车明细
//添加家居[CartItem]到Cart
public void addItem(CartItem cartItem) {
CartItem item = items.get(cartItem.getId());//得到购物车里的商品项
if (item == null) {//说明当前购物车还没有这个cartItem
items.put(cartItem.getId(), cartItem);
} else {//购物车中有这个cartItem
item.setCount(item.getCount() + 1);//数量加1
//修改总价
//item.getPrice() => BigDecmal
//item.getCount() => Integer
item.setTotalPrice(item.getPrice().multiply(new BigDecimal(item.getCount())));
}
}
public Integer getTotalCount() {
Integer totalCount = 0;//购物车商品的总数量
Collection<CartItem> cartItems = items.values();
for (CartItem cartItem : cartItems) {
totalCount += cartItem.getCount();
}
return totalCount;
}
/**
* 返回购物车的总价
* @return
*/
public BigDecimal getCartTotalPrice() {
BigDecimal cartTotalPrice = new BigDecimal(0);
//遍历我们的items
Set<Integer> keys = items.keySet();
for (Integer id : keys) {
CartItem item = items.get(id);
//提醒, 一定要包add后的值, 重新赋给 cartTotalPrice, 这样才是累加.
cartTotalPrice = cartTotalPrice.add(item.getTotalPrice());
}
return cartTotalPrice;
}
//todo setter, getter方法, toString方法
}
4.测试src/com/zzw/furns/test/CartTest.java
public class CartTest {
@Test
public void addItem() {
Cart cart = new Cart();
cart.addItem(new CartItem(1, "java从入门到精通", 1, new BigDecimal(1000), new BigDecimal(1000)));
cart.addItem(new CartItem(1, "java从入门到精通", 1, new BigDecimal(1000), new BigDecimal(1000)));
cart.addItem(new CartItem(2, "数据结构与算法", 1, new BigDecimal(100), new BigDecimal(100)));
System.out.println(cart);
}
}
5.创建src/com/zzw/furns/web/CartServlet.java
cart是个引用, cart内容变了, session中也会跟着变
@WebServlet(urlPatterns = "/cartServlet")
public class CartServlet extends BasicServlet {
private FurnService furnService = new FurnServiceImpl();
//添加一个添加家居到购物车的方法
protected void addItem(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
int id = DataUtils.parseInt(request.getParameter("id"), 1);//家居id
//根据id获取对应的家居信息
Furn furn = furnService.queryFurnById(id);
//先把正常的逻辑走完, 再处理异常的情况
HttpSession session = request.getSession();
Cart cart = (Cart) session.getAttribute("cart");
//得到购物车 有可能是空的,也有可能是上次的
if (cart == null) {
cart = new Cart();
session.setAttribute("cart", cart);
}
//构建一条家居明细: id,家居名,数量, 单价, 总价
//count类型为Integer, 不赋值默认值为null
CartItem cartItem = new CartItem(id, furn.getName(), 1, furn.getPrice(), furn.getPrice(), furn.getImagePath());
//将家居明细加入到购物车中. 如果家居id相同,数量+1;如果是一条新的商品,那么就新增
cart.addItem(cartItem);
System.out.println("cart= " + cart);
String referer = request.getHeader("referer");
response.sendRedirect(referer);
}
}
6.首页获取id请求后台
<button furnId="${furn/id}" title="Add To Cart" class="add-to-cart">Add
To Cart
</button>
<script type="text/javascript" src="script/jquery-3.6.0.min.js"></script>
<script type="text/javascript">
window.onload = function () {
$("button[title='Add To Cart']").click(function () {
window.location.href = "cartServlet?action=addItem&id=" + $(this).attr("furnId");
})
}
</script>
7.首页购买的商品总数量 - totalCount会默认调用getTotalCount方法
<a href="#offcanvas-cart"
class="header-action-btn header-action-btn-cart pr-0">
<i class="icon-handbag"> 购物车</i>
<span class="header-action-num">${sessionScope.cart.totalCount}</span>
</a>
8.Cart类getTotalCount
方法 - 错误写法
正确写法 - totalCount必须是局部变量, 否则会造成累加
public class Cart {
private Integer totalCount = 0;//1 3 3
//如果totalCount是全局变量, 将遵循这样的增长方式
//次数 购物车数量 totalCount(=totalCount+购物车数量)
// 1 1 1
// 2 2 3
// 3 3 6
// 4 4 10
// 5 5 15
// 6 6 21
// 7 7 28
public Integer getTotalCount() {
Collection<CartItem> cartItems = items.values();
for (CartItem cartItem : cartItems) {
totalCount += cartItem.getCount();
}
return totalCount;
}
}
1.HashMap的数据实际上是存在HashMap$Node中的, Node是HashMap的内部类
2.keySet里的key, 实际上只是引用, 指向了HashMap$Node<k, v>对象中的k, 真正的key值是保存在HashMap的Node内部类中的(HashMap$Node)
🍆显示购物车
需求分析
1.查看购物车, 可以显示如下信息
2.选中了哪些家居, 名称, 数量, 金额
3.统计购物车共多少商品, 总价多少
程序框架图
代码实现
1.从资源包上传文件web/views/cart/cart.jsp
2.web/views/customer/index.jsp
跳转购物车页面无响应 - 排错技巧展示
定位
3.显示家居项
<tbody>
<%--找到显示购物车项, 进行循环的items--%>
<c:if test="${not empty sessionScope.cart.items}">
<%--
1.sessionScope.cart.items => 取出的是HashMap<Integer, CartItem>
2.所以通过foreach标签取出的每一个对象, 即entry是 HashMap<Integer, CartItem>的 k-v
3.var其实就是 entry
4.所以要取出cartItem对象, 是通过 entry.value取出
--%>
<c:forEach items="${sessionScope.cart.items}" var="entry">
<tr>
<td class="product-thumbnail">
<a href="#"><img class="img-responsive ml-3"
src="${entry.value.imagePath}"
alt=""/></a>
</td>
<%--隐藏域--%>
<td hidden="hidden" class="product-name"><a href="#">${entry.key}</a></td>
<td class="product-name"><a href="#">${entry.value.name}</a></td>
<td class="product-price-cart"><span class="amount">$${entry.value.price}</span>
</td>
<td class="product-quantity">
<div class="cart-plus-minus">
<input class="cart-plus-minus-box" type="text" name="qtyButton"
value="${entry.value.count}"/>
</div>
</td>
<td class="product-subtotal">$${entry.value.totalPrice}</td>
<td class="product-remove">
<a href="#"><i>class="icon-close"></i></a>
</td>
</tr>
</c:forEach>
</c:if>
</tbody>
4.显示底部统计数据
<div class="row">
<div class="col-lg-12">
<div class="cart-shiping-update-wrapper">
<h4>共${sessionScope.cart.totalCount}件商品 总价 ${sessionScope.cart.totalPrice}元</h4>
<div class="cart-shiping-update">
<a href="#">购 物 车 结 账</a>
</div>
<div class="cart-clear">
<button>继 续 购 物</button>
<a href="#">清 空 购 物 车</a>
</div>
</div>
</div>
</div>
🍆更改商品数量
需求分析
1.进入购物车, 可以修改购买数量
2.更新该商品项的金额
3.更新购物车商品数量和总金额
程序框架图
1.修改Cart购物车类
/**
* 修改指定的CartItem的数量和总价, 根据传入的id 和 count
* @param id
* @param count
*/
public void updateCount(int id, int count) {//传进来的更新后的数量
CartItem cartItem = items.get(id);
if (cartItem != null) {//如果得到cartItem
//先更新数量
cartItem.setCount(count);
//再更新总价
cartItem.setTotalPrice(cartItem.getPrice().multiply(new BigDecimal(cartItem.getCount())));
//set方法有可能对count进行了二次处理, 所里这里用getCount()比较安全
}
}
2.修改src/com/zzw/furns/web/CartServlet.java
,增加方法updateCount
/**
* 更新某个cartItem的数量, 即更新购物车
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
protected void updateCount(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
int id = DataUtils.parseInt(req.getParameter("id"), 0);//家居id[默认0, 即使传错也不会影响数据]
int count = DataUtils.parseInt(req.getParameter("count"), 1);//更新后的数量
//获取session中的购物车
HttpSession session = req.getSession();
Cart cart = (Cart) session.getAttribute("cart");
if (cart != null) {
cart.updateCount(id, count);
}
//回到请求更新购物车的原页面
String referer = req.getHeader("referer");
resp.sendRedirect(referer);
}
3.修改web/views/cart/cart.jsp
<%--某个js文件对 cart-plus-minus 做了事件处理--%>
<div class="cart-plus-minus" furnId="${entry.value.id}">
<input class="cart-plus-minus-box" type="text" name="qtybutton"
value="${entry.value.count}"/>
</div>
4.修改web/views/cart/cart.jsp
<script type="text/javascript" src="script/jquery-3.6.0.min.js"></script>
<script type="text/javascript">
window.onload = function () {
var CartPlusMinus = $(".cart-plus-minus");
CartPlusMinus.prepend('<div class="dec qtybutton">-</div>');
CartPlusMinus.append('<div class="inc qtybutton">+</div>');
$(".qtybutton").on("click", function() {
var $button = $(this);
var oldValue = $button.parent().find("input").val();
if ($button.text() === "+") {
var newVal = parseFloat(oldValue) + 1;
} else {
// Don't allow decrementing below zero
if (oldValue > 1) {
var newVal = parseFloat(oldValue) - 1;
} else {
newVal = 1;
}
}
$button.parent().find("input").val(newVal);
//在这里书写我们的代码
var furnId = $button.parent().attr("furnId");
//在这里发出修改购物车的请求
location.href = "cartServlet?action=updateCount&id=" + furnId + "&count=" + newVal;
});
}
</script>
🍆清空购物车&&删除商品
需求分析
1.进入购物车, 可以删除某商品
2.可以清空购物车
3.要求给出适当的确认信息
程序框架图
应用实例-删除商品
1.修改Cart购物车类
/**
* 根据传入的id, 删除指定的购物车项
* @param id
*/
public void deleteItem(int id) {
items.remove(id);
}
2.修改src/com/zzw/furns/web/CartServlet.java
,增加方法delItem
protected void delItem(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//为了防止接收的id转化成数字时报错, 在工具类DataUtils中写一个方法
int id = DataUtils.parseInt(req.getParameter("id"), 0);
HttpSession session = req.getSession();
Cart cart = (Cart) session.getAttribute("cart");
if (cart != null) {
cart.deleteItem(id);
}
//返回到请求删除购物车的页面
String referer = req.getHeader("referer");
resp.sendRedirect(referer);
}
3.修改web/views/cart/cart.jsp
$(".product-remove").click(function () {
var furnName = $(this).attr("furnName");
return confirm("确定要删除 " + furnName + " 家居项吗?");
})
<td class="product-remove" furnName="${entry.value.name}">
<a href="cartServlet?action=delItem&id=${entry.value.id}">
<i class="icon-close"></i>
</a>
</td>
清空购物车
1.修改Cart购物车类
//清空购物车
public void clear() {
items.clear();
}
2.修改src/com/zzw/furns/web/CartServlet.java
,增加方法clear
protected void clear(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//清空购物车
Cart cart = (Cart) req.getSession().getAttribute("cart");
if (cart != null) {
cart.clear();
}
String referer = req.getHeader("referer");
resp.sendRedirect(referer);
}
3.修改web/views/cart/cart.jsp
//清空购物车
$("a:contains('清 空 购 物 车')").click(function () {
return confirm("确定要清空购物车吗?");
})
<a href="cartServlet?action=clear">清 空 购 物 车</a>
🌳生成订单
需求分析
1.进入购物车, 点击购物车结账
2.生成订单和订单项, 并更新商品的销量和库存
3.如果会员没有登陆, 先进入登陆页面, 完成登陆后再结账
程序框架图
1.创建order表
-- 创建家居网购需要的数据库和表
-- 删除数据库
DROP DATABASE IF EXISTS home_furnishing;
-- 删除表
DROP TABLE `order`;
-- 创建数据库
CREATE DATABASE home_furnishing;
-- 切换
USE home_furnishing;
-- 创建订单表
-- 每个字段应当使用 not null 来约束
-- 字段类型的设计, 应当和相关联表的字段类型相对应
-- 是否需要使用外键?
-- 1.需要[可以从db层保证数据的一致性(早期hibernate框架要求必须使用外键)]
-- 2.不需要[外键对效率有影响, 应当从程序的业务层保证数据的一致性(推荐)]
CREATE TABLE `order` (
id VARCHAR(60) PRIMARY KEY, -- 订单编号
create_time DATETIME NOT NULL,-- 年月日 时分秒
price DECIMAL(10,2) NOT NULL,-- 订单价格
`status` TINYINT NOT NULL, -- 订单状态(1未发货 2已发货 3已结账)
member_id INT NOT NULL -- 谁的订单
)CHARSET utf8 ENGINE INNODB;
2.创建order_item表
-- 创建家居网购需要的数据库和表
-- 删除数据库
DROP DATABASE IF EXISTS home_furnishing;
-- 删除表
DROP TABLE order_item;
-- 创建数据库
CREATE DATABASE home_furnishing;
-- 切换
USE home_furnishing;
-- 创建订单明细表
CREATE TABLE order_item (
id INT PRIMARY KEY AUTO_INCREMENT, -- 订单明细id
`name` VARCHAR(32) NOT NULL,-- 家居名
`count` INT UNSIGNED NOT NULL,-- 数量
price DECIMAL(10, 2) NOT NULL,-- 价格
total_price DECIMAL(10, 2) NOT NULL,-- 订单项的总价格
order_id VARCHAR(60) NOT NULL -- 订单编号
)CHARSET utf8 ENGINE INNODB;
3.新建实体类src/com/zzw/furns/entity/OrderItem.java
, 订单明细表
public class OrderItem {
private Integer id;
private String name;
private Integer count;
private BigDecimal price;
private BigDecimal totalPrice;
private String orderId;
public OrderItem() {
}
//有参构造器
//getter方法, setter方法, toString方法
}
4.新建实体类src/com/zzw/furns/entity/Order.java
, 订单表
public class Order {
private String id;
private Date createTime;
private BigDecimal price;
private Integer status;
private Integer memberId;
private List<OrderItem> items = new ArrayList<>();
//计算订单的商品数, 供前端EL表达式使用
public Integer getTotalCount() {
Integer totalCount = 0;
for (OrderItem orderItem : items) {
totalCount += orderItem.getCount();
}
return totalCount;
}
//无参构造器
public Order() {
}
//有参构造器
//getter方法, setter方法, toString方法
}
5.新建src/com/zzw/furns/dao/OrderItemDao.java
public interface OrderItemDAO {
public boolean saveOrderItem(OrderItem orderItem);
}
6.新建src/com/zzw/furns/dao/impl/OrderItemDaoImpl.java
public class OrderItemDAOImpl extends BasicDAO<OrderItem> implements OrderItemDAO {
@Override
public boolean saveOrderItem(OrderItem orderItem) {
String sql = "INSERT INTO order_item(id, `name`, `count`, price, total_price, order_id) " +
"VALUES(?, ?, ?, ?, ?, ?)";
int update = update(sql, orderItem.getId(), orderItem.getName(), orderItem.getCount(),
orderItem.getPrice(), orderItem.getTotalPrice(), orderItem.getOrderId());
return update > 0;
}
}
7.测试, 新建src/com/zzw/furns/test/OrderItemDAOTest.java
public class OrderItemDAOTest {
private OrderItemDAO orderItemDAO = new OrderItemDAOImpl();
@Test
public void saveOrderItem() {
OrderItem orderItem = new OrderItem(null, "小台灯", 10,
new BigDecimal(25), new BigDecimal(10).multiply(new BigDecimal(25)), "9");
boolean b = orderItemDAO.saveOrderItem(orderItem);
System.out.println(b);
}
}
8.新建src/com/zzw/furns/dao/OrderDao.java
public interface OrderDAO {
//将传入的order对象 保存到数据库order表
public boolean saveOrder(Order order);
}
9.新建src/com/zzw/furns/dao/impl/OrderDaoImpl.java
public class OrderDAOImpl extends BasicDAO<Order> implements OrderDAO {
@Override
public boolean saveOrder(Order order) {
String sql = "INSERT INTO `order`(id, `create_time`, price, `count`, `status`, member_id) " +
"VALUES(?, ?, ?, ?, ?, ?)";
int update = update(sql, order.getId(), order.getCreateTime(), order.getPrice(), order.getCount(),
order.getStatus(), order.getMemberId());
return update > 0;
}
}
10.测试, 新建src/com/zzw/furns/test/OrderDAOTest.java
public class OrderDAOTest {
private OrderDAO orderDAO = new OrderDAOImpl();
@Test
public void saveOrder() {
Order order = new Order(UUID.randomUUID().toString(), new Date(), new BigDecimal(22.00), 0, 123);
boolean saveOrder = orderDAO.saveOrder(order);
System.out.println(saveOrder);
}
}
11.新建src/com/zzw/furns/service/OrderService.java
public interface OrderService {
//1.生成订单
//2.订单是根据cart来生成, cart对象在session. 通过web层, 传入saveOrder
//3.订单和一个会员关联
public String saveOrder(Cart cart, int memberId);
}
12.新建src/com/zzw/furns/service/impl/OrderServiceImpl.java
public class OrderServiceImpl implements OrderService {
private OrderDAO orderDAO = new OrderDAOImpl();
private OrderItemDAO orderItemDAO = new OrderItemDAOImpl();
private FurnDAO furnDAO = new FurnDAOImpl();
//在这里可以感受到javaee分层的好处. 在service层, 通过组合多个dao的方法,
// 完成某个业务 慢慢体会好处
@Override
public String saveOrder(Cart cart, int memberId) {
//将cart购物车的数据以order和orderItem的形式保存到DB中
//因为生成订单会操作多张表, 因此会涉及到多表事务的问题, ThreadLocal+Mysql事务机制+过滤器
//1.通过cart对象, 构建一个对应的order对象
// 先生成一个UUID, 表示当前的订单号, UUID是唯一的
String orderId = UUID.randomUUID().toString();//订单id
Order order = new Order(orderId, new Date(), cart.getCartTotalPrice(), 0, memberId);
//保存order到数据表
orderDAO.saveOrder(order);//订单生成成功
//通过cart对象, 遍历CartItem, 构建OrderItem对象, 并保存到对应的order_item表
Map<Integer, CartItem> cartItems = cart.getItems();
String orderItemId = "";
for (CartItem cartItem : cartItems.values()) {
//通过cartItem对象构建了orderItem对象
OrderItem orderItem = new OrderItem(null, cartItem.getName(), cartItem.getCount(),
cartItem.getPrice(), cartItem.getTotalPrice(), orderId);
//保存
orderItemDAO.saveOrderItem(orderItem);
//更新furn表 saleNum销量 - inventory库存
//(1) 获取furn对象
Furn furn = furnDAO.queryFurnById(cartItem.getId());
//(2) 更新furn对象的 saleNum销量 - inventory库存
furn.setInventory(furn.getInventory() - cartItem.getCount());
furn.setSaleNum(furn.getSaleNum() + cartItem.getCount());
//(3) 更新到数据表
furnDAO.updateFurn(furn);
}
//清空购物车
cart.clear();
return orderId;
}
}
13.测试src/com/zzw/furns/service/impl/OrderServiceTest.java
public class OrderServiceTest {
private OrderService orderService = new OrderServiceImpl();
@Test
public void saveOrder() {
//构建一个Cart对象
Cart cart = new Cart();
cart.addItem(new CartItem(1, "书桌", 1, new BigDecimal(12), new BigDecimal(12)));
cart.addItem(new CartItem(2, "电脑", 2, new BigDecimal(12), new BigDecimal(24)));
cart.addItem(new CartItem(3, "鼠标", 3, new BigDecimal(12), new BigDecimal(36)));
String ordId = orderService.saveOrder(cart, 12);
System.out.println("ordId = " + ordId);
}
}
14.新建src/com/zzw/furns/web/OrderServlet.java
public class OrderServlet extends BasicServlet {
//定义属性
private OrderService orderService = new OrderServiceImpl();
/**
* 生成订单
* @param request
* @param response
* @throws ServletException
* @throws IOException
*/
protected void saveOrder(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
HttpSession session = request.getSession();
//获取购物车
Cart cart = (Cart) session.getAttribute("cart");
//如果cart为空, 说明会员没有购买任何家居, 转发到首页
//这里需要补充逻辑: 购物车在session里, 但没有家居数据
if (cart == null || cart.isEmpty()) {
//重定向, 请求转发后面的代码会继续执行
response.sendRedirect(request.getContextPath());
return;
}
//获取到登陆的member对象
Member member = (Member) session.getAttribute("member");
if (member == null) {//说明用户没有登录, 转发到登陆页面
//重定向到登陆页面
request.getRequestDispatcher("/views/member/login.jsp")
.forward(request, response);
return;//直接返回
}
//可以生成订单
String orderId = orderService.saveOrder(cart, member.getId());//订单, 订单明细已生成
session.setAttribute("orderId", orderId);//订单id
//使用重定向放入到checkout.jsp
response.sendRedirect(request.getContextPath() + "/views/order/checkout.jsp");
}
}
15.新建web/views/order/checkout.jsp
16.测试src/com/zzw/furns/test/CartTest.java
, 防止生成空订单
@Test
public void isEmpty() {
Map<Object, Object> map = new HashMap<>();
map.put("k", "v");
map.clear();
System.out.println(map == null);//false
System.out.println(map.size());//0
}
17.查看HashMap源码
public void clear() {
Node<K,V>[] tab;
modCount++;
if ((tab = table) != null && size > 0) {
//clear之后, size置为0
size = 0;
for (int i = 0; i < tab.length; ++i)
tab[i] = null;
}
}
18.修改web/views/cart/cart.jsp
<div class="cart-shiping-update">
<a href="orderServlet?action=saveOrder">购 物 车 结 账</a>
</div>
<a class="active" href="index.jsp">
<h4>订单已结算, 订单号-${sessionScope.orderId}</h4>
</a>