本文主要是介绍项目实战系列三: 家居购项目 第四部分,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
购物车
- 🌳购物车
- 🍆显示购物车
- 🍆更改商品数量
- 🍆清空购物车&&删除商品
- 🌳生成订单
🌳购物车
需求分析
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]到Cartpublic void addItem(CartItem cartItem) {CartItem item = items.get(cartItem.getId());//得到购物车里的商品项if (item == null) {//说明当前购物车还没有这个cartItemitems.put(cartItem.getId(), cartItem);} else {//购物车中有这个cartItemitem.setCount(item.getCount() + 1);//数量加1//修改总价//item.getPrice() => BigDecmal//item.getCount() => Integeritem.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);//遍历我们的itemsSet<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 {@Testpublic 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, 不赋值默认值为nullCartItem 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">AddTo 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 28public 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-v3.var其实就是 entry4.所以要取出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 zeroif (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 {@Overridepublic 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();@Testpublic 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 {@Overridepublic 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();@Testpublic 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的方法,// 完成某个业务 慢慢体会好处@Overridepublic String saveOrder(Cart cart, int memberId) {//将cart购物车的数据以order和orderItem的形式保存到DB中//因为生成订单会操作多张表, 因此会涉及到多表事务的问题, ThreadLocal+Mysql事务机制+过滤器//1.通过cart对象, 构建一个对应的order对象// 先生成一个UUID, 表示当前的订单号, UUID是唯一的String orderId = UUID.randomUUID().toString();//订单idOrder 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();@Testpublic 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.jspresponse.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);//falseSystem.out.println(map.size());//0
}
17.查看HashMap源码
public void clear() {Node<K,V>[] tab;modCount++;if ((tab = table) != null && size > 0) {//clear之后, size置为0size = 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>
这篇关于项目实战系列三: 家居购项目 第四部分的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!