购物车服务-----功能实现逻辑2

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: 购物车服务-----功能实现逻辑2

(3)渲染界面数据

<div class="w" id="result">
  <div class="m succeed-box">
    <div th:if="${cartItem!=null}" class="mc success-cont">
      <div class="success-lcol">
        <div class="success-top"><b class="succ-icon"></b>
          <h3 class="ftx-02">商品已成功加入购物车</h3></div>
        <div class="p-item">
          <div class="p-img">
            <a href="/javascript:;" target="_blank"><img style="height: 60px;width:60px;"
                                                         th:src="${cartItem.image}"></a>
          </div>
          <div class="p-info">
            <div class="p-name">
              <a th:href="'http://item.saodaimall.com/'+${cartItem.skuId}+'.html'"
                 th:text="${cartItem.title}">TCL 55A950C 55英寸32核人工智能 HDR曲面超薄4K电视金属机身(枪色)</a>
            </div>
            <div class="p-extra"><span class="txt" th:text="'数量:'+${cartItem.count}">  数量:1</span></div>
          </div>
          <div class="clr"></div>
        </div>
      </div>
      <div class="success-btns success-btns-new">
        <div class="success-ad">
          <a href="/#none"></a>
        </div>
        <div class="clr"></div>
        <div class="bg_shop">
          <a class="btn-tobback" th:href="'http://item.saodaimall.com/'+${cartItem.skuId}+'.html'">查看商品详情</a>
          <a class="btn-addtocart" href="http://cart.saodaimall.com/cart.html"
             id="GotoShoppingCart"><b></b>去购物车结算</a>
        </div>
      </div>
    </div>
    <div th:if="${cartItem==null}" class="mc success-cont">
      <h2>购物车中无此商品</h2>
      <a href="http://saodaimall.com/">去购物</a>
    </div>
  </div>
</div>

6、去结算(获取购物车的所有购物项)

(1)前台去购物车结算按钮点击后发送请求/cart.html给后端

 <div class="bg_shop">
    <a class="btn-tobback" th:href="'http://item.saodaimall.com/'+${cartItem.skuId}+'.html'">查看商品详情</a>
    <a class="btn-addtocart" href="http://cart.saodaimall.com/cart.html"
       id="GotoShoppingCart"><b></b>去购物车结算</a>
 </div>

(2)购物车服务的CartController来处理/cart.html请求

/**
* 去购物车结算的请求
*
* 浏览器有一个cookie:user-key 标识用户的身份,一个月过期
* 如果第一次使用jd的购物车功能,都会给一个临时的用户身份:
* 浏览器以后保存,每次访问都会带上这个cookie;
*
* 登录:session有
* 没登录:按照cookie里面带来user-key来做
* 第一次,如果没有临时用户,自动创建一个临时用户
*
* @return
*/
@GetMapping(value = "/cart.html")
public String cartListPage(Model model) throws ExecutionException, InterruptedException {
    //快速得到用户信息:id,user-key
    // UserInfoTo userInfoTo = CartInterceptor.toThreadLocal.get();
    CartVo cartVo = cartService.getCart();
    model.addAttribute("cart",cartVo);
    return "cartList";
    }

分流程:(封装CartVo对象,其实只封装了CartVo对象的购物项items)


判断用户有没有登录


已登录:(登录了还需要把没有登录时候的购物车中的商品也加进购物车来)


1>获取登录和未登录的Hash值(相当于拿到了这两种类型的购物车)


2>调用getCartItems方法来查询临时购物车所有的购物项


3>遍历获取到的临时购物车的所有购物项,调用addToCart方法来合并到用户登录了的购物车中


4>调用clearCartInfo方法来删除临时购物车的所有购物项


5>获取登录后的购物车数据【包含合并过来的临时购物车的所有购物项和登录后购物车的购物项】并封装到CartVo对象


未登录: 获取临时购物车里面的所有购物项封装到CartVo对象

  /**
     * 获取用户登录或者未登录购物车里所有的数据
     * @return
     * @throws ExecutionException
     * @throws InterruptedException
     */
    @Override
    public CartVo getCart() throws ExecutionException, InterruptedException {
        CartVo cartVo = new CartVo();
        UserInfoTo userInfoTo = CartInterceptor.toThreadLocal.get();
        //这里没有直接用userInfoTo==null来判断用户是否登录是因为这样判断不出来到底登录了还是没有登录
        //但是可以用userInfoTo.getUserId()!= null来判断出用户是否登录,userInfoTo.getUserId()只有登录了才会有值
        if (userInfoTo.getUserId() != null) {
            //1、登录了还需要把没有登录时候的购物车中的商品也加进购物车来
            String cartKey = CART_PREFIX + userInfoTo.getUserId();
            //临时购物车的键
            String temptCartKey = CART_PREFIX + userInfoTo.getUserKey();
            //2、如果临时购物车的数据还未进行合并
            List<CartItemVo> tempCartItems = getCartItems(temptCartKey);
            if (tempCartItems != null) {
                //临时购物车有数据需要进行合并操作
                for (CartItemVo item : tempCartItems) {
                    addToCart(item.getSkuId(),item.getCount());
                }
                //清除临时购物车的数据
                clearCartInfo(temptCartKey);
            }
            //3、获取登录后的购物车数据【包含合并过来的临时购物车的数据和登录后购物车的数据】
            List<CartItemVo> cartItems = getCartItems(cartKey);
            cartVo.setItems(cartItems);
        } else {
            //没登录
            String cartKey = CART_PREFIX + userInfoTo.getUserKey();
            //获取临时购物车里面的所有购物项
            List<CartItemVo> cartItems = getCartItems(cartKey);
            cartVo.setItems(cartItems);
        }
        return cartVo;
    }
    /**
     * 获取购物车里面的数据
     * @param cartKey redis中的外围map的key值
     * @return
     */
    private List<CartItemVo> getCartItems(String cartKey) {
        //获取购物车里面的所有商品
        BoundHashOperations<String, Object, Object> operations = redisTemplate.boundHashOps(cartKey);
        //获取这个Hash值对应的Map的所有value值
        List<Object> values = operations.values();
        if (values != null && values.size() > 0) {
            List<CartItemVo> cartItemVoStream = values.stream().map((obj) -> {
                String str = (String) obj;
                CartItemVo cartItem = JSON.parseObject(str, CartItemVo.class);
                return cartItem;
            }).collect(Collectors.toList());
            return cartItemVoStream;
        }
        return null;
    }
    /**
     * 添加商品到购物车
         购物车和购物项存入redis的结构用的是HashMap结构
             Hash值为cartKey ,表示购物车,其中的 map结构为: Map<String skuId,String cartItem>,表示购物项
             cartKey表示格式为saodaimall:cart:key,表示购物车,其中saodaimall:cart:key的saodaimall:cart:是一个固定前缀,key值有两种,如果登录了就是用户id(saodaimall:cart:1),没登录就是名为user-key的cookie的值(例如saodaimall:cart:a191459a-0f24-4e69-8dc7-a3f81de96202)
             Map<String skuId,String cartItem>作为外表示用户购物车的里的购物项,购物项中skuId为商品id作为key,values作为购物项的详细信息
     */
    @Override
    public CartItemVo addToCart(Long skuId, Integer num) throws ExecutionException, InterruptedException {
        //拿到要操作的购物车信息
        BoundHashOperations<String, Object, Object> cartOps = getCartOps();
        //判断Redis是否有该商品的信息
        String productRedisValue = (String) cartOps.get(skuId.toString());
        //如果没有就添加数据
        if (StringUtils.isEmpty(productRedisValue)) {
            //2、添加新的商品到购物车(redis)
            CartItemVo cartItemVo = new CartItemVo();
            //开启第一个异步任务
            CompletableFuture<Void> getSkuInfoFuture = CompletableFuture.runAsync(() -> {
                //1、远程查询当前要添加商品的信息
                R productSkuInfo = productFeignService.getInfo(skuId);
                SkuInfoVo skuInfo = productSkuInfo.getData("skuInfo", new TypeReference<SkuInfoVo>() {});
                //数据赋值操作
                cartItemVo.setSkuId(skuInfo.getSkuId());
                cartItemVo.setTitle(skuInfo.getSkuTitle());
                cartItemVo.setImage(skuInfo.getSkuDefaultImg());
                cartItemVo.setPrice(skuInfo.getPrice());
                cartItemVo.setCount(num);
            }, executor);
            //开启第二个异步任务
            CompletableFuture<Void> getSkuAttrValuesFuture = CompletableFuture.runAsync(() -> {
                //2、远程查询skuAttrValues组合信息
                List<String> skuSaleAttrValues = productFeignService.getSkuSaleAttrValues(skuId);
                cartItemVo.setSkuAttrValues(skuSaleAttrValues);
            }, executor);
            //等待所有的异步任务全部完成
            CompletableFuture.allOf(getSkuInfoFuture, getSkuAttrValuesFuture).get();
            String cartItemJson = JSON.toJSONString(cartItemVo);
            cartOps.put(skuId.toString(), cartItemJson);
            return cartItemVo;
        } else {
            //购物车有此商品,修改数量即可
            CartItemVo cartItemVo = JSON.parseObject(productRedisValue, CartItemVo.class);
            cartItemVo.setCount(cartItemVo.getCount() + num);
            //修改redis的数据
            String cartItemJson = JSON.toJSONString(cartItemVo);
            cartOps.put(skuId.toString(),cartItemJson);
            return cartItemVo;
        }
    }
/**
     * 登录后合并临时购物车后清空临时购物车的内容
     * @param cartKey
     */
    @Override
    public void clearCartInfo(String cartKey) {
        redisTemplate.delete(cartKey);
    }
package com.saodai.saodaimall.cart.vo;
import org.springframework.util.CollectionUtils;
import java.math.BigDecimal;
import java.util.List;
/**
*  购物车VO模型
* 需要计算的属性需要重写get方法,保证每次获取属性都会进行计算(这里没用@Data是因为要重写get方法)
**/
public class CartVo {
    /**
    * 购物车子项信息
    */
    List<CartItemVo> items;
    /**
    * 商品数量
    */
    private Integer countNum;
    /**
    * 商品类型数量
    */
    private Integer countType;
    /**
    * 商品总价
    */
    private BigDecimal totalAmount;
    /**
    * 减免价格
    */
    private BigDecimal reduce = new BigDecimal("0.00");;
    public List<CartItemVo> getItems() {
        return items;
    }
    public void setItems(List<CartItemVo> items) {
        this.items = items;
    }
    //获取购物车中所有商品数量总和
    public Integer getCountNum() {
        int count = 0;
        if (items != null && items.size() > 0) {
            for (CartItemVo item : items) {
                count += item.getCount();
            }
        }
        return count;
    }
    //获取购物车中所有商品类型总和
    public Integer getCountType() {
        int count = 0;
        if (items != null && items.size() > 0) {
            for (CartItemVo item : items) {
                count += 1;
            }
        }
        return count;
    }
    //获取购物车中所有商品价格总和
    public BigDecimal getTotalAmount() {
        BigDecimal amount = new BigDecimal("0");
        // 计算购物项总价
        if (!CollectionUtils.isEmpty(items)) {
            for (CartItemVo cartItem : items) {
                if (cartItem.getCheck()) {
                    amount = amount.add(cartItem.getTotalPrice());
                }
            }
        }
        // 计算优惠后的价格
        return amount.subtract(getReduce());
    }
    public BigDecimal getReduce() {
        return reduce;
    }
    public void setReduce(BigDecimal reduce) {
        this.reduce = reduce;
    }
}

(3)渲染购物车界面

<div class="One_ShopCon">
  <h1 th:if="${cart.items == null}">
    购物车还没有商品,<a href="http://saodaimall.com/">去购物</a>
  </h1>
  <ul th:if="${cart.items != null}">
    <li th:each="item:${cart.items}">
      <div>
      </div>
      <div>
        <ol>
          <li><input type="checkbox" th:attr="skuId=${item.skuId}" class="itemChecked" th:checked="${item.check}"></li>
          <li>
            <dt><img th:src="${item.image}" alt=""></dt>
            <dd style="width: 300px;">
              <p>
                <span th:text="${item.title}">TCL 55A950C 55英寸32核</span>
                <br/>
                <span th:each="attr:${item.skuAttrValues}" th:text="${attr}">尺码: 55时 超薄曲面 人工智能</span>
              </p>
            </dd>
          </li>
          <li>
            <p class="dj" th:text="'¥' + ${#numbers.formatDecimal(item.price,3,2)}">¥4599.00</p>
          </li>
          <li>
            <p th:attr="skuId=${item.skuId}">
              <span class="countOpsBtn">-</span>
              <span class="countOpsNum" th:text="${item.count}">5</span>
              <span class="countOpsBtn">+</span>
            </p>
          </li>
          <li style="font-weight:bold"><p class="zj">¥[[${#numbers.formatDecimal(item.totalPrice,3,2)}]]</p></li>
          <li>
            <p class="deleteItemBtn" th:attr="skuId=${item.skuId}">删除</p>
          </li>
        </ol>
      </div>
    </li>
  </ul >
</div>
<div class="One_ShopFootBuy fix1">
  <div>
    <ul>
      <li><input type="checkbox" class="allCheck"><span>全选</span></li>
      <li>删除选中的商品</li>
      <li>移到我的关注</li>
      <li>清除下柜商品</li>
    </ul>
  </div>
  <div>
    <font style="color:#e64346;font-weight:bold;" class="sumNum"> </font>&nbsp;
    <ul>
      <li><img src="/static/cart/img/buyNumleft.png" alt=""></li>
      <li><img src="/static/cart/img/buyNumright.png" alt=""></li>
    </ul>
  </div>
  <div>
    <ol>
      <li>总价:<span style="color:#e64346;font-weight:bold;font-size:16px;" class="fnt">¥[[${#numbers.formatDecimal(cart.totalAmount, 3, 2)}]]</span>
      <li>优惠:¥[[${#numbers.formatDecimal(cart.reduce,1,2)}]]</li>
    </ol>
  </div>
  <div>
    <button onclick="toTrade()" type="button">去结算</button>
  </div>
</div>


7、选中购物项

(1)选中购物项后发送请求/checkItem给后端

  <li><input type="checkbox" th:attr="skuId=${item.skuId}" class="itemChecked" th:checked="${item.check}"></li>
$(".itemChecked").click(function () {
    const skuId = $(this).attr("skuId");
    const checked = $(this).prop("checked");
    //如果框被选中了checked的值就是1,否则就是0
    location.href = "http://cart.saodaimall.com/checkItem?skuId=" + skuId + "&checked=" + (checked ? 1 : 0);
});  <li><input type="checkbox" th:attr="skuId=${item.skuId}" class="itemChecked" th:checked="${item.check}"></li>
$(".itemChecked").click(function () {
    const skuId = $(this).attr("skuId");
    const checked = $(this).prop("checked");
    //如果框被选中了checked的值就是1,否则就是0
    location.href = "http://cart.saodaimall.com/checkItem?skuId=" + skuId + "&checked=" + (checked ? 1 : 0);
});

(2)购物车服务的CartController来处理/checkItem请求

  /**
     * 商品是否选中
     * @param skuId
     * @param checked
     * @return
     */
    @GetMapping(value = "/checkItem")
    public String checkItem(@RequestParam(value = "skuId") Long skuId,
                            @RequestParam(value = "checked") Integer checked) {
        cartService.checkItem(skuId,checked);
        return "redirect:http://cart.saodaimall.com/cart.html";
    }
    /**
     * 商品是否选中
     */
    @Override
    public void checkItem(Long skuId, Integer check) {
        //查询购物车里面的商品
        CartItemVo cartItem = getCartItem(skuId);
        //修改商品状态
        cartItem.setCheck(check == 1?true:false);
        //序列化存入redis中
        String redisValue = JSON.toJSONString(cartItem);
        BoundHashOperations<String, Object, Object> cartOps = getCartOps();
        cartOps.put(skuId.toString(),redisValue);
    }
    /**
     * 根据skuId的值查询到redis中的购物项对象
     * @param skuId
     * @return
     */
    @Override
    public CartItemVo getCartItem(Long skuId) {
        //拿到要操作的购物车信息
        BoundHashOperations<String, Object, Object> cartOps = getCartOps();
        String redisValue = (String) cartOps.get(skuId.toString());
        CartItemVo cartItemVo = JSON.parseObject(redisValue, CartItemVo.class);
        return cartItemVo;
    }
    /**
     * 获取到我们要操作的购物车
         购物车和购物项存入redis的结构用的是HashMap结构
             Hash值为cartKey ,表示购物车,其中的 map结构为: Map<String skuId,String cartItem>,表示购物项
             cartKey表示格式为saodaimall:cart:key,表示购物车,其中saodaimall:cart:key的saodaimall:cart:是一个固定前缀,key值有两种,如果登录了就是用户id(saodaimall:cart:1),没登录就是名为user-key的cookie的值(例如saodaimall:cart:a191459a-0f24-4e69-8dc7-a3f81de96202)
             Map<String skuId,String cartItem>作为外表示用户购物车的里的购物项,购物项中skuId为商品id作为key,values作为购物项的详细信息
     */
    private BoundHashOperations<String, Object, Object> getCartOps() {
        //先从拦截器中得到当前用户信息
        UserInfoTo userInfoTo = CartInterceptor.toThreadLocal.get();
        //cartKey是存在redis的key值
        String cartKey = "";
        if (userInfoTo.getUserId() != null) {
            //用户登录了就用用户的id号
            cartKey = CART_PREFIX + userInfoTo.getUserId();
        } else {
            //没登录就用浏览器中名为user-key的cookie的值
            cartKey = CART_PREFIX + userInfoTo.getUserKey();
        }
        //绑定指定的key操作Redis(本质用的是 redisTemplate.opsForHash(),也就是哈希map,其中cartKey作为哈希值)
        BoundHashOperations<String, Object, Object> operations = redisTemplate.boundHashOps(cartKey);
        return operations;
    }

其实就是修改了购物项的checked属性的值,如果选中了就改成true,否则就是false,注意不是所有控制器处理请求后都会返回一个对象,比如这里就没有返回对象,只是重定向了一个请求redirect:http://cart.saodaimall.com/cart.html,相当于又发了请求/cart.html给控制器


8、改变购物项的数量

<p th:attr="skuId=${item.skuId}">
  <span class="countOpsBtn">-</span>
  <span class="countOpsNum" th:text="${item.count}">5</span>
  <span class="countOpsBtn">+</span>
</p>

(1)改变购物项的数量后发送请求/countItem给后端

$(".countOpsBtn").click(function () {
    const skuId = $(this).parent().attr("skuId");
    const num = $(this).parent().find(".countOpsNum").text();
    location.href = "http://cart.saodaimall.com/countItem?skuId=" + skuId + "&num=" + num;
    });


(2)购物车服务的CartController来处理/countItem请求

 /**
     * 改变商品数量
     * @param skuId
     * @param num
     * @return
     */
    @GetMapping(value = "/countItem")
    public String countItem(@RequestParam(value = "skuId") Long skuId,
                            @RequestParam(value = "num") Integer num) {
        cartService.changeItemCount(skuId,num);
        return "redirect:http://cart.saodaimall.com/cart.html";
    }
    /**
     * 修改购物项数量
     * @param skuId
     * @param num
     */
    @Override
    public void changeItemCount(Long skuId, Integer num) {
        //查询购物车里面的商品(从redis中拿到对应的商品项的数据)
        CartItemVo cartItem = getCartItem(skuId);
        cartItem.setCount(num);
        BoundHashOperations<String, Object, Object> cartOps = getCartOps();
        //序列化存入redis中
        String redisValue = JSON.toJSONString(cartItem);
        cartOps.put(skuId.toString(),redisValue);
    }

其实就是改变Redis缓存中的购物项的数量

9、删除商品项

<p class="deleteItemBtn" th:attr="skuId=${item.skuId}">删除</p>


(1)点击删除按钮后发送请求/deleteItem给后端

let deleteId = 0;
$(".deleteItemBtn").click(function () {
    deleteId = $(this).attr("skuId");
});
//删除购物车选项
function deleteItem() {
    location.href = "http://cart.saodaimall.com/deleteItem?skuId=" + deleteId;
}

(2)购物车服务的CartController来处理/deleteItem请求

/**
     * 删除商品信息
     * @param skuId
     * @return
     */
    @GetMapping(value = "/deleteItem")
    public String deleteItem(@RequestParam("skuId") Integer skuId) {
        cartService.deleteIdCartInfo(skuId);
        return "redirect:http://cart.saodaimall.com/cart.html";
    }
    /**
     * 删除购物项(删除商品信息)
     * @param skuId
     */
    @Override
    public void deleteIdCartInfo(Integer skuId) {
        BoundHashOperations<String, Object, Object> cartOps = getCartOps();
        cartOps.delete(skuId.toString());
    }
相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
6月前
|
前端开发 数据库 索引
前后端分离------后端创建笔记(05)用户列表查询接口(下)
前后端分离------后端创建笔记(05)用户列表查询接口(下)
|
5月前
|
前端开发 API
支付系统27-------梳理一下支付按钮,前端的代码
支付系统27-------梳理一下支付按钮,前端的代码
|
7月前
|
小程序
外卖小程序-购物车模块表结构设计和后端代码
外卖小程序-购物车模块表结构设计和后端代码
59 0
|
7月前
|
JavaScript
基础购物车功能
基础购物车功能
|
7月前
|
缓存 前端开发 JavaScript
若依框架中的权限控制逻辑 ---- 菜单
若依框架中的权限控制逻辑 ---- 菜单
655 0
|
SQL 前端开发 JavaScript
Layui框架实现用户系统-----增删改查
Layui框架实现用户系统-----增删改查
83 0
|
存储 小程序
小程序实现购物车功能
购物车功能是电商小程序中比较常见的功能之一,实现起来也比较简单。通过本文的介绍,我们可以学习到如何将用户所选的商品信息保存在本地,如何展示商品信息,如何计算商品总价和总数,以及如何提供用户对购物车中商品的操作。在实际开发中,还可以根据具体需求进行定制和优化,例如添加优惠券、满减活动等功能,提升用户购物体验。
321 0
|
存储 JSON 缓存
购物车服务-----功能实现逻辑1
购物车服务-----功能实现逻辑
127 0
|
JavaScript Java 数据库
订单服务-----功能实现逻辑2
订单服务-----功能实现逻辑2
206 0
|
缓存 NoSQL Redis
订单服务-----功能实现逻辑1
订单服务-----功能实现逻辑
89 0