目标
- 说出品优购购物车的实现思路
- 运用cookie存储购物车
- 编写购物车前端代码
- 运用redis存储购物车
1. 购物车需求分析与解决方案
1. 需求分析
用户在商品详情页点击加入购物车,提交商品SKU编号和购买数量,添加到购物车,购物车展示页面如下:
2. 实现思路
购物车数据的存储结构如下
用户未登录时,将此购物车存入cookies,在用户登录的时候,将购物车数据存入redis,如果用户登录时,cookie中存在购物车,需要将cookie的购物车合并到redis存储。
3. 工程搭建
- 创建cart-interface,依赖pojo
- 创建cart-service,同其他service,tomcat端口9007,dubbo端口20887
- 创建cart-web,web.xml和spring配置参考其他web,tomcat端口为9107,拷贝页面资源和UserDetailsServiceImpl
- 将资源文件夹的Cookie工具类拷贝到common工程,该工程引入servlet-api依赖
4. 购物车实体类
购物车的明细其实就是订单的明细,只是没有订单id
在pojo的pojogroup创建购物车实体类
public class Cart implements Serializable {
private String sellerId;// 商家ID
private String sellerName;// 商家名称
private List<TbOrderItem> orderItemList;//购物车明细
//getter/setter...
该类是对每个商家的购物车进行的封装。
2. Cookie 存储购物车
1. 需求分析
使用cookie存储购物车数据,服务层负责逻辑,控制层负责读写cookie
在服务层不要涉及到cookie操作
2. 服务接口层
- 服务层接口 cart-interface新建com.pinyougou.cart.service包,新建接口CartService
// cartList –> 原来得购物车列表 返回新得购物车列表
public List<Cart> addGoodsToCartList(List<Cart> cartList,Long itemId,Integer num);
3. 服务实现层
实现的思路:
//1.根据商品SKU ID查询SKU商品信息
//2.获取商家ID
//3.根据商家ID判断购物车列表中是否存在该商家的购物车 -> Cart
//4.如果购物车列表中不存在该商家的购物车 Cart = null
//4.1 新建购物车对象 new Cart()
//4.2 新建一个购物车明细对象设置商品id 数量 金额 tbOrderItem
//4.3 将新建的购物车对象添加到购物车列表 cartList.add()
//5.如果购物车列表中存在该商家的购物车 Cart != null
// 查询购物车明细列表中是否存在该商品
//5.1. 如果没有,新增购物车明细
//5.2. 如果有,在原购物车明细上添加数量,更改金额
代码实现 cart-service工程创建CartServiceImpl
@Service
public class CartServiceImpl implements CartService {
@Autowired
private TbItemMapper itemMapper;
@Override
public List<Cart> addGoodsToCartList(List<Cart> cartList, Long itemId, Integer num) {
// 1. 根据skuID查询商品明细sku的对象
TbItem item = itemMapper.selectByPrimaryKey(itemId);
if(item==null){
throw new RuntimeException("商品不存在");
}
if(!"1".equals(item.getStatus())){
throw new RuntimeException("商品状态不合法");
}
// 2. 根据SKU对象获取商家id
String sellerId = item.getSellerId();
// 3. 根据商家id在购物车列表中查询购物车对象
Cart cart = findCartBySellerId(cartList, sellerId);
if(cart==null){
// 4. 如果购物车列表中不存在该商家的购物车
// 4.1 创建一个新的购物车对象
cart = new Cart();
cart.setSellerId(sellerId);// 商家id
cart.setSellerName(item.getSeller());//商家名称
List<TbOrderItem> orderItemList = new ArrayList<>();//创建购物车明细列表
// 创建新的购物车明细对象
TbOrderItem orderItem = createOrderItem(item, num);
orderItemList.add(orderItem);
cart.setOrderItemList(orderItemList);
// 4.2 将新的购物车对象添加到购物车列表中
cartList.add(cart);
}else{
// 5. 如果购物车列表存在该商家的购物车
// 判断该商品是否在该购物车的明细列表中存在
TbOrderItem orderItem = findOrderItemByItemId(cart.getOrderItemList(), itemId);
if(orderItem==null){
// 5.1 如果不存在,创建新的购物车明细对象,并添加到该购物车的明细列表中
orderItem = createOrderItem(item, num);
cart.getOrderItemList().add(orderItem);
}else{
// 5.2 如果存在,在原有的数量上添加数量,并且更新金额
orderItem.setNum(orderItem.getNum()+num);
orderItem.setTotalFee(new BigDecimal(orderItem.getPrice().doubleValue()*orderItem.getNum()));
// 当明细的数量小于等于0,移除该明细
if(orderItem.getNum()<=0){
cart.getOrderItemList().remove(orderItem);
}
// 当购物车的明细数量为0,在购物车列表中移除该购物车
if(cart.getOrderItemList().size()==0){
cartList.remove(cart);
}
}
}
return cartList;
}
// 根据商家id在购物车列表中查询购物车对象
private Cart findCartBySellerId(List<Cart> cartList,String sellerId){
for (Cart cart : cartList) {
if(sellerId.equals(cart.getSellerId())){
return cart;
}
}
return null;
}
// 根据SKUid在购物车明细列表中查询购物车明细对象
private TbOrderItem findOrderItemByItemId(List<TbOrderItem> orderItemList,Long itemId){
for (TbOrderItem orderItem : orderItemList) {
if(orderItem.getItemId().longValue()==itemId.longValue()){
return orderItem;
}
}
return null;
}
private TbOrderItem createOrderItem(TbItem item,Integer num){
// 创建新的购物车明细对象
TbOrderItem orderItem = new TbOrderItem();
orderItem.setGoodsId(item.getGoodsId());
orderItem.setItemId(item.getId());
orderItem.setTitle(item.getTitle());
orderItem.setPrice(item.getPrice());
orderItem.setNum(num);
orderItem.setPicPath(item.getImage());
orderItem.setSellerId(item.getSellerId());
orderItem.setTotalFee(new BigDecimal(item.getPrice().doubleValue()*num));
return orderItem;
}
}
4. 后端控制层
实现思路:
- 从cookie中取出购物车
- 向购物车添加商品
- 将购物车存入cookie
cart-web工程新建CartController.java
@RestController
@RequestMapping("/cart")
public class CartController {
@Autowired
private HttpServletRequest request;
@Autowired
private HttpServletResponse response;
@Reference
private CartService cartService;
@RequestMapping("/findCartList")
public List<Cart> findCartList(){
// 从cookie中提取购物车
String cartListString = CookieUtil.getCookieValue(request, "cartList", "UTF-8");
if(cartListString==null || cartListString.equals("")){
cartListString="[]";
}
List<Cart> cartList_cookie = JSON.parseArray(cartListString, Cart.class);
return cartList_cookie;
}
@RequestMapping("/addGoodsToCartList")
public Result addGoodsToCartList(Long itemId,Integer num){
try {
// 从cookie中提取购物车
List<Cart> cartList = findCartList();
// 调用服务方法操作购物车
cartList = cartService.addGoodsToCartList(cartList, itemId, num);
String cartListString = JSON.toJSONString(cartList);
// 将新的购物车存入cookie
CookieUtil.setCookie(request,response,"cartList",cartListString,3600*24,"UTF-8");
return new Result(true,"存入购物车成功");
} catch (Exception e) {
e.printStackTrace();
return new Result(false,"存入购物车失败");
}
}
}
浏览器测试
查看购物车:localhost:9105/cart/findCartList.do
添加商品到购物车:localhost:9105/cart/addGoodsToCartList.do?itemId=1369284&num=2
3. 购物车前端代码
1. 需求分析
实现购物车页面的展示和相关操作
可实现购物车列表、数量的增减与移除以及合计数统计
2. 购物车列表
1. 前端服务层
cart-web增加cartService.js
// 购物车服务层
app.service('cartService',function ($http) {
// 购物车列表
this.findCartList=function () {
return $http.get('cart/findCartList.do');
}
2. 前端控制层
cart-web增加cartController.js
// 购物车控制层
app.controller('cartController',function ($scope, cartService) {
// 查询购物车列表
$scope.findCartList = function () {
cartService.findCartList().success(
function (response) {
$scope.cartList = response;
}
);
};
3. 页面
修改cart.html,引入js
<script type="text/javascript" src="plugins/angularjs/angular.min.js"> </script>
<script type="text/javascript" src="js/base.js"> </script>
<script type="text/javascript" src="js/service/cartService.js"> </script>
<script type="text/javascript" src="js/controller/cartController.js"> </script>
添加相关指令,指令控制器和调用初始化方法
<body ng-app="pinyougou" ng-controller="cartController" ng-init="findCartList()">
循环显示购物车列表
<div class="cart-main">
<div class="yui3-g cart-th">
<div class="yui3-u-1-4"><input type="checkbox" name="" id="" value="" /> 全部</div>
<div class="yui3-u-1-4">商品</div>
<div class="yui3-u-1-8">单价(元)</div>
<div class="yui3-u-1-8">数量</div>
<div class="yui3-u-1-8">小计(元)</div>
<div class="yui3-u-1-8">操作</div>
</div>
<div class="cart-item-list" ng-repeat="cart in cartList">
<div class="cart-shop">
<input type="checkbox" name="" id="" value="" />
<span class="shopname self">{{cart.sellerName}} 【商家名称】{{cart.sellerId}}</span>
</div>
<div class="cart-body">
<div class="cart-list" ng-repeat="orderItem in cart.orderItemList">
<ul class="goods-list yui3-g">
<li class="yui3-u-1-24">
<input type="checkbox" name="" id="" value="" />
</li>
<li class="yui3-u-11-24">
<div class="good-item">
<div class="item-img"><img src="{{orderItem.picPath}}" /></div>
<div class="item-msg">{{orderItem.title}}</div>
</div>
</li>
<li class="yui3-u-1-8"><span class="price">{{orderItem.price.toFixed(2)}}</span></li>
<li class="yui3-u-1-8">
<a href="javascript:void(0)" ng-click="addGoodsToCartList(orderItem.itemId,-1)" class="increment mins">-</a>
<input autocomplete="off" type="text" value="1" minnum="1" class="itxt" ng-model="orderItem.num"/>
<a href="javascript:void(0)" ng-click="addGoodsToCartList(orderItem.itemId,1)" class="increment plus">+</a>
</li>
<li class="yui3-u-1-8"><span class="sum">{{orderItem.totalFee.toFixed(2)}}</span></li>
<li class="yui3-u-1-8">
<a href="#none" ng-click="addGoodsToCartList(orderItem.itemId,-orderItem.num)">删除</a><br />
<a href="#none">移到我的关注</a>
</li>
</ul>
</div>
</div>
</div>
</div>
3. 购物车数量增减和移除
1. 前端服务层
cart-web的cartService.js
// 添加商品到购物车
this.addGoodsToCartList = function (itemId,num) {
return $http.get('cart/addGoodsToCartList.do?itemId='+itemId+'&num='+num);
}
2. 前端控制层
cart-web的cartController.js
// 数量加减
$scope.addGoodsToCartList = function (itemId,num) {
cartService.addGoodsToCartList(itemId,num).success(
function (response) {
if(response.success){
// 如果成功,刷新列表
$scope.findCartList();
}else{
alert(response.message);
}
}
);
}
3. 页面
修改cart-web的cart.html,实现数量增减
见之前代码
4. 合计数
1. 前端服务层
cartService.js
// 求合计
this.sum = function (cartList) {
var totalValue = {totalNum:0,totalMoney:0.00};
for(var i=0;i<cartList.length;i++){
var cart = cartList[i];
for(var j=0;j<cart.orderItemList.length;j++){
var orderItem = cart.orderItemList[j];//购物车明细
totalValue.totalNum += orderItem.num;//累加数量
totalValue.totalMoney += orderItem.totalFee;//累加金额
}
}
return totalValue;
}
2. 前端控制层
// 查询购物车列表
$scope.findCartList = function () {
cartService.findCartList().success(
function (response) {
$scope.cartList = response;
$scope.totalValue = cartService.sum($scope.cartList);//求合计数
/* sum();*/
}
);
};
3. 页面
<div class="toolbar">
<div class="chosed">已选择<span>{{totalValue.totalNum}}</span>件商品</div>
<div class="sumprice">
<span><em>总价(不含运费) :</em><i class="summoney">¥{{totalValue.totalMoney.toFixed(2)}}</i></span>
4. Redis存储购物车
1. 需求分析
判断当前用户是否登录,如果未登录采用cookie存储,如果登录用redis存储。登陆后将cookie购物车与redis购物车合并,清除cookie购物车。
2. 获取当前登录人的账号
1. 配置文件
spring-security更改配置
去掉
<!--<http pattern="/cart/*.do" security="none"></http>-->
添加
<!--匿名角色-->
<intercept-url pattern="/cart/*.do" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
<intercept-url pattern="/**" access="ROLE_USER"/>
设置资源可以再不登录时可以访问。
此时登录人账号为anonymousUser
2. 代码实现
在cart-web的findCartList和addGoodsToCartList方法中,获取用户名
String name = SecurityContextHolder.getContext().getAuthentication().getName();
System.out.println("current registrant is: "+name);
3. 远程购物车的存取
1. 服务接口层
cart-interface中CartService.java定义方法
// 从redis中提取购物车列表
public List<Cart> findCartListFromRedis(String username);
// 将购物车列表存入redis
public void saveCartListToRedis(String username,List<Cart> cartList);
2. 服务实现层
cart-service中CartServiceImpl.java
@Autowired
private RedisTemplate redisTemplate;
@Override
public List<Cart> findCartListFromRedis(String username) {
System.out.println("get the cart from redis "+username);
List<Cart> cartList = (List<Cart>) redisTemplate.boundHashOps("cartList").get(username);
if(cartList==null){
cartList=new ArrayList<>();
}
return cartList;
}
@Override
public void saveCartListToRedis(String username, List<Cart> cartList) {
System.out.println("put the cart to redis "+username);
redisTemplate.boundHashOps("cartList").put(username,cartList);
}
3. 控制层
修改findCartList方法和addGoodsToCartList方法
@Reference(timeout = 6000)
private CartService cartService;
@RequestMapping("/findCartList")
public List<Cart> findCartList(){
// 获取当前登录人
String name = SecurityContextHolder.getContext().getAuthentication().getName();
System.out.println("current registrant is: "+name);
if(name.equals("anonymousUser")){//如果没登录
// 从cookie中提取购物车
System.out.println("get cart from cookie");
String cartListString = CookieUtil.getCookieValue(request, "cartList", "UTF-8");
if(cartListString==null || cartListString.equals("")){
cartListString="[]";
}
List<Cart> cartList_cookie = JSON.parseArray(cartListString, Cart.class);
return cartList_cookie;
}else{//如果已登录
List<Cart> cartList_redis = cartService.findCartListFromRedis(name);
return cartList_redis;
}
}
@RequestMapping("/addGoodsToCartList")
public Result addGoodsToCartList(Long itemId,Integer num){
try {
// 获取当前登录人
String name = SecurityContextHolder.getContext().getAuthentication().getName();
System.out.println("current registrant is: "+name);
// 从cookie中提取购物车
List<Cart> cartList = findCartList();
// 调用服务方法操作购物车
cartList = cartService.addGoodsToCartList(cartList, itemId, num);
if(name.equals("anonymousUser")){//如果没登录
String cartListString = JSON.toJSONString(cartList);
// 将新的购物车存入cookie
System.out.println("put cart to cookie");
CookieUtil.setCookie(request,response,"cartList",cartListString,3600*24,"UTF-8");
}else{// 登录
cartService.saveCartListToRedis(name,cartList);
}
return new Result(true,"存入购物车成功");
} catch (Exception e) {
e.printStackTrace();
return new Result(false,"存入购物车失败");
}
}
为了避免调用远程服务超时,可以将过期时间改为6秒
4. 跳板页
创建跳板页:cart-web工程新建login.html,页面添加脚本
<script type="text/javascript">
location.href='cart.html';
</script>
购物车页面链接到跳板页
请<a href="login.html">登录</a>
4. 购物车合并
1. 服务接口层
public List<Cart> mergeCartList(List<Cart> cartList1,List<Cart> cartList2);
2. 服务实现层
cart-service的CartServiceImpl
@Override
public List<Cart> mergeCartList(List<Cart> cartList1, List<Cart> cartList2) {
// cartList1.addAll(cartList2); //不能简单合并
for (Cart cart : cartList2) {
for (TbOrderItem orderItem : cart.getOrderItemList()) {
cartList1 = addGoodsToCartList(cartList1,orderItem.getItemId(),orderItem.getNum());
}
}
return cartList1;
}
3. 控制层
修改findCartList方法
@RequestMapping("/findCartList")
public List<Cart> findCartList(){
// 获取当前登录人
String username = SecurityContextHolder.getContext().getAuthentication().getName();
System.out.println("current registrant is: "+username);
String cartListString = CookieUtil.getCookieValue(request, "cartList", "UTF-8");
if(cartListString==null || cartListString.equals("")){
cartListString="[]";
}
List<Cart> cartList_cookie = JSON.parseArray(cartListString, Cart.class);
if(username.equals("anonymousUser")){//如果没登录
// 从cookie中提取购物车
System.out.println("get cart from cookie");
return cartList_cookie;
}else{//如果已登录
// 获取redis购物车
List<Cart> cartList_redis = cartService.findCartListFromRedis(username);
if(cartList_cookie.size()>0){//判断本地购物车中存在数据
// 得到合并后的购物车
List<Cart> cartList = cartService.mergeCartList(cartList_cookie, cartList_redis);
// 将合并后的购物车存入redis
cartService.saveCartListToRedis(username,cartList);
// 清除本地购物车
util.CookieUtil.deleteCookie(request,response,"cartList");
System.out.println("merge cart...");
return cartList;
}
return cartList_redis;
}
}