购物车
离线购物车
- 离线的时候保存着用户没有登录时的购物车信息
- 等用户登录后,离线购物车的内容自动合并到登录用户的购物车内
- 离线购物车清空
vo封装
购物车的各个属性都需要计算
@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;
}
}
@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)
- 拦截器判断用户是否登录
- 登录保存用户id
- 没登录保存用户user-key
- 保存用户信息,共享出去
拦截器
@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);
}
}
}
注册拦截器
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册拦截器 -> 拦截所有请求
registry.addInterceptor(new CartInterceptor()).addPathPatterns("/**");
}
}
购物车功能(redis保存,异步编排)
controller方法
@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
运用了线程池以及异步编排
@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;
}
获取购物车功能
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;
}
功能测试
发送请求后:
解决页面刷新,再次发送请求的问题
@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>() {});
}
增加用户登录后合并购物车功能
/**
* 购物车列表
* 浏览器有一个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";
}
@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;
}
修复用户登录后获取购物车失败
@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;
}