当前位置 博文首页 > 努力充实,远方可期:【谷粒商城】分布式事务与下单
笔记-基础篇-1(P1-P28):https://blog.csdn.net/hancoder/article/details/106922139
笔记-基础篇-2(P28-P100):https://blog.csdn.net/hancoder/article/details/107612619
笔记-高级篇(P340):https://blog.csdn.net/hancoder/article/details/107612746
笔记-vue:https://blog.csdn.net/hancoder/article/details/107007605
笔记-elastic search、上架、检索:https://blog.csdn.net/hancoder/article/details/113922398
笔记-认证服务:https://blog.csdn.net/hancoder/article/details/114242184
笔记-分布式锁与缓存:https://blog.csdn.net/hancoder/article/details/114004280
笔记-集群篇:https://blog.csdn.net/hancoder/article/details/107612802
springcloud笔记:https://blog.csdn.net/hancoder/article/details/109063671
笔记版本说明:2020年提供过笔记文档,但只有P1-P50的内容,2021年整理了P340的内容。请点击标题下面分栏查看系列笔记
声明:
sql:https://github.com/FermHan/gulimall/sql文件
本项目其他笔记见专栏:https://blog.csdn.net/hancoder/category_10822407.html
本篇2.5W字,请直接ctrl+F搜索内容
构建gulimall-cart,复制静态资源到nginx,修改网关
购物车分为离线购物车和登录购物车
离线购物车重启浏览器了也还有
特点:读多写少,放入数据库并不合适
登录状态:登录购物车
未登录状态:离线购物车
购物车
{
skuid:123123,
check:true, # 每一项是否被选中
title:"apple ...",
defaultImage:"",
price:4999,
count:1,
totalPrice:4999, # 商品的总价=单价*数量
skuSaleVO:{...}
}
购物车不只一条数据
[
{sku1},{sku2},{}
]
redis有5种不同数据结构,这里选择哪一种比较合适呢?Map<String,List<String>>
不好的方式:不同用户应该有独立的购物车,因此购物车应该以用户作为key来存储,value是用户的所有购车信息。这样看来基本的k-v结构就可以了。
但是,我们对购车中的商品进行增、删、改操作,基本都需要根据商品id讲行判断,为了方便后期处理,我们的购车也应该是k-v结构,key是商品id,value才是这个商品的购车信息。
一个购物车是由各个购物项组成的,但是我们用
List
进行存储并不合适,因为使用List
查找某个购物项时需要挨个遍历每个购物项,会造成大量时间损耗,为保证查找速度,我们使用hash
进行存储每个人都有一个hash表,key为skuId,value为数据
public class CartItem {
private Long skuId;
/*** 是否被选中*/
private Boolean check = true;
private String title;
private String image;
private List<String> skuAttr;
/*** 价格*/
private BigDecimal price;
/*** 数量*/
private Integer count;
public class Cart {
private List<CartItem> items;
/*** 商品的数量*/
private Integer countNum;
/*** 商品的类型数量*/
private Integer countType;
/*** 整个购物车的总价*/
private BigDecimal totalAmount;
/*** 减免的价格*/
private BigDecimal reduce = new BigDecimal("0.00");
/*** 计算商品的总量*/
public Integer getCountNum() {
int count = 0;
if(this.items != null && this.items.size() > 0){
for (CartItem item : this.items) {
count += item.getCount();
}
}
return count;
}
public Integer getCountType() {
int count = 0;
if(this.items != null && this.items.size() > 0){
for (CartItem item : this.items) {
count += 1;
}
}
return count;
}
public BigDecimal getTotalAmount() {
BigDecimal amount = new BigDecimal("0");
if(this.items != null && this.items.size() > 0){
for (CartItem item : this.items) {
if(item.getCheck()){
BigDecimal totalPrice = item.getTotalPrice();
amount = amount.add(totalPrice);
}
}
}
return amount.subtract(this.getReduce());
}
threadlocal的效果是其中存储的内容只有当前线程能访问的
如果想了解更多threadlocal知识可以查看:https://blog.csdn.net/hancoder/article/details/107853513
threadlocal的原理是每个线程都有一个map,key为threadlocal对象,value为对象所对应的值
参考京东,在点击购物车时,会为临时用户生成一个name
为user-key
的cookie
临时标识,过期时间为一个月,如果手动清除user-key
,那么临时购物车的购物项也被清除,所以user-key
是用来标识和存储临时购物车数据的
但是注意的是tomcat中线程可以复用,所以线程和会话不是一对一的关系。但是没有关系,会在拦截器中先判断会话有没有用户信息(cookie),
购物车拦截器的配置
@Configuration
public class GulimallWebConfig implements WebMvcConfigurer {
//拦截所有请求
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new CartInterceptor()).addPathPatterns("/**");
}
}
购物车拦截器
public class CartInterceptor implements HandlerInterceptor {
// 静态,
public static ThreadLocal<UserInfoTo> threadLocal = new ThreadLocal<>();
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 准备好要设置到threadlocal里的user对象
UserInfoTo userInfoTo = new UserInfoTo();
HttpSession session = request.getSession();
// 获取loginUser对应的用户value,没有也不去登录了。登录逻辑放到别的代码里,需要登录时再重定向
MemberRespVo user = (MemberRespVo) session.getAttribute(AuthServerConstant.LOGIN_USER);
if (user != null){ // 用户登陆了,设置userId
userInfoTo.setUsername(user.getUsername());
userInfoTo.setUserId(user.getId());
}
// 不登录也没关系,可以访问临时用户购物车
// 去查看请求带过来的cookies里的临时购物车cookie
Cookie[] cookies = request.getCookies();
if(cookies != null && cookies.length > 0){
for (Cookie cookie : cookies) {
String name = cookie.getName();
if(name.equals(CartConstant.TEMP_USER_COOKIE_NAME)){
userInfoTo.setUserKey(cookie.getValue());
userInfoTo.setTempUser(true);
}
}
}
// 如果没有临时用户 则分配一个临时用户 // 分配的临时用户在postHandle的时候放到cookie里即可
if (StringUtils.isEmpty(userInfoTo.getUserKey())){
String uuid = UUID.randomUUID().toString().replace("-","");
userInfoTo.setUserKey("GULI-" + uuid);//临时用户
}
threadLocal.set(userInfoTo);
return true;
// 还有一个登录后应该删除临时购物车的逻辑没有实现
}
/**
* 执行完毕之后分配临时用户让浏览器保存
*/
@Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
UserInfoTo userInfoTo = threadLocal.get();
// 如果是临时用户,返回临时购物车的cookie
if(!userInfoTo.isTempUser()){
Cookie cookie = new Cookie(CartConstant.TEMP_USER_COOKIE_NAME, userInfoTo.getUserKey());
// 设置这个cookie作用域 过期时间
cookie.setDomain(