A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

© 金烁 初级黑马   /  2019-8-14 17:31  /  1219 人查看  /  0 人回复  /   0 人收藏 转载请遵从CC协议 禁止商业使用本文

第8章 购物车解决方案学习目标:



ps:做好敲吐 的准备
  • 完成购物车列表功能
  • 完成购物车勾选逻辑
  • 完成满减优惠金额计算

1. 购物车列表1.1 需求分析
(1)实现购物车列表的显示。购物车必须登录后才可以访问。
(2)从商品详细页点击“加入购物车按钮”,将商品添加到购物车。
(3)点击购物车列表项中数量的加减按钮实现对数量的修改。
(4)删除购物车中某条记录。
file://C:\Users\jinli\Desktop\青橙\day16\讲义\img\8-1.png?lastModify=1565774998
1.2 实现思路
​     使用redis(hash类型数据)实现购物车的存储。大key为  CacheKey.CART_LIST ,当前登录名作为小key。购物车存储结构如下图:
file://C:\Users\jinli\Desktop\青橙\day16\讲义\img\8-2.png?lastModify=1565774998
每个用户的购物车就是一个list集合,集合中是map类型的数据,有两个属性,一个是item,表示购物车明细数据,另一个是checked,用于存储购物车是否被选中。
每个用户的购物车类型是 List<Map<String, Object>> 。
购物车列表采用前端渲染(vue.js)的方式。
1.3 后端代码1.3.1 购物车列表
(1)在接口工程中的order包下创建服务接口
/**
* 购物车服务
*/
public interface CartService {

    /**
     * 从redis中提取购物车
     * @param username
     * @return
     */
    public List<Map<String,Object>> findCartList(String username);
}
(2)CacheKey枚举增加CART_LIST用于存储购物车列表 , 在qingcheng_service_order中创建服务实现类  
@Service
public class CartServiceImpl implements CartService {

    @Autowired
    private RedisTemplate redisTemplate;

    @Override
    public List<Map<String, Object>> findCartList(String username) {
        System.out.println("从redis中提取购物车"+username);
        List<Map<String,Object>> cartList = (List<Map<String,Object>>) redisTemplate.boundHashOps(CacheKey.CART_LIST).get(username);
        if(cartList==null){
            return new ArrayList<>();
        }
        return cartList;
    }
}
(3)在qingcheng_web_portal工程添加CartController

@RestController
@RequestMapping("/cart")
public class CartController {

    @Reference
    private CartService cartService;

    /**
     * 从redis中提取购物车
     * @return
     */
    @GetMapping("/findCartList")
    public List<Map<String, Object>> findCartList(){
        String username = SecurityContextHolder.getContext().getAuthentication().getName();
        List<Map<String, Object>> cartList = cartService.findCartList(username);
        return cartList;
    }
}1.3.2 添加商品到购物车
实现思路:遍历购物车,如果购物车列表中不存在该商品则添加,存在则累加数量。
(1)CartService接口新增方法定义

    /**
     * 添加商品到购物车
     * @param username
     * @param skuId
     * @param num
     */
    public void addItem(String username, String  skuId, Integer num);
(2)CartServiceImpl实现此方法
    @Reference
    private SkuService skuService;

    @Reference
    private CategoryService categoryService;

    @Override
    public void addItem(String username, String skuId, Integer num) {
        //实现思路: 遍历购物车,如果购物车中存在该商品则累加数量,如果不存在则添加购物车项
        //获取购物车
        List<Map<String, Object>> cartList = findCartList(username);

        boolean flag=false;//是否在购物车中存在
        for( Map map:cartList  ){
            
            OrderItem orderItem= (OrderItem)map.get("item");
            if(orderItem.getSkuId().equals(skuId)){  //购物车存在该商品
               
                //业务逻辑问题
                if(orderItem.getNum()<=0){ //如果数量小于等于0
                    cartList.remove(map);//购物车项删除
                    boolean flag= true;
                    break;
                }
                //业务逻辑问题
               
               
               
                int weight = orderItem.getWeight() / orderItem.getNum();  //单个商品重量

                orderItem.setNum( orderItem.getNum()+num ); //数量累加
                orderItem.setMoney( orderItem.getNum()*orderItem.getPrice() );//金额计算
                orderItem.setWeight( weight*orderItem.getNum() );//重量计算

                if(orderItem.getNum()<=0){ //如果数量小于等于0
                    cartList.remove(map);//购物车项删除
                }

                flag=true;
                break;
            }
        }
        //如果购物车中没有该商品,则添加
        if(flag==false){

            Sku sku = skuService.findById(skuId);
            if(sku==null){
                throw new RuntimeException("商品不存在");
            }
            if(!"1".equals(sku.getStatus())){
                throw new RuntimeException("商品状态不合法");
            }
            if(num<=0){  //数量不能为0或负数
                throw new RuntimeException("商品数量不合法");
            }

            OrderItem orderItem=new OrderItem();


            orderItem.setSkuId(skuId);
            orderItem.setSpuId(sku.getSpuId());
            orderItem.setNum(num);
            orderItem.setImage(sku.getImage());
            orderItem.setPrice(sku.getPrice());
            orderItem.setName(sku.getName());
            orderItem.setMoney( orderItem.getPrice()*num );//金额计算
            if(sku.getWeight()==null){
                sku.setWeight(0);
            }
            orderItem.setWeight(sku.getWeight()*num); //重量计算


            //商品分类
            orderItem.setCategoryId3(sku.getCategoryId());
            Category category3 = (Category)redisTemplate.boundHashOps(CacheKey.CATEGORY).get(sku.getCategoryId());
            if(category3==null){
                category3 = categoryService.findById(sku.getCategoryId()); //根据3级分类id查2级
                redisTemplate.boundHashOps(CacheKey.CATEGORY).put(sku.getCategoryId(), category3 );
            }
            orderItem.setCategoryId2(category3.getParentId());
            Category category2 = (Category)redisTemplate.boundHashOps(CacheKey.CATEGORY).get(category3.getParentId());
            if(category2==null ){
                category2 = categoryService.findById(category3.getParentId()); //根据2级分类id查询1级
                redisTemplate.boundHashOps(CacheKey.CATEGORY).put(category3.getParentId(), category2 );
            }
            orderItem.setCategoryId1(category2.getParentId());

            Map map=new HashMap();
            map.put("item",orderItem);
            map.put("checked",true);//默认选中

            cartList.add(map);
        }
        redisTemplate.boundHashOps(CacheKey.CART_LIST).put(username,cartList);
    }  
(3)qingcheng_web_portal的CartController新增方法

    /**
     * 添加商品到购物车
     * @param skuId 商品id
     * @param num 数量
     * @return
     */
    @GetMapping("/addItem")
    public Result addItem(String skuId,Integer num){
        String username=SecurityContextHolder.getContext().getAuthentication().getName();
        cartService.addItem(username,skuId,num);
        return new Result();
    }
1.4 前端代码1.4.1 购物车列表
(1)拷贝cart.html到qingcheng_web_portal
(2)cart.html增加js脚本

<script src="/js/vue.js"></script>
<script src="/js/axios.js"></script>
<script>
    new Vue({
        el: '#app',
        data(){
            return {
                cartList: {}
            }
        },
        created(){
            this.findCartList();//查询列表
        },
        methods:{
            findCartList(){
                axios.get(`/cart/findCartList.do`).then(response => {
                    this.cartList=response.data;
                });
            }
        }
    })
</script>
(3)为购物车的div增加id       

<div id="app">
修改cart.html购物车列表部分代码
<div class="cart-list" v-for="cart in cartList">
    <ul class="goods-list yui3-g">
        <li class="yui3-u-1-24">
            <input type="checkbox" name="chk_list"  v-model="cart.checked" />
        </li>
        <li class="yui3-u-6-24">
            <div class="good-item">
                <div class="item-img"><img :src="cart.item.image" /></div>
                <div class="item-msg">{{cart.item.title}}</div>
            </div>
        </li>
        <li class="yui3-u-5-24">
            <div class="item-txt"></div>
        </li>
        <li class="yui3-u-1-8"><span class="price">{{(cart.item.price/100).toFixed(2)}}</span></li>
        <li class="yui3-u-1-8">
            <a  class="increment mins" >-</a>
            <input autocomplete="off" type="text" v-model="cart.item.num" minnum="1" class="itxt" />
            <a  class="increment plus" >+</a>
        </li>
        <li class="yui3-u-1-8"><span class="sum">{{(cart.item.price*cart.item.num/100).toFixed(2)}}</span></li>
        <li class="yui3-u-1-8">
            <a href="#none">删除</a><br />
            <a href="#none">移到收藏</a>
        </li>
    </ul>
</div>1.4.2 更改数量与删除
(1)cart.html 增加方法
addItem(skuId,num){
    axios.get(`/cart/addItem.do?skuId=${skuId}&num=${num}`).then(response => {
        this.findCartList();
    });
}
(2)加减号调用方法
<a  class="increment mins" @click="addItem(cart.item.skuId,-1)">-</a>
<input autocomplete="off" type="text" v-model="cart.item.num" minnum="1" class="itxt" />
<a  class="increment plus" @click="addItem(cart.item.skuId,1)">+</a>
(3)修改删除链接  。传递数量的相反数就是实现删除的功能。

<a @click="addItem(cart.item.skuId,-cart.item.num)" >删除</a><br />1.5 与商品详细页对接
(1)在CartController新增方法

    @RequestMapping("/buy")
    public void buy(HttpServletResponse response,String skuId,Integer num) throws Exception{
        String username=SecurityContextHolder.getContext().getAuthentication().getName();
        cartService.addItem(username,skuId,num);
        response.sendRedirect("/cart.html");
    }
这个方法实现了添加商品到购物车并重定向到购物车页面的功能。
在浏览器上测试   http://localhost:9102/cart/buy.do?skuId=100000030326&num=1
(2)修改item.html模板  ,js代码如下:
    new Vue({
        el:'#app',
        data(){
            return {
                skuId:/*[[${sku.id}]]*/,
                price:0,
                num:1
            }
        },
        created(){
            //读取价格
            axios.get('http://localhost:9102/sku/price.do?id='+this.skuId).then(response=>{
                this.price=(response.data/100).toFixed(2);
            })
        },
        methods:{
            addNum(num){
                this.num=this.num+num;
                if(this.num<=0){
                    this.num=1;
                }
            },
            toCartList(){
                //添加商品到购物车
                location.href=`http://localhost:9102/cart/buy.do?skuId=${this.skuId}&num=${this.num}`;
            }

        }

    });
html部分
<div class="summary-wrap">
    <div class="fl title">
        <div class="control-group">
            <div class="controls">
                <input autocomplete="off"  v-model="num"  minnum="1" class="itxt" />
                <a @click="addNum(1)" class="increment plus">+</a>
                <a @click="addNum(-1)" class="increment mins">-</a>
            </div>
        </div>
    </div>
    <div class="fl">
        <ul class="btn-choose unstyled">
            <li>
                <a @click="toCartList()"  class="sui-btn  btn-danger addshopcar">加入购物车</a>
            </li>
        </ul>
    </div>
</div>
2. 购物车勾选逻辑2.1 需求分析
点击购物车前面的复选框即选中该购物车项,选中后需要保存该选中状态。
购物车下方显示选择的商品数量以及合计金额。
点击删除按钮,实现对选中购物车的删除。
2.2 代码实现2.2.1 保存勾选状态
实现思路:在页面点击复选框后向后端提交ajax请求,提交的参数为当前行的skuId和选中状态,后端接受到请求后更新redis中的购物车状态。
代码实现:
(1)CartService新增方法定义

/**
* 更新选中状态
* @param username
* @param skuId
* @param checked
*/
public boolean updateChecked(String username, String skuId,boolean checked);
(2)CartServiceImpl实现此方法

@Override
public boolean updateChecked(String username, String skuId, boolean checked) {
    //获取购物车
    List<Map<String,Object>> cartList= findCartList(username);
    //判断缓存中是含有已购物商品
    boolean isOk=false;
    for (Map map:cartList) {
        OrderItem orderItem=(OrderItem)map.get("item");
        if(orderItem.getSkuId().equals(skuId)){
            map.put("checked",checked);
            isOk=true;//执行成功
            break;
        }
    }
    if(isOk){
        redisTemplate.boundHashOps(CacheKey.CART_LIST).put(username,cartList);//存入缓存
    }
    return isOk;
}
(3)CartController新增方法

/**
* 更改购物车项选中状态
* @param skuId
* @param checked
* @return
*/
@GetMapping("/updateChecked")
public Result updateChecked(String skuId, boolean checked){
    String username = SecurityContextHolder.getContext().getAuthentication().getName();
    cartService.updateChecked(username,skuId,checked);
    return new Result();
}
(4)cart.html新增方法
updateChecked(skuId,checked){
    axios.get(`/cart/updateChecked.do?skuId=${skuId}&checked=${checked}`).then(
      response => {});
}
(5)修改cart.html ,为复选框绑定点击事件

<input type="checkbox" name="chk_list"  v-model="cart.checked" @click="updateChecked(cart.item.skuId,!cart.checked)"  />2.2.2 合计金额计算
实现思路:使用前端的代码实现合计金额
代码实现:
(1)修改cart.html ,新增属性

totalNum:0,//总数量
totalMoney:0//总金额
(2)新增方法
count(){
    this.totalNum=0;  //数量
    this.totalMoney=0; //金额
    //循环购物车
    for(let i=0;i<this.cartList.length;i++){
        if(this.cartList.checked){ //判断如果选中,数量和金额累加
            this.totalNum+= this.cartList.item.num;
            this.totalMoney+= this.cartList.item.money;
        }
    }
}
(3)在findCartList和updateChecked方法的回调处调用count方法。
(4)修改数量和金额的显示`
<div class="chosed">已选择<span>{{totalNum}}</span>件商品</div>
<div class="sumprice">
    <span><em>总价(不含运费) :</em><i class="summoney">¥{{(totalMoney/100).toFixed(2)}}</i></span>
    <span><em>已节省:</em><i></i></span>
</div>2.2.3 删除勾选项
实现思路:使用 stream 获取没选择的购物车列表,覆盖redis 现有的购物车。
代码实现:
(1)CartService新增方法

    /**
     * 删除选中的购物车
     * @param username
     */
    public void deleteCheckedCart(String username);
(2)CartServiceImpl实现方法
    @Override
    public void deleteCheckedCart(String username) {
        //获取未选中的购物车
        List<Map<String, Object>> cartList = findCartList(username).stream()
                .filter(cart -> (boolean) cart.get("checked") == false)
                .collect(Collectors.toList());
        redisTemplate.boundHashOps(CacheKey.CART_LIST).put(username,cartList);//存入缓存
    }
(3)CartController新增方法

    /**
     * 删除选中的购物车
     */
    @GetMapping("/deleteCheckedCart")
    public Result deleteCheckedCart(){
        String username = SecurityContextHolder.getContext().getAuthentication().getName();
        cartService.deleteCheckedCart(username);
        return new Result();
    }
(4)删除选中的购物车

deleteCheckedCart(){ //删除选择的商品
    axios.get(`/cart/deleteCheckedCart.do`).then(response => {
        this.findCartList();
    });
}
(5)删除选中的商品

<a @click="deleteCheckedCart()">删除选中的商品</a>
3. 满减优惠金额计算3.1 需求分析
我们采用满减的规则进行优惠金额的计算。例如:某品类满XX元减XX元  。
我们需要对购物车的商品按照品类进行分组,某个品类如果达到了优惠设置的消费额度,则按照设置的优惠金额进行减免。
优惠计算有翻倍和不翻倍两种。例如 ,满100元减30元。如果消费者花了320元,不翻倍是减30元,翻倍是减90元。
3.2 实现思路
首先我们先看一下满减优惠规则表  tb_preferential
字段名称
字段含义
字段类型
字段长度
备注

idIDINT
buy_money消费金额INT
pre_money优惠金额INT
category_id商品分类IDBIGINT
start_time活动开始日期DATE
end_time活动截至日期DATE
state状态VARCHAR1 有效   0无效
type类型VARCHAR1不翻倍 2翻倍
PS:数据库中的 tb_preferential数据有问题,记得一定要修改为所操作的商品数据类型。
主要修改的字段数据为:商品分类Id、开始时间和截止时间、消费金额、优惠金额
实现思路:
(1)创建一个方法,可以根据商品分类id和消费金额计算优惠金额
(2)对购物车按商品分类进行分组,统计每个品类的消费额,调用第1步的方法得到优惠金额,将每个品类的优惠金额累加起来,得到总的优惠金额。
青橙是自运营的电商系统,不是电商平台,所有的商品都是青橙系统自己定义。
满减的粒度:按照商品的分类来进行满减。
3.3 代码实现3.3.1 根据分类和消费额查询优惠金额
(1)PreferentialService新增方法定义

/**
* 根据分类和消费额查询优惠金额
* @param categoryId
* @param money
* @return
*/
public int findPreMoneyByCategoryId(Integer categoryId,int money);
(2)PreferentialServiceImpl实现此方法
@Override
public int findPreMoneyByCategoryId(Integer categoryId, int money) {
   
    //1.根据用户的购买物品来查询满减是数据
    //1.1 筛选满减数据
    Example example=new Example(Preferential.class);
    Example.Criteria criteria = example.createCriteria();
    criteria.andEqualTo("state","1");//*状态
    criteria.andEqualTo("categoryId",categoryId);
    criteria.andLessThanOrEqualTo("buyMoney",money);//*小于等于优惠金额
    criteria.andGreaterThanOrEqualTo("endTime",new Date());//*截至日期大于等于当前日期
    criteria.andLessThanOrEqualTo("startTime",new Date());//*开始日期小于等于当前日期
   
    example.setOrderByClause("buy_money desc");//按照购买金额降序排序
   
    //1.2 根据筛选条件查询出满减数据
    List<Preferential> preferentials = preferentialMapper.selectByExample(example);
   
   
    //2. 从查询出的满减数据进行满减操作
    //2.1 有满减数据
    if(preferentials.size()>=1){
        
        Preferential preferential = preferentials.get(0);
        
        // 2.2.1 进行满减的不翻倍操作
        if("1".equals(preferential.getType())){//不翻倍
            return preferential.getPreMoney();//返回优惠的金额
        }else{ //翻倍
            //2.2.2 进行满减的翻倍操作
            int multiple=  money/preferentials.get(0).getBuyMoney();
            return preferential.getPreMoney()*multiple;
        }
        
    }else{
         //2.2 没有满减数据
        return 0;
    }
}   3.3.2 计算当前购物车优惠金额
(1)修改CartService,新增计算优惠金额的方法定义

/**
* 计算当前选中的购物车的优惠金额
* @param
* @return
*/
public int preferential(String username);
(2)CartServiceImpl实现该方法

@Autowired
private PreferentialService preferentialService;

@Override
public int preferential(String username) {
    //获取选中的购物车  List<OrderItem>  List<Map>
    List<OrderItem> orderItemList = findCartList(username).stream()
            .filter(cart -> (boolean) cart.get("checked") == true)
            .map(cart -> (OrderItem) cart.get("item"))
            .collect(Collectors.toList());
   
   
   
    //按分类聚合统计每个分类的金额    group by
    Map<Integer, IntSummaryStatistics> cartMap = orderItemList.stream()
            .collect(Collectors.groupingBy(OrderItem::getCategoryId3, Collectors.summarizingInt(OrderItem::getMoney)));
   
   
   
   
    //循环结果,统计每个分类的优惠金额,并累加
    int allPreMoney=0;//累计优惠金额
    for( Integer categoryId:  cartMap.keySet() ){
        // 获取品类的消费金额
        int money = (int)cartMap.get(categoryId).getSum();
        int preMoney = preferentialService.findPreMoneyByCategoryId(categoryId, money); //获取优惠金额
        System.out.println("分类:"+categoryId+"  消费金额:"+ money+  " 优惠金额:" + preMoney );
        allPreMoney+=   preMoney;
    }
    return allPreMoney;
}
(3)CartController新增方法

/**
* 计算当前选中的购物车的优惠金额
* @param
* @return
*/
@GetMapping("/preferential")
public Map preferential(){
    String username = SecurityContextHolder.getContext().getAuthentication().getName();
    int preferential = cartService.preferential(username);
    Map map=new HashMap();
    map.put("preferential",preferential);
    return map;
}
(4)修改cart.html,新增属性

preferential:0//优惠金额
修改count方法,追加以下代码,实现优惠金额的计算

//计算优惠金额
axios.get(`/cart/preferential.do`).then(response => {
    this.preferential= response.data.preferential;
    this.totalMoney=this.totalMoney-this.preferential;//最后的总金额
});
4.补充
  • JDK8的Stream流操作
    https://www.runoob.com/java/java8-streams.html
    https://www.ibm.com/developerworks/cn/java/j-lo-java8streamapi/index.html
  • JDK8的方法引用操作
    https://www.runoob.com/java/java8-method-references.html
  • JDK 8 SummaryStatistics类
    https://dzone.com/articles/jdk-8-summarystatistics


0 个回复

您需要登录后才可以回帖 登录 | 加入黑马