乐优商城:笔记(十四):购物车微服务:LyCartApplication

2023-12-28 21:18

本文主要是介绍乐优商城:笔记(十四):购物车微服务:LyCartApplication,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 1 分析
    • 1.1 需求分析
    • 1.2 购物车数据结构
    • 1.3 购物车商品存储位置
      • 1.3.1 web本地存储
      • 1.3.2 localstorage的用法
  • 2 未登录购物车时——localstorage
    • 2.1 添加到购物车
    • 2.2 提供查询接口显示价格变化
  • 3 已登陆购物车——Redis
    • 3.1 搭建购物车服务
      • 3.1.1 创建module
      • 3.1.2 引入依赖
      • 3.1.3 配置文件
      • 3.1.4 启动类
      • 3.1.5 添加路由
    • 3.2 鉴权
      • 3.2.1 引入公钥实现解析
      • 3.2.2 读取公钥
      • 3.2.3 编写拦截器
      • 3.2.4 使得拦截器生效
    • 3.3 购物车数据设计
      • 3.3.1 购物车数据结构
      • 3.3.2 实体类
    • 3.4 添加商品到购物车
      • 3.4.1 controller
      • 3.4.2 service
    • 3.5 查询购物车
      • 3.5.1 controller
      • 3.5.2 service
    • 3.6 修改购物车中商品数量
      • 3.6.1 controller
      • 3.6.2 service
    • 3.7 删除购物车中商品
      • 3.7.1 controller
      • 3.7.2 service
  • 4 待优化

1 分析

1.1 需求分析

需求描述:

  • 用户可以在登录状态下将商品添加到购物车
    • 放入数据库
    • 放入redis(采用)
  • 用户可以在未登录状态下将商品添加到购物车
    • 放入localstorage
  • 用户可以使用购物车一起结算下单
  • 用户可以查询自己的购物车
  • 用户可以在购物车中可以修改购买商品的数量。
  • 用户可以在购物车中删除商品。
  • 在购物车中展示商品优惠信息
  • 提示购物车商品价格变化

1.2 购物车数据结构

首先分析一下未登录购物车的数据结构。

我们看下页面展示需要什么数据:
在这里插入图片描述
因此每一个购物车信息,都是一个对象,包含:

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

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

[{...},{...},{...}
]

:购物车的价格是加入时的价格,当价格发生变化时要有降价的友好提示提升用户体验

1.3 购物车商品存储位置

知道了数据结构,下一个问题,就是如何保存购物车数据。购物车的数据是被频繁读写的,存入数据库是不合适的,那么我们选择的是——web本地存储

1.3.1 web本地存储

在这里插入图片描述
web本地存储主要有两种方式:

  • LocalStorage:localStorage 方法存储的数据没有时间限制。第二天、第二周或下一年之后,数据依然可用。
  • SessionStorage:sessionStorage 方法针对一个 session 进行数据存储。当用户关闭浏览器窗口后,数据会被删除。

显然我们选择的是localstorage,不能关闭了浏览器商品信息就没有了

1.3.2 localstorage的用法

localstorage已经变成了js的内置变量,可以在js的任何一个地方使用它,语法如下:

localStorage.setItem("key","value"); // 存储数据
localStorage.getItem("key"); // 获取数据
localStorage.removeItem("key"); // 删除数据

注意:localStorage和SessionStorage都只能保存字符串

2 未登录购物车时——localstorage

2.1 添加到购物车

我们看下商品详情页:
在这里插入图片描述
现在点击加入购物车会跳转到购物车成功页面:cart.html,localstorage中的信息如下:
在这里插入图片描述
但是此时却报错了,发现浏览器发起了一条请求:
在这里插入图片描述
但是很奇怪,购物车所需要的信息在localstorage中都有的呀,为什么还要查询sku的信息呢?答案是我们之前所说的购物车中需要显示价格变化,我们加入购物车中的商品价格不是一成不变的,因此需要发送这条请求来查询此时的价格与加入购物车时的价格是否发生了变化,提供友好的提示,接下来我们来实现

2.2 提供查询接口显示价格变化

  • 请求方式:get
  • 请求路径:/sku/list
  • 请求参数:sku的id集合
  • 返回结果:sku的集合

controller

@GetMapping("/sku/list/ids")
public ResponseEntity<List<Sku>> querySkuByIds(@RequestParam("ids") List<Long> ids){return ResponseEntity.ok(goodsService.querySkuBySpuIds(ids));}

service

public List<Sku> querySkuBySpuIds(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;}

之后刷新页面,可以看到我们加入购物车的商品:
在这里插入图片描述
之后我们验证一下修改价格是不是会有有好的提示,我们去数据库中修改商品的价格,然后刷新页面:
在这里插入图片描述
发现价格变化已经有了有好的提示

3 已登陆购物车——Redis

3.1 搭建购物车服务

3.1.1 创建module

在这里插入图片描述

3.1.2 引入依赖

<?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</artifactId><groupId>com.leyou.parent</groupId><version>1.0.0-SNAPSHOT</version></parent><modelVersion>4.0.0</modelVersion><groupId>com.leyou.service</groupId><artifactId>ly-cart</artifactId><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-client</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency><groupId>com.leyou.service</groupId><artifactId>ly-auth-common</artifactId><version>1.0.0-SNAPSHOT</version></dependency><dependency><groupId>com.leyou.common</groupId><artifactId>ly-common</artifactId><version>1.0.0-SNAPSHOT</version></dependency></dependencies>
</project>

3.1.3 配置文件

server:port: 8088
spring:application:name: cart-serviceredis:host: 192.168.124.128
eureka:client:service-url:defaultZone: http://127.0.0.1:10086/eurekaregistry-fetch-interval-seconds: 5instance:prefer-ip-address: trueip-address: 127.0.0.1

3.1.4 启动类

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

3.1.5 添加路由

在这里插入图片描述

3.2 鉴权

当我们在商品详情页选好商品添加购物车时,经过上面的分析我们知道,商品不是添加到数据库中,而是添加到Redis中,而Redis是以<key,value>的形式存储的,那key自然就是用户了,而我们的用户信息key存储在token中,所以要想实现添加购物车的功能,要先实现鉴权

那可能会有疑问,我们不是在网关写了鉴权吗,为什么不把用户信息传递过来?问题就在这里,我们网关微服务和购物车微服务不是一个微服务,属于不同的tomcat,不共享session,因此这样是不可行的

我们不仅要添加购物车,还有修改、删除购物车信息,每一步操作都需要用户信息key,所以每个地方都需要鉴权,显然在每个操作都做一次鉴权会使得代码很臃肿,我们需要的是统一解析而不是一次又一次的写,因此我们把这部分功能抽取出来,此时我们想到SpringMVC的拦截器——interceptor,通过拦截器我们可以统一处理每个请求,然后再进入controller层处理

3.2.1 引入公钥实现解析

ly:jwt:pubKeyPath: H:/javacode/idea/rsa/rsa.pub # 公钥地址cookieName: LY_TOKEN

3.2.2 读取公钥

@Data
@ConfigurationProperties(prefix = "ly.jwt")
public class JwtProperties {private String pubKeyPath;// 公钥private String cookieName;private PublicKey publicKey; // 公钥@PostConstruct // 构造函数执行完毕后就执行public void init(){// 获取公钥和私钥try {this.publicKey = RsaUtils.getPublicKey(pubKeyPath);} catch (Exception e) {e.printStackTrace();}}
}

3.2.3 编写拦截器

分析

  • 解析完token后需要把user信息传递过去,那通过什么传递呢?
    • 既然要传递,就要思考从拦截器到controller再到service有什么东西是共享的?——request、spring容器、thread
      • request :可行,但是SpringMVC是不推荐这种方案的
      • spring容器:会有线程安全问题,因为spring容器都单例的
      • thread:可行,有一个容器threadlocal(线程域),是一个map结构,key是thread,value是存储的值,原理图:
        在这里插入图片描述
@Slf4j
@EnableConfigurationProperties(JwtProperties.class)
public class UserInterceptor implements HandlerInterceptor {@Autowiredprivate JwtProperties prop;// threadLocal是一个map结构,key是thread,value是存储的值private static final ThreadLocal<UserInfo> tl = new ThreadLocal<>();@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {try {// 解析token -- 解析token要先获得cookieString token = CookieUtils.getCookieValue(request, prop.getCookieName());UserInfo user = JwtUtils.getInfoFromToken(token, prop.getPublicKey());// 保存user -- request和thread是“共享”的,所以可以把user放到这两个中tl.set(user); // key是不需要自己给定的,会自己获取return true;} catch (Exception e) {log.error("[购物车异常] 用户身份解析失败!", e);return false;}}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {tl.remove();// 用完之后要删除,否则数据越存越多}public static UserInfo getUser(){return tl.get();}
}

3.2.4 使得拦截器生效

在这里插入图片描述
new 是自己在创建对象,但是拦截器中要使用spring(@EnableConfigurationProperties(JwtProperties.class)),如果使用spring,就不能自己创建,spring要想注入,必须是spring来创建,因此我们修改拦截器,把自动注入去掉,并修改成通过构造函数的方式来注入:
在这里插入图片描述
在这里插入图片描述
这样拦截器就生效了

3.3 购物车数据设计

3.3.1 购物车数据结构

当用户登录时,我们需要把购物车数据保存到后台,可以选择保存在数据库。但是购物车是一个读写频率很高的数据。因此我们这里选择读写效率比较高的Redis作为购物车存储。

Redis有5种不同数据结构,这里选择哪一种比较合适呢?

  • 首先不同用户应该有独立的购物车,因此购物车应该以用户的作为key来存储,Value是用户的所有购物车信息。这样看来基本的k-v结构就可以了。
  • 但是,我们对购物车中的商品进行增、删、改操作,基本都需要根据商品id进行判断,为了方便后期处理,我们的购物车也应该是k-v结构,key是商品id,value才是这个商品的购物车信息。

综上所述,我们的购物车结构是一个双层MapMap<String,Map<String,String>>

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

3.3.2 实体类

@Data
public class Cart {private Long skuId;// 商品idprivate String title;// 标题private String image;// 图片private Long price;// 加入购物车时的价格private Integer num;// 购买数量private String ownSpec;// 商品规格参数
}

3.4 添加商品到购物车

3.4.1 controller

@PostMapping
public ResponseEntity<Void> addCart(@RequestBody Cart cart){cartService.addCart(cart);return ResponseEntity.status(HttpStatus.CREATED).build();
}

3.4.2 service

这里我们不访问数据库,而是直接操作Redis。

基本思路:

  • 先查询之前的购物车数据
  • 判断要添加的商品是否存在
    • 存在:则直接修改数量后写回Redis
    • 不存在:新建一条数据,然后写入Redis

代码:

public void addCart(Cart cart) {// 获取登录的用户 -- 从线程中获得UserInfo user = UserInterceptor.getUser();// redis存储的结构是一个Map<String,Map<String,String>>,第一个key是用户的key,第二个key是商品的key,value是商品信息String key = KEY_PREFIX + user.getId();String hashKey = cart.getSkuId().toString();BoundHashOperations<String, Object, Object> operation = redisTemplate.boundHashOps(key);if(operation.hasKey(hashKey)){// 如果存在  商品数量新增,新增之前先取出商品信息String json = operation.get(hashKey).toString();Cart cacheCart = JsonUtils.parse(json, Cart.class);cacheCart.setNum(cacheCart.getNum() + cart.getNum());operation.put(hashKey,JsonUtils.serialize(cacheCart));}else{// 如果不存在 新增operation.put(hashKey, JsonUtils.serialize(cart));}
}

结果:
在这里插入图片描述

3.5 查询购物车

我们新增商品到购物车,刷新页面却看不到商品信息,这是因为我们没有写查询购物车的实现

3.5.1 controller

@GetMapping("list")
public ResponseEntity<List<Cart>> queryCartList(){return ResponseEntity.ok(cartService.queryCartList());
}

3.5.2 service

public List<Cart> queryCartList() {// 获取登录的用户 -- 从线程中获得UserInfo user = UserInterceptor.getUser();String key = KEY_PREFIX + user.getId();if(!redisTemplate.hasKey(key)){throw new LyException(ExceptionEnum.SKU_NOT_FOUND);}// 获取登录用户的所有购物车BoundHashOperations<String, Object, Object> operation = redisTemplate.boundHashOps(key);List<Cart> carts = operation.values().stream().map(o -> JsonUtils.parse(o.toString(), Cart.class)).collect(Collectors.toList());return carts;
}

3.6 修改购物车中商品数量

3.6.1 controller

    @PutMappingpublic ResponseEntity<Void> updateCartNum(@RequestParam("id") Long skuId,@RequestParam("num") Integer num){cartService.updateCartNum(skuId, num);return ResponseEntity.status(HttpStatus.NO_CONTENT).build();}

3.6.2 service

public void updateCartNum(Long skuId, Integer num) {// 获取登录的用户 -- 从线程中获得UserInfo user = UserInterceptor.getUser();String key = KEY_PREFIX + user.getId();// 获取登录用户的所有购物车BoundHashOperations<String, Object, Object> operation = redisTemplate.boundHashOps(key);// 查询String json = operation.get(skuId.toString()).toString();Cart cart = JsonUtils.parse(json, Cart.class);cart.setNum(num);// 写回redisoperation.put(skuId.toString(), JsonUtils.serialize(cart));
}

3.7 删除购物车中商品

3.7.1 controller

@DeleteMapping("{skuId}")
public ResponseEntity<Void> deleteCart(@PathVariable("skuId") Long skuId){cartService.deleteCart(skuId);return ResponseEntity.status(HttpStatus.NO_CONTENT).build();
}

3.7.2 service

public void deleteCart(Long skuId) {// 获取登录的用户 -- 从线程中获得UserInfo user = UserInterceptor.getUser();String key = KEY_PREFIX + user.getId();// 删除redisTemplate.opsForHash().delete(key, skuId.toString());
}

4 待优化

  • 商品的下架功能
    • 商品下架要提供清空下架商品的功能实现
  • 商品移入收藏夹
    • 需要单独的表结构,类似于购物车的数据结构,只是不需要商品数量
  • 商品的优惠功能:需要一个单独的微服务,因为优惠功能略复杂,关键问题在于优惠条件,比如:
    • 用户权限:哪些用户可享受优惠,哪些用户不可享受优惠
    • 商品限制:哪些商品可以用,那些商品不可以用
    • 价格限制:满足一定的购买条件才可以享受优惠,比如购买一定量的金额,或者一定的数量
    • 优惠方案:满减或者打折
    • 商品组合:某几个商品组合起来可享受优惠
    • 店铺问题:哪些店铺可用,哪些店铺不可用,是否可以跨店铺享受优惠
    • 结算问题:解算时候优惠的计算以及发生退款情况时如何处理
  • 登录后购物车合并
    • 如果登录:
      • 首先检查用户的LocalStorage中是否有购物车信息,
      • 如果有,则提交到后台保存,
      • 清空LocalStorage
    • 如果未登录,直接查询即可

这篇关于乐优商城:笔记(十四):购物车微服务:LyCartApplication的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/547332

相关文章

windos server2022的配置故障转移服务的图文教程

《windosserver2022的配置故障转移服务的图文教程》本文主要介绍了windosserver2022的配置故障转移服务的图文教程,以确保服务和应用程序的连续性和可用性,文中通过图文介绍的非... 目录准备环境:步骤故障转移群集是 Windows Server 2022 中提供的一种功能,用于在多个

解决systemctl reload nginx重启Nginx服务报错:Job for nginx.service invalid问题

《解决systemctlreloadnginx重启Nginx服务报错:Jobfornginx.serviceinvalid问题》文章描述了通过`systemctlstatusnginx.se... 目录systemctl reload nginx重启Nginx服务报错:Job for nginx.javas

【区块链 + 人才服务】可信教育区块链治理系统 | FISCO BCOS应用案例

伴随着区块链技术的不断完善,其在教育信息化中的应用也在持续发展。利用区块链数据共识、不可篡改的特性, 将与教育相关的数据要素在区块链上进行存证确权,在确保数据可信的前提下,促进教育的公平、透明、开放,为教育教学质量提升赋能,实现教育数据的安全共享、高等教育体系的智慧治理。 可信教育区块链治理系统的顶层治理架构由教育部、高校、企业、学生等多方角色共同参与建设、维护,支撑教育资源共享、教学质量评估、

【学习笔记】 陈强-机器学习-Python-Ch15 人工神经网络(1)sklearn

系列文章目录 监督学习:参数方法 【学习笔记】 陈强-机器学习-Python-Ch4 线性回归 【学习笔记】 陈强-机器学习-Python-Ch5 逻辑回归 【课后题练习】 陈强-机器学习-Python-Ch5 逻辑回归(SAheart.csv) 【学习笔记】 陈强-机器学习-Python-Ch6 多项逻辑回归 【学习笔记 及 课后题练习】 陈强-机器学习-Python-Ch7 判别分析 【学

系统架构师考试学习笔记第三篇——架构设计高级知识(20)通信系统架构设计理论与实践

本章知识考点:         第20课时主要学习通信系统架构设计的理论和工作中的实践。根据新版考试大纲,本课时知识点会涉及案例分析题(25分),而在历年考试中,案例题对该部分内容的考查并不多,虽在综合知识选择题目中经常考查,但分值也不高。本课时内容侧重于对知识点的记忆和理解,按照以往的出题规律,通信系统架构设计基础知识点多来源于教材内的基础网络设备、网络架构和教材外最新时事热点技术。本课时知识

【区块链 + 人才服务】区块链集成开发平台 | FISCO BCOS应用案例

随着区块链技术的快速发展,越来越多的企业开始将其应用于实际业务中。然而,区块链技术的专业性使得其集成开发成为一项挑战。针对此,广东中创智慧科技有限公司基于国产开源联盟链 FISCO BCOS 推出了区块链集成开发平台。该平台基于区块链技术,提供一套全面的区块链开发工具和开发环境,支持开发者快速开发和部署区块链应用。此外,该平台还可以提供一套全面的区块链开发教程和文档,帮助开发者快速上手区块链开发。

论文阅读笔记: Segment Anything

文章目录 Segment Anything摘要引言任务模型数据引擎数据集负责任的人工智能 Segment Anything Model图像编码器提示编码器mask解码器解决歧义损失和训练 Segment Anything 论文地址: https://arxiv.org/abs/2304.02643 代码地址:https://github.com/facebookresear

数学建模笔记—— 非线性规划

数学建模笔记—— 非线性规划 非线性规划1. 模型原理1.1 非线性规划的标准型1.2 非线性规划求解的Matlab函数 2. 典型例题3. matlab代码求解3.1 例1 一个简单示例3.2 例2 选址问题1. 第一问 线性规划2. 第二问 非线性规划 非线性规划 非线性规划是一种求解目标函数或约束条件中有一个或几个非线性函数的最优化问题的方法。运筹学的一个重要分支。2

【C++学习笔记 20】C++中的智能指针

智能指针的功能 在上一篇笔记提到了在栈和堆上创建变量的区别,使用new关键字创建变量时,需要搭配delete关键字销毁变量。而智能指针的作用就是调用new分配内存时,不必自己去调用delete,甚至不用调用new。 智能指针实际上就是对原始指针的包装。 unique_ptr 最简单的智能指针,是一种作用域指针,意思是当指针超出该作用域时,会自动调用delete。它名为unique的原因是这个

查看提交历史 —— Git 学习笔记 11

查看提交历史 查看提交历史 不带任何选项的git log-p选项--stat 选项--pretty=oneline选项--pretty=format选项git log常用选项列表参考资料 在提交了若干更新,又或者克隆了某个项目之后,你也许想回顾下提交历史。 完成这个任务最简单而又有效的 工具是 git log 命令。 接下来的例子会用一个用于演示的 simplegit