购物车微服务:
1. 购物车功能分析
1.1 需求
需求描述:
- 用户可以在登录状态下将商品添加到购物车
- 放入数据库
- 放入redis(采用)
- 用户可以在未登录状态下将商品添加到购物车
- 放入localstorage
- 用户可以使用购物车一起结算下单
- 用户可以查询自己的购物车
- 用户可以在购物车中可以修改购买商品的数量。
- 用户可以在购物车中删除商品。
- 在购物车中展示商品优惠信息
- 提示购物车商品价格变化
1.2 业务分析
在需求描述中,不管用户知否登录,都需要实现加入购物车功能,那么已登录和未登录下,购物车数据应存放在哪里呢?
未登录购物车
用户如果未登录,将数据保存在服务端存在一些问题:
- 无法确定用户身份,需要借助于客户端存储识别身份
- 服务端存储数据压了增加,而且可能是无效数据
那么我们应该把数据保存在客户端,这样每个用户保存自己的数据,就不存在身份识别的问题了,而且也解决了服务端数据存储压力问题。
已登录购物车
用户登录时,数据保存在哪里呢?
我们首先想到的是数据库,不过购物车数据比较特殊,读和写比较频繁,存储数据库压力会比较大。因此我们可以考虑存入Redis中。
1.3 流程图
这幅图主要描述了两个功能:新增商品到购物车、查询购物车。
新增商品:
- 判断是否登录
- 是:则添加商品到后台Redis中
- 否:则添加商品到本地的Localstorage
无论哪种新增,完成后都需要查询购物车列表:
- 判断是否登录
- 否:直接查询localstorage中数据并展示
- 是:已登录,则需要先看本地是否有数据,
- 有:需要提交到后台添加到redis,合并数据,而后查询
- 否:直接去后台查询redis,而后返回
2.未登录购物车-- Localstorage
2.1 购物车的数据结构
首先分析一下未登录购物车的数据结构。
我们看下页面展示需要什么数据:
因此每一个购物车信息,都是一个对象,包含:
{
skuId:2131241,
title:"小米6",
image:"",
price:190000,
num:1,
ownSpec:"{"机身颜色":"陶瓷黑尊享版","内存":"6GB","机身存储":"128GB"}"
}
注:这里不能根据id查询价格等信息,是因为这些信息都应保存的是刚加入购物车时的数据,若此时从数据库去查,有可能价格等数据信息会变,或者商品下架,或者规格参数变了,去数据库查也不一定能一一对应,因此应把加入数据库那一刻的信息做一个快照。
另外,购物车中不止一条数据,因此最终会是对象的数组。即:
[
{
...},{
...},{
...}
]
2.2 web本地存储
知道了数据结构,下一个问题,就是如何保存购物车数据。前面我们分析过,可以使用Localstorage来实现。Localstorage是web本地存储的一种,那么,什么是web本地存储呢?
2.2.1 什么是web本地存储?
web本地存储主要有两种方式:
- LocalStorage:localStorage 方法存储的数据没有时间限制。第二天、第二周或下一年之后,数据依然可用。
- SessionStorage:sessionStorage 方法针对一个 session 进行数据存储。当用户关闭浏览器窗口后,数据会被删除。
我们采用的当然是localstorage,关闭浏览器商品信息依然存在。
2.2.2 LocalStorage的用法
语法非常简单:
localStorage.setItem("key","value"); // 存储数据
localStorage.getItem("key"); // 获取数据
localStorage.removeItem("key"); // 删除数据
注意:localStorage和SessionStorage都只能保存字符串。
不过,在我们的common.js中,已经对localStorage进行了简单的封装:
示例:
2.3 添加购物车
2.3.1 页面分析
我们看下商品详情页:
点击加入购物车会跳转到购物车页面:cart.html,用户信息都保存在localstorage中,结果如下:
刷新购物车页面,却发现报错了:浏览器发了一次请求
可是,明明localstorage中有完整信息(下图)啊,为什么还要再去查一次?原因:购物车中加入的信数据信息只是当下那一刻的信息,但是这些信息胡随着时间变化,我们需要查一下价格是否发生了变化,商品有没有下架等,若商品价格发生变化了,我们可以给与友好提示
2.3.2 实现查询所有sku接口
我们需要在商品微服务中添加该接口:
- 请求方式:get
- 请求路径:/sku/list
- 请求参数:sku的id集合
- 返回结果:sku集合
GoodsController
//根据sku的id集合查询所有的sku
@GetMapping("/sku/list/ids")
public ResponseEntity<List<Sku>> querySkuByIds(@RequestParam("ids") List<Long> ids){
return ResponseEntity.ok(goodsService.querySkuByIds(ids));
}
GoodsService
//根据sku的id集合查询所有的sku
public List<Sku> querySkuByIds(List<Long> ids) {
List<Sku> skus = skuMapper.selectByIdList(ids);
if(CollectionUtils.isEmpty(skus)){
throw new LyException(ExceptionEnum.SKU_NOT_FOUND);
}
//查询库存
List<Stock> stockList = stockMapper.selectByIdList(ids);
if(CollectionUtils.isEmpty(stockList))
throw new LyException(ExceptionEnum.STOCK_NOT_FOUND);
//把stock变成一个map,其key:skuId,值:库存值
Map<Long, Integer> stockMap = stockList.stream().collect(Collectors.toMap(Stock::getSkuId, Stock::getStock));
skus.forEach(s ->s.setStock(stockMap.get(s.getId())));
return skus;
}
刷新页面,完成查询
之后我们在数据库中修改一下商品价格,刷新页面可以看到友好提示
总结:未登录情况下,我们都是在localstorage中来完成,从ly.store可以体现出。
3. 登录购物车-- Redis
3.1 搭建项目
3.1.1 引入依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>leyou</art