乐优商城第12次购物车操作有哪些新功能?
- 内容介绍
- 文章标签
- 相关推荐
本文共计3944个文字,预计阅读时间需要16分钟。
文章目录
1.构建购物车微服务
2.购物车功能分析
2.1 需求分析 2.2 技术选型 2.3 流程图3.未登录购物车
3.1 购物车数据结构 3.2 获取购物数量 3.3 添加购物车 3.4 渲染购物车页面 文章目录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
右键 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: 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.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.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.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.1 流程分析
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 中,那应该采用怎样的数据结构保存呢?
综上所述,我们的购物车结构是一个双层 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 在 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;} 在登录后,添加商品到购物车后,查询到的购物车 /** * 修改购物车 * * @return */@PutMappingpublic ResponseEntity updateCart(@RequestBody Cart cart) { cartService.updateCart(cart); return ResponseEntity.status(HttpStatus.NO_CONTENT).build();} /** * 修改购物车 * * @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));} /** * 删除购物车 * * @param skuId * @return */@DeleteMapping("{skuId}")public ResponseEntity deleteCart(@PathVariable("skuId") String skuId) { cartService.deleteCart(skuId); return ResponseEntity.status(HttpStatus.NO_CONTENT).build();} /** * 删除购物车 * * @param skuId * @return */public void deleteCart(String skuId) { // 获取用户信息 UserInfo userInfo = LoginInterceptor.getUserInfo(); // 操作 Hash 数据 BoundHashOperations boundHashOps = stringRedisTemplate.boundHashOps(KEY_PREFIX + userInfo.getId()); // 删除商品 boundHashOps.delete(skuId);} queryCart() { List carts = cartService.queryCart(); if(carts == null) { return ResponseEntity.notFound().build(); } return ResponseEntity.ok(carts);}
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
本文共计3944个文字,预计阅读时间需要16分钟。
文章目录
1.构建购物车微服务
2.购物车功能分析
2.1 需求分析 2.2 技术选型 2.3 流程图3.未登录购物车
3.1 购物车数据结构 3.2 获取购物数量 3.3 添加购物车 3.4 渲染购物车页面 文章目录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
右键 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: 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.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.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.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.1 流程分析
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 中,那应该采用怎样的数据结构保存呢?
综上所述,我们的购物车结构是一个双层 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 在 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;} 在登录后,添加商品到购物车后,查询到的购物车 /** * 修改购物车 * * @return */@PutMappingpublic ResponseEntity updateCart(@RequestBody Cart cart) { cartService.updateCart(cart); return ResponseEntity.status(HttpStatus.NO_CONTENT).build();} /** * 修改购物车 * * @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));} /** * 删除购物车 * * @param skuId * @return */@DeleteMapping("{skuId}")public ResponseEntity deleteCart(@PathVariable("skuId") String skuId) { cartService.deleteCart(skuId); return ResponseEntity.status(HttpStatus.NO_CONTENT).build();} /** * 删除购物车 * * @param skuId * @return */public void deleteCart(String skuId) { // 获取用户信息 UserInfo userInfo = LoginInterceptor.getUserInfo(); // 操作 Hash 数据 BoundHashOperations boundHashOps = stringRedisTemplate.boundHashOps(KEY_PREFIX + userInfo.getId()); // 删除商品 boundHashOps.delete(skuId);} queryCart() { List carts = cartService.queryCart(); if(carts == null) { return ResponseEntity.notFound().build(); } return ResponseEntity.ok(carts);}
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

