window.location.href = “http://www.leyou.com/cart.html”;
});
}
结果:
添加完成后,页面会跳转到购物车结算页面:cart.html
3.3.1.校验用户登录
因为会多次校验用户登录状态,因此我们封装一个校验的方法:
在common.js中:
3.3.2.查询购物车
页面加载时,就应该去查询购物车。cart.html中
var cartVm = new Vue({
el: “#cartApp”,
data: {
ly,
carts: [],// 购物车数据
},
created() {
this.loadCarts();
},
methods: {
loadCarts() {
// 先判断登录状态
ly.verifyUser()
.then(() => {
// 已登录
})
.catch(() => {
// 未登录
this.carts = ly.store.get(“carts”) || [];
this.selected = this.carts;
})
}
}
components: {
shortcut: () => import(“/js/pages/shortcut.js”)
}
})
刷新页面,查看控制台Vue实例:
3.5.2.渲染到页面
接下来,我们在页面中展示carts的数据:
页面位置:
修改后:
要注意,价格的展示需要进行格式化,这里使用的是我们在common.js中定义的formatPrice方法:
效果:
我们给页面的 +
和 -
绑定点击事件,修改num 的值:
两个事件:
increment© {
c.num++;
ly.verifyUser()
.then(() => {
// TODO 已登录,向后台发起请求
})
.catch(() => {
// 未登录,直接操作本地数据
ly.store.set(“carts”, this.carts);
})
},
decrement© {
if (c.num <= 1) {
return;
}
c.num–;
this.verifyUser()
.then(() => {
// TODO 已登录,向后台发起请求
})
.catch(() => {
// 未登录,直接操作本地数据
ly.store.set(“carts”, this.carts);
})
},
给删除按钮绑定事件:
点击事件中删除商品:
deleteCart(i) {
ly.verifyUser()
.then(() => {
// 已登录
})
.catch(() => {
// 未登录
this.carts.splice(i, 1);
ly.store.set(“carts”, this.carts);
})
}
在页面中,每个购物车商品左侧,都有一个复选框,用户可以选择部分商品进行下单,而不一定是全部:
我们定义一个变量,记录所有被选中的商品:
3.8.1.选中一个
我们给商品前面的复选框与selected绑定,并且指定其值为当前购物车商品:
3.8.2.初始化全选
我们在加载完成购物车查询后,初始化全选:
3.8.4.总价格
然后编写一个计算属性,计算出选中商品总价格:
computed: {
totalPrice() {
return this.selected.map(c => c.num * c.price).reduce((p1, p2) => p1 + p2, 0);
}
}
在页面中展示总价格:
效果:
===================================================================
4.1.1.创建购物车的微服务
4.1.2.pom依赖
<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”>
leyou
com.leyou.parent
1.0.0-SNAPSHOT
4.0.0
com.leyou.service
ly-cart
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
4.1.3.配置文件
server:
port: 8088
spring:
application:
name: cart-service
redis:
host: 192.168.206.66
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
registry-fetch-interval-seconds: 5
instance:
prefer-ip-address: true
ip-address: 127.0.0.1
instance-id: e u r e k a . i n s t a n c e . i p − a d d r e s s . {eureka.instance.ip-address}. eureka.instance.ip−address.{server.port}
lease-renewal-interval-in-seconds: 3
lease-expiration-duration-in-seconds: 10
4.1.4.启动类
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class LyCartApplication {
public static void main(String[] args) {
SpringApplication.run(LyCartApplication.class, args);
}
}
4.1.5.添加到网关的路由当中
===================================================================
接下来,我们完成已登录购物车。
在刚才的未登录购物车编写时,我们已经预留好了编写代码的位置,逻辑也基本一致。
购物车系统只负责登录状态的购物车处理,因此需要添加登录校验,我们通过JWT鉴权即可实现。
5.1.1.引入JWT相关依赖
我们引入之前写的鉴权工具:ly-auth-common
com.leyou.auth
ly-auth-common
1.0.0-SNAPSHOT
5.1.2.配置公钥
ly:
jwt:
pubKeyPath: C:\Users\ZHENG\Desktop\leyou_msgrs\rsa\rsa.pub # 公钥地址
cookieName: LY_TOKEN # cookie的名称
5.1.3.加载公钥
package com.leyou.cart.config;
import com.leyou.auth.utils.RsaUtils;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.ConfigurationProperties;
import javax.annotation.PostConstruct;
import java.security.PublicKey;
@Data
@ConfigurationProperties(prefix = “ly.jwt”)
@Slf4j
public class JwtProperties {
private String pubKeyPath;// 公钥
private PublicKey publicKey; // 公钥
private String cookieName;
@PostConstruct
public void init(){
try {
// 获取公钥和私钥
this.publicKey = RsaUtils.getPublicKey(pubKeyPath);
} catch (Exception e) {
log.error(“初始化公钥失败!”, e);
throw new RuntimeException();
}
}
}
5.1.4.编写过滤器
引入依赖:
com.leyou.common
ly-common
1.0.0-SNAPSHOT
代码:
因为很多接口都需要进行登录,我们直接编写SpringMVC拦截器,进行统一登录校验。同时,我们还要把解析得到的用户信息保存起来,以便后续的接口可以使用。
package com.leyou.interceptor;
import com.leyou.auth.entity.UserInfo;
import com.leyou.auth.utils.JwtUtils;
import com.leyou.cart.config.JwtProperties;
import com.leyou.common.utils.CookieUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Slf4j
public class UserInterceptor implements HandlerInterceptor {
private JwtProperties prop;
private static final ThreadLocal tl = new ThreadLocal<>();
public UserInterceptor(JwtProperties prop) {
this.prop = prop;
}
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
//获取Cookie
String token = CookieUtils.getCookieValue(request, prop.getCookieName());
try {
//解析token
UserInfo user = JwtUtils.getUserInfo(prop.getPublicKey(), token);
//传递User
tl.set(user);
//放行
return true;
} catch (Exception e) {
log.error(“[购物车服务]解析用户身份失败”, e);
return false;
}
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler, Exception ex)
throws Exception {
tl.remove();
}
public static UserInfo getLoginUser() {
return tl.get();
}
}
注意:
-
这里我们使用了
ThreadLocal
来存储查询到的用户信息,线程内共享,因此请求到达Controller
后可以共享User -
并且对外提供了静态的方法:
getLoginUser()
来获取User信息
5.1.5.配置过滤器
配置SpringMVC,使过滤器生效:
package com.leyou.cart.config;
import com.leyou.interceptor.UserInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
@EnableConfigurationProperties(JwtProperties.class)
public class MvcConfig implements WebMvcConfigurer {
@Autowired
private JwtProperties prop;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new UserInterceptor(prop)).addPathPatterns(“/**”);
}
}
数据结构设计
当用户登录时,我们需要把购物车数据保存到后台,可以选择保存在数据库。但是购物车是一个读写频率很高的数据。因此我们这里选择读写效率比较高的Redis作为购物车存储。
Redis有5种不同数据结构,这里选择哪一种比较合适呢?
-
首先不同用户应该有独立的购物车,因此购物车应该以用户的作为key来存储,Value是用户的所有购物车信息。这样看来基本的
k-v
结构就可以了。 -
但是,我们对购物车中的商品进行增、删、改操作,基本都需要根据商品id进行判断,为了方便后期处理,我们的购物车也应该是
k-v
结构,key是商品id,value才是这个商品的购物车信息。
综上所述,我们的购物车结构是一个双层Map:Map<String,Map<String,String>>
-
第一层Map,Key是用户id
-
第二层Map,Key是购物车中商品id,值是购物车数据
-
Map<string,Map<String,String>>
实体类
@Data
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;// 商品规格参数
}
4.3.1.页面发起请求:
已登录情况下,向后台添加购物车:
这里发起的是Json请求。那么我们后台也要以json接收。
4.3.2.后台添加购物车
Service
这里我们不访问数据库,而是直接操作Redis。基本思路:
-
先查询之前的购物车数据
-
判断要添加的商品是否存在
-
存在:则直接修改数量后写回Redis
-
不存在:新建一条数据,然后写入Redis
代码:
controller
先分析一下:
-
请求方式:新增,肯定是Post
-
请求路径:/cart ,这个其实是Zuul路由的路径,我们可以不管
-
请求参数:Json对象,包含skuId和num属性
-
返回结果:无
package com.leyou.cart.web;
import com.leyou.cart.pojo.Cart;
import com.leyou.cart.service.CartService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class CartController {
@Autowired
private CartService cartService;
/*
新增购物车
*/
@PostMapping(“cart”)
public ResponseEntity addCart(@RequestBody Cart cart){
cartService.addCart(cart);
return ResponseEntity.status(HttpStatus.CREATED).build();
}
}
完善CartService
package com.leyou.cart.service;
import com.leyou.auth.entity.UserInfo;
import com.leyou.cart.pojo.Cart;
import com.leyou.common.utils.JsonUtils;
import com.leyou.interceptor.UserInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
@Service
public class CartService {
@Autowired
private StringRedisTemplate redisTemplate;
private static final String KEY_PREFIX = “cart:uid:”;
public void addCart(Cart cart) {
//获取登录的用户
UserInfo user = UserInterceptor.getLoginUser();
//key
String key = KEY_PREFIX + user.getId();
//hashKey
String hashKey = cart.getSkuId().toString();
//记录num
Integer num = cart.getNum();
BoundHashOperations<String, Object, Object> operation = redisTemplate.boundHashOps(key);
//判断当前购物车商品是否存在
if(operation.hasKey(hashKey)){
//存在,修改数量
String json = operation.get(hashKey).toString();
cart = JsonUtils.toBean(json, Cart.class);
cart.setNum(cart.getNum() + num);
}
//写回redis
operation.put(hashKey,JsonUtils.toString(cart));
}
}
4.3.3.运行测试
在虚拟机的redis当中查看
5.4.1.页面发起请求
5.4.2.后台实现
Controller
/**
-
查询购物车列表
-
@return
*/
@GetMapping
public ResponseEntity<List> queryCartList(){
return ResponseEntity.ok(cartService.queryCartList());
}
Service
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)
最后
资料过多,篇幅有限,需要文中全部资料可以点击这里免费获取前端面试资料PDF完整版!
自古成功在尝试。不尝试永远都不会成功。勇敢的尝试是成功的一半。
zZmFsbGJhY2s,shadow_50,text_Q1NETiBA6JOd55uS5a2QaXRibHVlYm94,size_20,color_FFFFFF,t_70,g_se,x_16)
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-srFRMgrm-1713508585832)]
[外链图片转存中…(img-WqCTDtno-1713508585832)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!
[外链图片转存中…(img-XkjdQ2NW-1713508585833)]
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)
[外链图片转存中…(img-1Krrh8Hr-1713508585833)]
最后
[外链图片转存中…(img-KAnkRrrt-1713508585833)]
[外链图片转存中…(img-9NXNJC6Q-1713508585833)]
资料过多,篇幅有限,需要文中全部资料可以点击这里免费获取前端面试资料PDF完整版!
自古成功在尝试。不尝试永远都不会成功。勇敢的尝试是成功的一半。