当前位置 : 主页 > 网络编程 > 其它编程 >

乐优商城(十二)购物车

来源:互联网 收集:自由互联 发布时间:2023-07-02
文章目录1.搭建购物车微服务2.购物车功能分析2.1需求分析2.2技术选型2.3流程图3.未登录购物车3.1购物车数据结构3.2获取购物数量3.3添加购物车3.4渲染购物车页面3. 文章目录 1. 搭建购物车
文章目录1.搭建购物车微服务2.购物车功能分析2.1需求分析2.2技术选型2.3流程图3.未登录购物车3.1购物车数据结构3.2获取购物数量3.3添加购物车3.4渲染购物车页面3.

文章目录

  • 1. 搭建购物车微服务
  • 2. 购物车功能分析
    • 2.1 需求分析
    • 2.2 技术选型
    • 2.3 流程图
  • 3. 未登录购物车
    • 3.1 购物车数据结构
    • 3.2 获取购物数量
    • 3.3 添加购物车
    • 3.4 渲染购物车页面
      • 3.4.1 封装判断用户的登录状态方法
      • 3.4.2 查询购物车
      • 3.4.3 渲染页面
    • 3.5 修改数量
    • 3.6 删除商品
  • 4. 已登录购物车
    • 4.1 流程分析
    • 4.2 实现解析用户信息
    • 4.3 后台购物车设计
    • 4.4 添加购物车
      • 4.4.1 前端发起请求
      • 4.4.2 实体类
      • 4.4.3 FeignClient
      • 4.4.4 Controller
      • 4.4.5 Service
      • 4.4.6 最终目录结构
      • 4.4.7 测试
    • 4.5 查询购物车
      • 4.5.1 前端发起请求
      • 4.5.2 Controller
      • 4.5.3 Service
      • 4.5.4 测试
    • 4.6 修改数量
      • 4.6.1 前端发起请求
      • 4.6.2 Controller
      • 4.6.3 Service
    • 4.7 删除商品
      • 4.7.1 前端发起请求
      • 4.7.2 Controller
      • 4.7.3 Service
1. 搭建购物车微服务
  • 右键 leyou 项目 > New Module > Maven > Next

  • 填写项目信息 > Next

    《乐优商城(十二)购物车》

  • 填写保存的位置 > Finish

    《乐优商城(十二)购物车》

  • 添加依赖

    leyou com.leyou.parent 1.0-SNAPSHOT 4.0.0 com.leyou.cart leyou-cart 1.0-SNAPSHOT org.springframework.boot spring-boot-starter-web org.springframework.cloud spring-cloud-starter-netflix-eureka-client org.springframework.cloud spring-cloud-starter-openfeign org.springframework.boot spring-boot-starter-data-redis com.leyou.item leyou-item-interface 1.0-SNAPSHOT com.leyou.common leyou-common 1.0-SNAPSHOT

  • 编写配置文件 application.yaml

    server: port: 8088spring: application: name: cart-service redis: host: 192.168.222.132eureka: client: service-url: defaultZone: http://localhost:10086/eureka registry-fetch-interval-seconds: 10 instance: lease-renewal-interval-in-seconds: 5 lease-expiration-duration-in-seconds: 15

  • 编写启动类

    @EnableDiscoveryClient@EnableFeignClients@SpringBootApplicationpublic class LeyouCartApplicaion { public static void main(String[] args) { SpringApplication.run(LeyouCartApplicaion.class, args); }}

  • 最终目录结构

    《乐优商城(十二)购物车》

  • 在 leyou-gateway 的 applicaton.yaml 中添加路由配置

    zuul: prefix: /api routes: item-service: /item/** search-service: /search/** user-service: /user/** auth-service: /auth/** cart-service: /cart/**

  • 2. 购物车功能分析

    2.1 需求分析

    • 用户可以在登录状态下将商品添加到购物车
    • 用户可以在未登录状态下将商品添加到购物车
    • 用户可以使用购物车一起结算下单
    • 用户可以查询自己的购物车
    • 用户可以在购物车中修改购买商品的数量。
    • 用户可以在购物车中删除商品。

    2.2 技术选型

    • 用户在登录状态下将商品添加到购物车,数据应该保存在哪里呢?

      • MySQL:在大量数据时,MySQL 的效率会显著降低,不推荐使用。
      • Redis:在大量数据时,Redis 数据放在内存中,大小有限,不推荐使用。
      • MongoDB:MongoDB 是文档型数据库,它保存在硬盘上,在大量数据时,效率很高,推荐使用。

      注意:本项目中还是使用的 Redis,但更推荐 MongoDB。

    • 用户在未登录状态下将商品添加到购物车,数据应该保存在哪里呢?

      • COOKIE:COOKIE 大小有限制(4 KB),同一个域名下的总 COOKIE 数量也有限制(20 个),并且每次请求携带 COOKIE 会占用大量带宽,不推荐使用。
      • Web SQL:使用有些麻烦,还需要写 SQL,不推荐使用。
      • Local Storage:主要是用来作为本地存储来使用的,解决了 COOKIE 存储空间不足的问题,Local Storage 中一般浏览器支持的是 5M 大小,推荐使用。

    2.3 流程图

    《乐优商城(十二)购物车》

    这幅图主要描述了两个功能:添加购物车、查询购物车。

    • 添加购物车:

      • 判断是否登录
        • 是:则添加商品到 Redis 中
        • 否:则添加商品到本地的 Local Storage
    • 查询购物车列表:

      • 判断是否登录
        • 否:直接查询 Local Storage 中数据并展示
        • 是:已登录,则需要先看本地是否有数据,
          • 有:需要提交到后台添加到 Redis,合并数据,而后查询
          • 否:直接去后台查询 Redis,而后返回
    3. 未登录购物车

    3.1 购物车数据结构

    首先分析一下未登录购物车的数据结构,下面是需要展示的数据:

    《乐优商城(十二)购物车》

    因此每一个购物车信息,都是一个对象:

    { skuId:2131241, title:"小米6", image:"", price:190000, num:1, ownSpec:"{"机身颜色":"陶瓷黑尊享版","内存":"6GB","机身存储":"128GB"}"}

    另外,购物车中不止一条数据,因此最终会是对象的数组。

    3.2 获取购物数量

    添加购物车需要知道购物的数量,所以我们需要获取数量大小。

  • 我们在 item.html 中定义 num,保存数量

    《乐优商城(十二)购物车》

  • 然后将 num 与页面的 input 框绑定,同时给 + 和 - 的按钮绑定事件

    《乐优商城(十二)购物车》

  • 编写方法

    《乐优商城(十二)购物车》

  • 3.3 添加购物车

  • 绑定点击事件 addCart 方法

    《乐优商城(十二)购物车》

  • addCart 方法中判断用户的登录状态,未登录状态下保存商品在浏览器本地的 Local Storage 中,然后跳转到购物车页面

    addCart(){ //判断有没有登陆 ly.http.get("/auth/verify").then(res=>{ // 已登录发送信息到后台,保存到redis中 }).catch(()=>{ // 未登录保存在浏览器本地的localStorage中 // 1、查询本地购物车 let carts = ly.store.get("LY_CART") || []; let cart = carts.find(c=>c.skuId===this.sku.id); // 2、判断是否存在 if (cart) { // 3、存在更新数量 cart.num += this.num; } else { // 4、不存在,新增 cart = { skuId: this.sku.id, title: this.sku.title, price: this.sku.price, image: this.sku.images, ownSpec: this.sku.ownSpec, num: this.num }; carts.push(cart); } // 把carts写回localstorage ly.store.set("LY_CART", carts); // 跳转 window.location.href = "http://www.leyou.com/cart.html"; });}

  • 点击加入购物车后,查看 Local Storage

    《乐优商城(十二)购物车》

  • 3.4 渲染购物车页面

    3.4.1 封装判断用户的登录状态方法

  • 因为查询购物车也会用到判断用户的登录状态,因此我们将这个方法封装到 common.js 中

    《乐优商城(十二)购物车》

  • 修改 item.html 中的方法

    《乐优商城(十二)购物车》

  • 3.4.2 查询购物车

    修改 cart.html,页面加载时,就应该去查询购物车

    《乐优商城(十二)购物车》

    3.4.3 渲染页面

    略,交给前端吧,最终效果如下:

    《乐优商城(十二)购物车》

    3.5 修改数量

  • 我们给页面的 + 和 -绑定点击事件

    《乐优商城(十二)购物车》

  • 点击事件中修改 num 的值

    《乐优商城(十二)购物车》

    《乐优商城(十二)购物车》

  • 3.6 删除商品

  • 给删除按钮绑定事件

    《乐优商城(十二)购物车》

  • 点击事件中删除商品

    《乐优商城(十二)购物车》

  • 4. 已登录购物车

    4.1 流程分析

    《乐优商城(十二)购物车》

  • 登录后,加入购物车会发送请求到后台,首先会经过 Zuul 网关 LoginFilter 登录验证,如果通过则请求被转发到 leyou-cart
  • 由于加入购物车还需要用户信息,所以我们需要解析用户信息并保存起来,以便后续的接口可以使用。这里用到 SpringMVC 的前置 preHandle,并将解析后的 UserInfo 放入 ThreadLocal 中。
  • 4.2 实现解析用户信息

  • 在 leyou-cart 中添加依赖

    com.leyou.auth leyou-auth-common 1.0-SNAPSHOT

  • 在 application.yaml 中添加 JWT 配置

    leyou: jwt: pubKeyPath: D:\tmp\rsa\\rsa.pub # 公钥地址 COOKIEName: LY_TOKEN # COOKIE的名称

  • 创建 JWT 属性读取类

    《乐优商城(十二)购物车》

    @ConfigurationProperties(prefix = "leyou.jwt")public class JwtProperties { private String pubKeyPath;// 公钥 private PublicKey publicKey; // 公钥 private String COOKIEName; private static final Logger logger = LoggerFactory.getLogger(JwtProperties.class); @PostConstruct public void init(){ try { // 获取公钥和私钥 this.publicKey = RsaUtils.getPublicKey(pubKeyPath); } catch (Exception e) { logger.error("初始化公钥失败!", e); throw new RuntimeException(); } } public String getPubKeyPath() { return pubKeyPath; } public void setPubKeyPath(String pubKeyPath) { this.pubKeyPath = pubKeyPath; } public PublicKey getPublicKey() { return publicKey; } public void setPublicKey(PublicKey publicKey) { this.publicKey = publicKey; } public String getCOOKIEName() { return COOKIEName; } public void setCOOKIEName(String COOKIEName) { this.COOKIEName = COOKIEName; }}

  • 编写拦截器

    @Component@EnableConfigurationProperties(JwtProperties.class)public class LoginInterceptor extends HandlerInterceptorAdapter { @Autowired private JwtProperties jwtProperties; // 定义一个线程域,存放用户信息 private static final ThreadLocal THREAD_LOCAL = new ThreadLocal(); /** * 获取用户信息 * @return */ public static UserInfo getUserInfo() { return THREAD_LOCAL.get(); } /** * 解析 JWT,并将用户信息存放入 ThreadLocal * @param request * @param response * @param handler * @return * @throws Exception */ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 查询 token String token = COOKIEUtils.getCOOKIEValue(request, "LY_TOKEN"); if (StringUtils.isBlank(token)) { // 未登录,返回 401 response.setStatus(HttpStatus.UNAUTHORIZED.value()); return false; } // 有 token,查询用户信息 try { // 解析成功,证明已经登录 UserInfo userInfo = JwtUtils.getInfoFromToken(token, jwtProperties.getPublicKey()); // 放入线程域 THREAD_LOCAL.set(userInfo); return true; } catch (Exception e){ // 抛出异常,登录,返回 401 response.setStatus(HttpStatus.UNAUTHORIZED.value()); return false; } } /** * 响应视图后,释放 ThreadLocal * @param request * @param response * @param handler * @param ex * @throws Exception */ @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { THREAD_LOCAL.remove(); }}

    注意:

    • 这里我们使用了 ThreadLocal 来存储查询到的用户信息,线程内共享,因此请求到达 Controller 后可以共享 UserInfo
    • 并且对外提供了静态的方法 getLoginUser() 来获取 UserInfo 信息
  • 定义配置类,注册拦截器

    @Configurationpublic class MvcConfig implements WebMvcConfigurer { @Autowired private LoginInterceptor loginInterceptor; @Override /** * 重写接口中的 addInterceptors 方法,添加自定义拦截器 */ public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(loginInterceptor).addPathPatterns("/**"); }}

  • 4.3 后台购物车设计

    当用户登录时,我们需要把购物车数据保存 Redis 中,那应该采用怎样的数据结构保存呢?

  • 首先每个用户应该有独立的购物车,因此购物车应该以用户的作为 key 来存储,value 是用户的所有购物车信息。
  • 我们对购物车中的商品进行增、删、改操作,基本都需要根据商品 id 进行判断,为了方便后期处理,我们的购物车也应该是 k-v 结构,key 是商品 id,value 才是这个商品的购物车信息。
  • 综上所述,我们的购物车结构是一个双层 Map,也就对应着 Redis 中的 Hash 数据结构。

    Map

    • 第一层 Map,Key 是用户 id
    • 第二层 Map,Key 是购物车中商品 id,值是购物车数据

    4.4 添加购物车

    4.4.1 前端发起请求

    修改 item.html,已登录情况下,向后台发送 POST 请求添加购物车

    《乐优商城(十二)购物车》

    4.4.2 实体类

    在 leyou-cart 中添加购物车实体类 Cart

    public class Cart { private Long userId;// 用户id private Long skuId;// 商品id private String title;// 标题 private String image;// 图片 private Long price;// 加入购物车时的价格 private Integer num;// 购买数量 private String ownSpec;// 商品规格参数 // 省略 getter、setter 方法}

    4.4.3 FeignClient

    在添加购物车时,需要根据 skuId 去查询 Sku 信息,我们会在 leyou-cart 中远程调用 leyou-item 提供的对应接口。

  • 在 leyou-item-service 中 SpuController 添加方法

    /** * 通过 skuId 查询 Sku * * @param skuId * @return */@GetMapping("sku")public ResponseEntity querySkuBySkuId(@RequestParam("skuId") Long skuId) { Sku sku = spuService.querySkuBySkuId(skuId); if (sku == null) { return ResponseEntity.notFound().build(); } return ResponseEntity.ok(sku);}

  • 在 leyou-item-service 中 SpuService 添加方法

    /** * 通过 skuId 查询 Sku * * @param skuId * @return */public Sku querySkuBySkuId(Long skuId) { Sku sku = skuMapper.selectByPrimaryKey(skuId); return sku;}

  • 在 leyou-item-interface 中 SpuApi 添加接口

    /** * 通过 skuId 查询 Sku * * @param skuId * @return */@GetMapping("sku")public Sku querySkuBySkuId(@RequestParam("skuId") Long skuId);

  • 在 leyou-cart 中添加 SpuClient

    《乐优商城(十二)购物车》

    @FeignClient("item-service")public interface SpuClient extends SpuApi { }

  • 4.4.4 Controller

    在 leyou-cart 中添加 CartController

    @Controllerpublic class CartController { @Autowired private CartService cartService; /** * 添加购物车 * @param cart * @return */ @PostMapping public ResponseEntity addCart(@RequestBody Cart cart) { cartService.addCart(cart); return ResponseEntity.status(HttpStatus.CREATED).build(); }}

    4.4.5 Service

    在 leyou-cart 中添加 CartService

    @Servicepublic class CartService { @Autowired private StringRedisTemplate stringRedisTemplate; @Autowired private SpuClient spuClient; private static final String KEY_PREFIX = "leyou:cart:uid:"; /** * 添加购物车 * * @param cart * @return */ public void addCart(Cart cart) { // 获取用户信息 UserInfo userInfo = LoginInterceptor.getUserInfo(); // 操作 Hash 数据 BoundHashOperations boundHashOps = stringRedisTemplate.boundHashOps(KEY_PREFIX + userInfo.getId()); // 判断购物车是否有该商品 if (boundHashOps.hasKey(cart.getSkuId().toString())) { // 有,更改该商品数量 String jsonCart = boundHashOps.get(cart.getSkuId().toString()).toString(); Cart cart1 = JsonUtils.parse(jsonCart, Cart.class); cart1.setNum(cart1.getNum() + cart.getNum()); boundHashOps.put(cart1.getSkuId().toString(), JsonUtils.serialize(cart1)); } else { // 无,新增该商品 Sku sku = spuClient.querySkuBySkuId(cart.getSkuId()); cart.setUserId(userInfo.getId()); cart.setTitle(sku.getTitle()); cart.setImage(sku.getImages().split(",")[0]); cart.setOwnSpec(sku.getOwnSpec()); boundHashOps.put(cart.getSkuId().toString(),JsonUtils.serialize(cart)); } }}

    4.4.6 最终目录结构

    《乐优商城(十二)购物车》

    4.4.7 测试

    在登录后,添加商品到购物车后,查看 Redis

    《乐优商城(十二)购物车》

    4.5 查询购物车

    4.5.1 前端发起请求

    修改 item.html,已登录情况下,向后台发送 GET 请求添加购物车

    《乐优商城(十二)购物车》

    4.5.2 Controller

    在 CartController 中添加 queryCart 方法

    /** * 查询购物车 * @return */@GetMappingpublic ResponseEntity queryCart() { List carts = cartService.queryCart(); if(carts == null) { return ResponseEntity.notFound().build(); } return ResponseEntity.ok(carts);}

    4.5.3 Service

    在 CartService 中添加 queryCart 方法

    /** * 查询购物车 * * @return */public List queryCart() { // 获取用户信息 UserInfo userInfo = LoginInterceptor.getUserInfo(); // 判断用户是否存在购物车 if (!stringRedisTemplate.hasKey(KEY_PREFIX + userInfo.getId())) { return null; } // 操作 Hash 数据 BoundHashOperations boundHashOps = stringRedisTemplate.boundHashOps(KEY_PREFIX + userInfo.getId()); // 获取所有购物车中商品 List jsonCarts = boundHashOps.values(); // 判断购物车中是否有商品 if (CollectionUtils.isEmpty(jsonCarts)) { return null; } ArrayList carts = new ArrayList(); for (Object jsonCart : jsonCarts) { Cart cart = JsonUtils.parse(jsonCart.toString(), Cart.class); carts.add(cart); } return carts;}

    4.5.4 测试

    在登录后,添加商品到购物车后,查询到的购物车

    《乐优商城(十二)购物车》

    4.6 修改数量

    4.6.1 前端发起请求

    《乐优商城(十二)购物车》

    4.6.2 Controller

    /** * 修改购物车 * * @return */@PutMappingpublic ResponseEntity updateCart(@RequestBody Cart cart) { cartService.updateCart(cart); return ResponseEntity.status(HttpStatus.NO_CONTENT).build();}

    4.6.3 Service

    /** * 修改购物车 * * @return */public void updateCart(Cart cart) { // 获取用户信息 UserInfo userInfo = LoginInterceptor.getUserInfo(); // 操作 Hash 数据 BoundHashOperations boundHashOps = stringRedisTemplate.boundHashOps(KEY_PREFIX + userInfo.getId()); // 更改该商品数量 String jsonCart = boundHashOps.get(cart.getSkuId().toString()).toString(); Cart cart1 = JsonUtils.parse(jsonCart, Cart.class); cart1.setNum(cart.getNum()); boundHashOps.put(cart1.getSkuId().toString(), JsonUtils.serialize(cart1));}

    4.7 删除商品

    4.7.1 前端发起请求

    《乐优商城(十二)购物车》

    4.7.2 Controller

    /** * 删除购物车 * * @param skuId * @return */@DeleteMapping("{skuId}")public ResponseEntity deleteCart(@PathVariable("skuId") String skuId) { cartService.deleteCart(skuId); return ResponseEntity.status(HttpStatus.NO_CONTENT).build();}

    4.7.3 Service

    /** * 删除购物车 * * @param skuId * @return */public void deleteCart(String skuId) { // 获取用户信息 UserInfo userInfo = LoginInterceptor.getUserInfo(); // 操作 Hash 数据 BoundHashOperations boundHashOps = stringRedisTemplate.boundHashOps(KEY_PREFIX + userInfo.getId()); // 删除商品 boundHashOps.delete(skuId);}

    网友评论