理解购物车的思路
购物车登陆后存储到redis中
数据结构分析:
购物车与商家相关联
我们由京东的案列,就是可以看出,不登录,也可以实现购物车的添加,不过天猫不可以,必须登陆后才能进行,添加到购物车
购物车列表中有多个购物车:[
京东自营(购物车对象){
商家id, sellerId
商家名称:sellerName
购物车商品明细列表:
[
购物车明细对象(展示商品信息){
荣耀9i
华为p20
}
]
}
疆界互联旗舰店:[
和京东自营存储数据结构相同
]
]
上面就是常用的购物车的存储结构
添加商品到购物车,登陆和未登陆都能实现添加购物车
未登陆时:
1.存储到cookie中,这个方法有个弊端,就是只能存4kb
2.localStorge 储存能达到5kb 是基于html5存储数据对象的
JSON.stringify();
JSON.parse();
3.缓存到redis中作为key-value储存
登陆后:
1.缓存到redis中
登陆后:用户名 作为redis存储购物车的可以
登陆后将购物车列表中的商品提交生成订单后,在清除购物车中的数据
首先搭建购物车的工程 cart_web cart_service cart_interface
工程搭建完毕,分析后端添加购物车的逻辑思路:
非常重要:
首先根据商品的id查询 该商家是否存在于购物车中
1.商家对应的购物车不存在,购物车列表中
创建购物车对象,在存入购物车列表中
创建购物车对象时,指定该购物车的商家信息,以及构建购物车明细对象和购物车明细列表
将购物车的明细对象添加到购物车明细列表中,将购物车明细列表添加到购物车对象,在将
购物车对象添加到购物车列表中
2.商家对应的购物车对象存在于购物车列表中
判断该商品是否存在于该购物车商品的明细列表中
1.如果该商品不存在购物车明细列表中
则创建购物车明细对象,在添加到购物车明细里列表中
2.如果该商品存在购物车明细列表中
修改购物车明细对象的数量和小计金额
首先我们先编写未登陆时,的接口方法
/**
* 添加商品到购物车
*/
public List addItemToCartList(List cartList, Long itemId, Integer num);
/**
* 根据sessionId获得购物车列表
* @param sessionId
* @return
*/
List selectCartListFromRedis(String sessionId);
/**
* 保存该sessionid 的 cartList 购物车列表
* @param sessionId
* @param cartList
*/
void saveCartListToRedis(String sessionId, List cartList);
/**
* 合并添加前购物车的数据到登陆后的购物车列表
* @param cartList_sessionId
* @param cartList_username
* @return
*/
List mergeCartList(List cartList_sessionId, List cartList_username);
/**
* 删除之前未登陆的购物车的列表数据
* @param cartList_sessionId
*/
void deleteCartList(List cartList_sessionId);
service实现的所有的功能,主要是:购物车的添加,以及通过sessionid获取购物车列表,还有就是,未登陆时,购物车列表的数据,在登陆后会跟新到redis数据库中,通过username的作为key值保存,然后删除登陆前购物车的数据
package com.pinyougou.cart.service.impl;
import com.alibaba.dubbo.config.annotation.Service;
import com.pinyougou.cart.service.CartService;
import com.pinyougou.groupentity.Cart;
import com.pinyougou.mapper.TbItemMapper;
import com.pinyougou.pojo.TbItem;
import com.pinyougou.pojo.TbOrderItem;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
@Service
@Transactional
public class CartServiceImpl implements CartService {
@Autowired
private TbItemMapper itemMapper;
@Autowired
private RedisTemplate redisTemplate;
/**
* 添加商品到购物车
* @param cartList
* @param itemId
* @param num
* @return
* 首先根据商品的id查询 该商家是否存在于购物车中
1.商家对应的购物车不存在,购物车列表中
创建购物车对象,在存入购物车列表中
创建购物车对象时,指定该购物车的商家信息,以及构建购物车明细对象和购物车明细列表
将购物车的明细对象添加到购物车明细列表中,将购物车明细列表添加到购物车对象,在将
购物车对象添加到购物车列表中
2.商家对应的购物车对象存在于购物车列表中
判断该商品是否存在于该购物车商品的明细列表中
1.如果该商品不存在购物车明细列表中
则创建购物车明细对象,在添加到购物车明细里列表中
2.如果该商品存在购物车明细列表中
修改购物车明细对象的数量和小计金额
*/
@Override
public List addItemToCartList(List cartList, Long itemId, Integer num) {
//首先根据商品的id查询 该商家是否存在于购物车中
TbItem item = itemMapper.selectByPrimaryKey(itemId);
//优化操作,添加购物车的时候,刚好该商品下架了
if(item==null){
throw new RuntimeException("商品不存在");
}
//如果商品的状态不为1则无效
if (!item.getStatus().equals("1")){
throw new RuntimeException("商品无效");
}
String sellerId = item.getSellerId();
//在购物车列表中基于商品id 查询购物车对象
Cart cart = searchCartBySellerId(cartList,sellerId);
//判断购物车是否在空
if (cart==null){//购物车为空
//创建购物车对象,
cart = new Cart();
//指定该购物车的商家信息,以及构建购物车明细对象和购物车明细列表
cart.setSellerId(sellerId);
cart.setSellerName(item.getSeller());
//以及构建购物车明细对象和购物车明细列表
List orderItemList = new ArrayList<>();
//构建商品明细对象
TbOrderItem orderItem = createOrderItem(item,num);
//购物车的明细对象添加到购物车明细列表中
orderItemList.add(orderItem);
//购物车明细列表添加到购物车对象
cart.setOrderItemList(orderItemList);
//购物车对象添加到购物车列表中
cartList.add(cart);
}else{//该商品的存在
//先通过购物车获取当前购物车的商品明细对象
List orderItemList = cart.getOrderItemList();
//判断该商品是否存在于该购物车商品的明细列表中
TbOrderItem orderItem = searchOrderIdByItemId(orderItemList,itemId);
if (orderItem==null){//在商品列表中不存在
//如果该商品不存在购物车明细列表中
//则创建购物车明细对象,在添加到购物车明细里列表中
orderItem = createOrderItem(item,num);
//在添加到购物车明细里列表中
orderItemList.add(orderItem);
}else{//存在于商品列表中
//修改购物车明细对象的数量和小计金额
orderItem.setNum(orderItem.getNum()+num);
//小计金额
orderItem.setTotalFee(new BigDecimal(orderItem.getPrice().doubleValue()*orderItem.getNum()));
//如果商品数量小于1 则删除该商品
if(orderItem.getNum()<1){
orderItemList.remove(orderItem);
}
//如果购物车商品明细列表中没有商品了, 则直接从购物车里列表中移除
if (orderItemList.size()<=0){
cartList.remove(cart);
}
}
}
return cartList;
}
//判断该商品是否存在于该购物车商品的明细列表中
private TbOrderItem searchOrderIdByItemId(List orderItems, Long itemId) {
for (TbOrderItem orderItem : orderItems) {
if(orderItem.getItemId().longValue()==itemId.longValue()){//存在商品列表中
return orderItem;
}
}
return null;
}
//创建商品的明细对象
private TbOrderItem createOrderItem(TbItem item, Integer num) {
//优化操作
//创建商品的数量如果为负数
if(num<1){
throw new RuntimeException("新添加商品到购物车,商品数量不能小于1");
}
/*
`item_id` bigint(20) NOT NULL COMMENT '商品id',
`goods_id` bigint(20) DEFAULT NULL COMMENT 'SPU_ID',
`title` varchar(200) COLLATE utf8_bin DEFAULT NULL COMMENT '商品标题',
`price` decimal(20,2) DEFAULT NULL COMMENT '商品单价',
`num` int(10) DEFAULT NULL COMMENT '商品购买数量',
`total_fee` decimal(20,2) DEFAULT NULL COMMENT '商品总金额',
`pic_path` varchar(200) COLLATE utf8_bin DEFAULT NULL COMMENT '商品图片地址',
`seller_id` varchar(100) COLLATE utf8_bin DEFAULT NULL,
*/
TbOrderItem orderItem = new TbOrderItem();
orderItem.setItemId(item.getId());
orderItem.setGoodsId(item.getGoodsId());
orderItem.setTitle(item.getTitle());
orderItem.setPrice(item.getPrice());
orderItem.setNum(num);
orderItem.setTotalFee(new BigDecimal(item.getPrice().doubleValue()*num));
orderItem.setPicPath(item.getImage());
orderItem.setSellerId(item.getSellerId());
return orderItem;
}
//根据商品的id查询是否存在购物车对象
private Cart searchCartBySellerId(List cartList, String sellerId) {
for (Cart cart : cartList) {
if (cart.getSellerId().equals(sellerId)){
return cart;
}
}
return null;
}
//通过sessionid获得购物车列表
@Override
public List selectCartListFromRedis(String sessionId) {
//从redis中获取
List cartList = (List) redisTemplate.boundValueOps(sessionId).get();
//判断cartList是否为空,因为你要返回一个list列表,如果为空,前台就不能通过fastJson解析了
if (cartList==null){
//为空我们可以直接从新创建一个新的arrayList
cartList = new ArrayList<>();
}
return cartList;
}
//保存购物车列表到redis中
@Override
public void saveCartListToRedis(String sessionId, List cartList) {
redisTemplate.boundValueOps(sessionId).set(cartList,7L, TimeUnit.DAYS);
}
//合并登陆前购物车的数据到登陆后的购物车数据中
@Override
public List mergeCartList(List cartList_sessionId, List cartList_username) {
//注意我们只需要构建我们需要添加的数据,因为我们在上面已经做过判断,我们只需要,拼装数据即可
for (Cart cart : cartList_sessionId) {
//获取购车列表
List itemList = cart.getOrderItemList();
for (TbOrderItem orderItem : itemList) {
//获取num和ItemId的值
Integer num = orderItem.getNum();
Long itemId = orderItem.getItemId();
//购物车列表我们直接用登陆的就可以了
cartList_username= addItemToCartList(cartList_username,itemId,num);
}
}
return cartList_username;
}
//删除登陆前购物车的数据
@Override
public void deleteCartList(List cartList_sessionId) {
redisTemplate.delete(cartList_sessionId);
}
}
controller层:
思路分析:我们主要做两件事 1.购物车列表数据的展示 2.添加商品到购物车列表中 还有一个非常重要就是获取sessionid的方法,我们浏览器的存在的cookie是一次会话,我们想要用户的商品存储一周,我们必须后台进行,重新创建cookie,设置为一周过期,通过一个工具类
CookieUtil来获取和存储
@RestController
@RequestMapping("/cart")
public class CartController {
@Autowired
private HttpSession session;
@Autowired
private HttpServletRequest request;
@Autowired
private HttpServletResponse response;
@Reference
private CartService cartService;
/**
* 获得sessionid的方法
*/
private String getSessionId(){
//先尝试从"cartCookie"中获得sessionId信息
String sessionId = CookieUtil.getCookieValue(request, "cartList", "utf-8");
if (sessionId==null){
//在从浏览器中获取sessionid
sessionId = session.getId();
//将浏览器获取的sessionId保存一周
CookieUtil.setCookie(request,response,"cartList",sessionId,3600*24*7,"utf-8");
}
return sessionId;
}
/**
* 展示购物车列表数据
*/
@RequestMapping("/findCartList")
public List findCartList(){
//获取登陆人用户名
String username = SecurityContextHolder.getContext().getAuthentication().getName();
//未登陆时,基于sessionid从redis中获取购物车数据列表
String sessionId = getSessionId();
//从redis中获取
List cartList_sessionId = cartService.selectCartListFromRedis(sessionId);
if ("anonymousUser".equals(username)){//未登陆
System.out.println("selectCartListFromRedis by sessionId....");
return cartList_sessionId;
}else{//已登录
System.out.println("selectCartListFromRedis by username....");
List cartList_username = cartService.selectCartListFromRedis(username);
//用户登录前,如果已经添加商品到购物车列表中。
if(cartList_sessionId!=null&&cartList_sessionId.size()>0){//说明添加前已经存在商品了
//登陆后,将登陆前的购物车列表数据合并到登陆后的购物车列表中
cartList_username = cartService.mergeCartList(cartList_sessionId,cartList_username);
//将合并后的结果从新放到缓存中
cartService.saveCartListToRedis(username,cartList_username);
//清除合并前的购物车列表数据
cartService.deleteCartList(cartList_sessionId);
}
return cartList_username;
}
}
/**
* 添加商品到购物车
*/
@RequestMapping("/addItemToCartList")
public Result addItemToCartList(Long itemId, Integer num){
try {
//获取登陆人用户名
String username = SecurityContextHolder.getContext().getAuthentication().getName();
System.out.println(username);
//获取sessionId
String sessionId = getSessionId();
//1.查询购物车列表
List cartList = findCartList();
if ("anonymousUser".equals(username)){//未登陆
System.out.println("saveCartListToRedis by sessionId.....");
//2.添加商品到购物车
cartList = cartService.addItemToCartList(cartList,itemId, num);
//3.保存购物车列表到redis中
cartService.saveCartListToRedis(sessionId,cartList);
}else{//已登录
System.out.println("saveCartListToRedis by username.....");
cartService.saveCartListToRedis(username,cartList);
}
return new Result(true,"添加购物车成功");
} catch (RuntimeException e) {
e.printStackTrace();
return new Result(false,e.getMessage());
}
catch (Exception e) {
e.printStackTrace();
return new Result(false,"添加购物车失败");
}
}
}
前台实现:
service层:
//服务层
app.service('cartService',function($http){
//查找购物车列表数据
this.findCartList=function(){
return $http.get('cart/findCartList.do');
}
//查找购物车列表数据
this.addItemToCartList=function (itemId,num) {
return $http.get('cart/addItemToCartList.do?itemId='+itemId+'&num='+num)
}
});
controller层:
//控制层
app.controller('cartController' ,function($scope,$controller ,cartService){
$controller('baseController',{$scope:$scope});//继承
//读取列表数据绑定到表单中
$scope.findCartList=function(){
cartService.findCartList().success(
function(response){
$scope.cartList=response;
sum();
}
);
}
//添加商品到购物车
$scope.addItemToCartList=function (itemId,num) {
cartService.addItemToCartList(itemId,num).success(function (response) {
if (response.success){
//添加购物车工程
$scope.findCartList();
}else{
//添加购物车失败
alert(response.message)
}
})
}
//统计商品的数量和总计
sum=function () {
//总数量 和总金额
$scope.totalNum = 0;
$scope.totalMoney=0.00;
//遍历购物车列表
for (var i = 0;i<$scope.cartList.length;i++){
//获取购物车对象
var cart = $scope.cartList[i];
var orderItemList = cart.orderItemList;//获取商品明细列表
//遍历商品购物车明细列表
for(var j = 0;j
1.页面的设置:
注意:如果我们未登陆,则要添加购物车,我们需要获取用户名:则通过一个spring-security.xml的配置
//之前增加的放行,删掉,否则所有访问cart下的访问都不进入到springSecurity中
//但是我们需要购物车可以不登录也能增加到购物车中的操作,IS_AUTHENTICATED_ANONYMOUSLY和上面的配置区别在于,可以匿名访问springSecurity(加在入口点引用中,注意拦截规则小范围的在上面)
//在此拦截器上
2.页面跳转的问题
思路分析:我们设置一个login.html页面这个页面就做一件事,就是用于转发跳转到cart.html页面,因为我们在spring-sceurity.xml中没有放行,所以会先进入cas登陆界面,我们登陆后,会转发到cart.html页面
代码如下:
跳转页面