商城项目-分布式高级-08-购物车功能实现 购物车

购物车

离线购物车

  • 离线的时候保存着用户没有登录时的购物车信息
  • 等用户登录后,离线购物车的内容自动合并到登录用户的购物车内
  • 离线购物车清空

vo封装

购物车的各个属性都需要计算

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
java复制代码@Data
public class Cart {
List<CartItem> items;
private Integer countNum; // 商品数量
private Integer countType; // 商品类型的个数
private BigDecimal totalAmount; // 当前购物车总价格
private BigDecimal reduce = new BigDecimal(0); // 优惠价格

public Integer getCountNum() {
int count = 0;
if (items != null && items.size() > 0) {
for (CartItem item : items) {
count += item.getCount();
}
}
setCountNum(count);
return count;
}

public void setCountNum(Integer countNum) {
this.countNum = countNum;
}

public Integer getCountType() {
int count = 0;
if (items != null && items.size() > 0) {
for (CartItem item : items) {
count += 1;
}
}
setCountType(count);
return countType;
}

public void setCountType(Integer countType) {
this.countType = countType;
}

public BigDecimal getTotalAmount() {
BigDecimal count = new BigDecimal(0);
if (items != null && items.size() > 0) {
for (CartItem item : items) {
count = count.add(item.getTotalPrice();
}
}
count = count.subtract(reduce);
setTotalAmount(count);
return totalAmount;
}

public void setTotalAmount(BigDecimal totalAmount) {
this.totalAmount = totalAmount;
}

public BigDecimal getReduce() {
return reduce;
}

public void setReduce(BigDecimal reduce) {
this.reduce = reduce;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
java复制代码@Data
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;
private BigDecimal totalPrice;

public BigDecimal getTotalPrice() {
totalPrice = price.multiply(new BigDecimal(count));
return totalPrice;
}
}

拦截器判断用户是否登录(threadLocal)

  1. 拦截器判断用户是否登录
  2. 登录保存用户id
  3. 没登录保存用户user-key
  4. 保存用户信息,共享出去

拦截器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
java复制代码@Component
public class CartInterceptor implements HandlerInterceptor {

// 共享数据
public static ThreadLocal<UserInfo> userInfoLocal = new ThreadLocal<>();

/**
* 方法执行前
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
UserInfo userInfo = new UserInfo();

// 封装userInfo
HttpSession session = request.getSession();
MemberVo user = (MemberVo) session.getAttribute(AuthConstant.LOGIN_USER);
if (user != null) {
// 获取登录用户的购物车 -> userId
userInfo.setUserId(user.getId());
}
// 获取离线购物车 -> user-key
Cookie[] cookies = request.getCookies();
if (cookies != null && cookies.length > 0) {
for (Cookie cookie : cookies) {
if (cookie.getName().equals(CartConstant.User_COOKIE_NAME)) {
userInfo.setUserKey(cookie.getValue());
userInfo.setTemp(true);
break;
}
}
}
// 用户第一次登录分配一个随机的user-key
if (StringUtils.isBlank(userInfo.getUserKey())) {
userInfo.setUserKey(UUID.randomUUID().toString());
}
// 目标方法执行前
userInfoLocal.set(userInfo);
return true;
}

/**
* 方法执行后
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
UserInfo userInfo = userInfoLocal.get();

// 如果是false就表明是第一次
if (!userInfo.isTemp()) {
Cookie cookie = new Cookie(CartConstant.User_COOKIE_NAME, userInfo.getUserKey());
cookie.setDomain("localhost");
cookie.setMaxAge(CartConstant.COOKIE_TTL);
response.addCookie(cookie);
}
}
}

注册拦截器

1
2
3
4
5
6
7
8
9
java复制代码@Configuration
public class WebConfig implements WebMvcConfigurer {

@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册拦截器 -> 拦截所有请求
registry.addInterceptor(new CartInterceptor()).addPathPatterns("/**");
}
}

购物车功能(redis保存,异步编排)

controller方法

1
2
3
4
5
6
java复制代码@GetMapping("/addToCart")
public String addToCart(@RequestParam String skuId, @RequestParam Integer num, Model model) throws ExecutionException, InterruptedException {
CartItem cartItem = cartService.addToCart(skuId, num);
model.addAttribute("item", cartItem);
return "success";
}

service

运用了线程池以及异步编排

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
java复制代码@Override
public CartItem addToCart(String skuId, Integer num) throws ExecutionException, InterruptedException {
BoundHashOperations<String, Object, Object> ops = getCartOps();
CartItem cartItem;

// 判断这个商品在购物车中是否存在
Object o = ops.get(JSON.toJSONString(skuId)); // fix 保存格式为json 所以读取格式也要是json
if (Objects.isNull(o)) {
cartItem = new CartItem();
// 添加新商品:
// 1.查询当前要添加的商品信息
CompletableFuture<Void> getSkuInfoTask = CompletableFuture.runAsync(() -> {
R r = productFeignService.info(Long.parseLong(skuId)); // 远程调用
SkuInfoEntity info = BeanUtil.toBean(r.get("skuInfo"), SkuInfoEntity.class);
cartItem.setSkuId(info.getSkuId());
cartItem.setCheck(true);
cartItem.setTitle(info.getSkuTitle());
cartItem.setImage(info.getSkuDefaultImg());
cartItem.setPrice(info.getPrice());
cartItem.setCount(num);
cartItem.setTotalPrice(info.getPrice().multiply(new BigDecimal(num)));
}, thread);
// 2.查询属性信息
CompletableFuture<Void> getAttrTask = CompletableFuture.runAsync(() -> {
List<String> value = productFeignService.getSkuSaleAttrValue(skuId.toString()); // 远程调用
cartItem.setSkuAttr(value);
}, thread);

CompletableFuture.allOf(getAttrTask, getSkuInfoTask).get();
} else {
// 1.修改数量
cartItem = (CartItem) o;
cartItem.setCount(cartItem.getCount() + num);
cartItem.setTotalPrice(cartItem.getTotalPrice());
}
// 3.保存到redis中
ops.put(JSON.toJSONString(skuId), cartItem);

return cartItem;
}

获取购物车功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
java复制代码private static final String cart_prefix = "cart:";

/**
* 获取购物车
*
* @return {@link BoundHashOperations<String, Object, Object>}
*/
private BoundHashOperations<String, Object, Object> getCartOps() {
UserInfo user = CartInterceptor.userInfoLocal.get();

// 1.生成redis中的key
StringBuilder cartKey = new StringBuilder(cart_prefix);
if (user.getUserId() != null) {
cartKey.append(user.getUserId());
} else {
cartKey.append(user.getUserKey());
}

BoundHashOperations<String, Object, Object> ops = redisTemplate.boundHashOps(cartKey.toString());
return ops;
}

功能测试

image-20201224190631928

image-20201224190658760

发送请求后:

image-20201224190715301

image-20201224190727885

解决页面刷新,再次发送请求的问题

1
2
3
4
5
6
java复制代码@Override
public CartItem getCartItem(String skuId) {
BoundHashOperations<String, Object, Object> ops = getCartOps();
String s = (String) ops.get(JSON.toJSONString(skuId));
return JSON.parseObject(s, new TypeReference<CartItem>() {});
}

image-20201224203550749

增加用户登录后合并购物车功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
java复制代码/**
* 购物车列表
* 浏览器有一个cookie:user-key,用来表示用户的身份
* 登录:按session
* 没有登录:user-key
* 第一次:创建user-key
*
* @return {@link String}
*/
@GetMapping("/cartList.html")
public String cartList(Model model) throws ExecutionException, InterruptedException {
// 获取当前登录用户的信息
Cart cart = cartService.getCart();
model.addAttribute("cart",cart);
return "cartList";
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
java复制代码@Override
public Cart getCart() throws ExecutionException, InterruptedException {
UserInfo user = CartInterceptor.userInfoLocal.get();
Cart cart = new Cart();

// 1.获取离线购物车
List<CartItem> items = getCartItems(cart_prefix+user.getUserKey());
// 判断离线购物车中是否有内容
if (items != null && items.size() > 0) {
// 2.获取登录购物车
Long userId = user.getUserId();
if (userId != null) {
// 3.用戶已经登录->合并购物车->清空离线购物车
for (CartItem cartItem : items) {
addItemToCart(cartItem.getSkuId().toString(),cartItem.getCount()); // 合并购物车
}
deleteCart(cart_prefix+ user.getUserKey()); // 清空离线购物车
items = getCartItems(cart_prefix + userId); // 获取合并后的购物车内容
}
}
cart.setItems(items);

return cart;
}

/**
* 删除购物车
*
* @param key user key
*/
private void deleteCart(String key) {
redisTemplate.delete(key);
}

/**
* 根据购物项的key,获取对应购物项
*
* @param key 关键
* @return {@link List<CartItem>}
*/
private List<CartItem> getCartItems(String key) {
BoundHashOperations<String, Object, Object> ops = redisTemplate.boundHashOps(key);
List<Object> values = ops.values();
if (values != null && values.size() > 0)
return values.stream()
.map(s -> (CartItem) s)
.collect(Collectors.toList());
return null;
}

修复用户登录后获取购物车失败

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
java复制代码@Override
public Cart getCart() throws ExecutionException, InterruptedException {
UserInfo user = CartInterceptor.userInfoLocal.get();
System.out.println(user);
Cart cart = new Cart();

// 1.获取离线购物车
List<CartItem> items = getCartItems(cart_prefix + user.getUserKey());
// 判断离线购物车中是否有内容

// 2.获取登录购物车
Long userId = user.getUserId();
if (userId != null) {
// 3.用戶已经登录->合并购物车->清空离线购物车
if (items != null && items.size() > 0) {
for (CartItem cartItem : items) {
addItemToCart(cartItem.getSkuId().toString(), cartItem.getCount()); // 合并购物车
}
}
deleteCart(cart_prefix + user.getUserKey()); // 清空离线购物车
items = getCartItems(cart_prefix + userId); // 获取合并后的购物车内容
}

cart.setItems(items);

return cart;
}

image-20201226174312259

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%