新增host
192.168.56.10 cart.gulimall.com
静态资源
修改资源路径
VO编写
public class CartItemVo {
private Long skuId;
//是否选中
private Boolean check = true;
//标题
private String title;
//图片
private String image;
//商品套餐属性
private List<String> skuAttrValues;
//价格
private BigDecimal price;
//数量
private Integer count;
//总价
private BigDecimal totalPrice;
/**
* 当前购物车项总价等于单价x数量
* @return
*/
public BigDecimal getTotalPrice() {
return price.multiply(new BigDecimal(count));
}
public void setTotalPrice(BigDecimal totalPrice) {
this.totalPrice = totalPrice;
}
购物车vo
public class CartVo {
/**
* 购物车子项信息
*/
List<CartItemVo> items;
/**
* 商品数量
*/
private Integer countNum;
/**
* 商品类型数量
*/
private Integer countType;
/**
* 商品总价
*/
private BigDecimal totalAmount;
/**
* 减免价格
*/
private BigDecimal reduce = new BigDecimal("0.00");
public List<CartItemVo> getItems() {
return items;
}
public void setItems(List<CartItemVo> items) {
this.items = items;
}
//总数量=遍历每个购物项总和
public Integer getCountNum() {
int count=0;
if (items != null && items.size() > 0) {
for (CartItemVo item : items) {
count += item.getCount();
}
}
return count;
}
public void setCountNum(Integer countNum) {
this.countNum = countNum;
}
//商品类型数量=遍历所有商品类型和
public Integer getCountType() {
int count=0;
if (items != null && items.size() > 0) {
for (CartItemVo item : items) {
count += 1;
}
}
return count;
}
public void setCountType(Integer countType) {
this.countType = countType;
}
//总价为单个购物项总价-优惠
public BigDecimal getTotalAmount() {
BigDecimal total = new BigDecimal(0);
if (items != null && items.size() > 0) {
for (CartItemVo item : items) {
total.add(item.getTotalPrice());
}
}
total.subtract(reduce);
return total;
}
public void setTotalAmount(BigDecimal totalAmount) {
this.totalAmount = totalAmount;
}
public BigDecimal getReduce() {
return reduce;
}
public void setReduce(BigDecimal reduce) {
this.reduce = reduce;
}
}
参考京东,在点击购物车时,会为临时用户生成一个name
为user-key
的cookie
临时标识,过期时间为一个月,如果手动清除user-key
,那么临时购物车的购物项也被清除,所以 user-key
是用来标识和存储临时购物车数据的
user-key
放在cookie中public class CartInterceptor implements HandlerInterceptor {
public static ThreadLocal<UserInfoTo> threadLocal = new ThreadLocal<>();
/**
* 目标方法执行之前
* @param request
* @param response
* @param handler
* @return
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
HttpSession session = request.getSession();
MemberResponseVo memberResponseVo = (MemberResponseVo) session.getAttribute(AuthServerConstant.LOGIN_USER);
UserInfoTo userInfoTo = new UserInfoTo();
// 1. 用户已经登录,设置userId
if(memberResponseVo!=null){
userInfoTo.setUserId(memberResponseVo.getId());
}
Cookie[] cookies = request.getCookies();
if(cookies.length > 0){
for (Cookie cookie : cookies) {
// 2. 如果cookie 中已经有user-Key,则直接设置
if(cookie.getName().equals(CartConstant.TEMP_USER_COOKIE_NAME)){
userInfoTo.setUserKey(cookie.getValue());
userInfoTo.setTempUser(true);
}
}
}
// 如果cookie没有user-Key,我们通过uuid生成user-key
if(StringUtils.isEmpty(userInfoTo.getUserKey())){
String uuid = UUID.randomUUID().toString();
userInfoTo.setUserKey(uuid);
}
// 4. 将用户身份认证信息放入到threadlocal进行传递
threadLocal.set(userInfoTo);
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
UserInfoTo userInfoTo = threadLocal.get();
// 如果Cookie中没有user-key,我们为其生成
if(!userInfoTo.getTempUser()){
Cookie cookie = new Cookie(CartConstant.TEMP_USER_COOKIE_NAME, userInfoTo.getUserKey());
cookie.setDomain("gulimall.com");
cookie.setMaxAge(CartConstant.TEMP_USER_COOKIE_TIMEOUT);
response.addCookie(cookie);
}
}
}
/**
* 添加商品到购物车
* RedirectAttributes.addFlashAttribute():将数据放在session中,可以在页面中取出,但是只能取一次
* RedirectAttributes.addAttribute():将数据拼接在url后面,?skuId=xxx
* @return
*/
@RequestMapping("/addCartItem")
public String addCartItem(@RequestParam("skuId") Long skuId, @RequestParam("num") Integer num, RedirectAttributes attributes) {
cartService.addCartItem(skuId, num);
attributes.addAttribute("skuId", skuId);
//为了防止成功页刷新可以重复提交添加商品,我们不直接转到成功页
return "redirect:http://cart.gulimall.com/addCartItemSuccess";
}
@RequestMapping("/addCartItemSuccess")
public String addCartItemSuccess(@RequestParam("skuId") Long skuId,Model model) {
CartItemVo cartItemVo = cartService.getCartItem(skuId);
model.addAttribute("cartItem", cartItemVo);
return "success";
}
public CartItemVo addCartItem(Long skuId, Integer num) {
//获取当前以当前用户标识为key的hash的操作
BoundHashOperations<String, Object, Object> ops = getCartItemOps();
// 判断当前商品是否已经存在购物车
String cartJson = (String) ops.get(skuId.toString());
// 1 已经存在购物车,将数据取出并添加商品数量
if (!StringUtils.isEmpty(cartJson)) {
//1.1 将json转为对象并将count+
CartItemVo cartItemVo = JSON.parseObject(cartJson, CartItemVo.class);
cartItemVo.setCount(cartItemVo.getCount() + num);
//1.2 将更新后的对象转为json并存入redis
String jsonString = JSON.toJSONString(cartItemVo);
ops.put(skuId.toString(), jsonString);
return cartItemVo;
} else {
CartItemVo cartItemVo = new CartItemVo();
// 2 未存在购物车,则添加新商品
CompletableFuture<Void> future1 = CompletableFuture.runAsync(() -> {
//2.1 远程查询sku基本信息
R info = productFeignService.info(skuId);
SkuInfoVo skuInfo = info.getData("skuInfo", new TypeReference<SkuInfoVo>() {
});
cartItemVo.setCheck(true);
cartItemVo.setCount(num);
cartItemVo.setImage(skuInfo.getSkuDefaultImg());
cartItemVo.setPrice(skuInfo.getPrice());
cartItemVo.setSkuId(skuId);
cartItemVo.setTitle(skuInfo.getSkuTitle());
}, executor);
//2.2 远程查询sku属性组合信息
CompletableFuture<Void> future2 = CompletableFuture.runAsync(() -> {
List<String> attrValuesAsString = productFeignService.getSkuSaleAttrValuesAsString(skuId);
cartItemVo.setSkuAttrValues(attrValuesAsString);
}, executor);
try {
CompletableFuture.allOf(future1, future2).get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
//2.3 将该属性封装并存入redis,登录用户使用userId为key,否则使用user-key
String toJSONString = JSON.toJSONString(cartItemVo);
ops.put(skuId.toString(), toJSONString);
return cartItemVo;
}
}
user-key
获取购物车数据userId
获取购物车数据,并将user-key
对应临时购物车数据与用户购物车数据合并,并删除临时购物车@RequestMapping("/cart.html")
public String getCartList(Model model) {
CartVo cartVo=cartService.getCart();
model.addAttribute("cart", cartVo);
return "cartList";
}
@Override
public CartVo getCart() {
CartVo cartVo = new CartVo();
UserInfoTo userInfoTo = CartInterceptor.threadLocal.get();
//1 用户未登录,直接通过user-key获取临时购物车
List<CartItemVo> tempCart = getCartByKey(CartConstant.CART_PREFIX + userInfoTo.getUserKey());
if (StringUtils.isEmpty(userInfoTo.getUserId())) {
List<CartItemVo> cartItemVos = tempCart;
cartVo.setItems(cartItemVos);
}else {
//2 用户登录
//2.1 查询userId对应的购物车
List<CartItemVo> userCart = getCartByKey(CartConstant.CART_PREFIX + userInfoTo.getUserId());
//2.2 查询user-key对应的临时购物车,并和用户购物车合并
if (tempCart!=null&&tempCart.size()>0){
BoundHashOperations<String, Object, Object> ops = redisTemplate.boundHashOps(CartConstant.CART_PREFIX + userInfoTo.getUserId());
for (CartItemVo cartItemVo : tempCart) {
userCart.add(cartItemVo);
//2.3 在redis中更新数据
addCartItem(cartItemVo.getSkuId(), cartItemVo.getCount());
}
}
cartVo.setItems(userCart);
//2.4 删除临时购物车数据
redisTemplate.delete(CartConstant.CART_PREFIX + userInfoTo.getUserKey());
}
return cartVo;
}
@RequestMapping("/checkCart")
public String checkCart(@RequestParam("isChecked") Integer isChecked,@RequestParam("skuId")Long skuId) {
cartService.checkCart(skuId, isChecked);
return "redirect:http://cart.gulimall.com/cart.html";
}
//修改skuId对应购物车项的选中状态
@Override
public void checkCart(Long skuId, Integer isChecked) {
BoundHashOperations<String, Object, Object> ops = getCartItemOps();
String cartJson = (String) ops.get(skuId.toString());
CartItemVo cartItemVo = JSON.parseObject(cartJson, CartItemVo.class);
cartItemVo.setCheck(isChecked==1);
ops.put(skuId.toString(),JSON.toJSONString(cartItemVo));
}
@RequestMapping("/countItem")
public String changeItemCount(@RequestParam("skuId") Long skuId, @RequestParam("num") Integer num) {
cartService.changeItemCount(skuId, num);
return "redirect:http://cart.gulimall.com/cart.html";
}
@Override
public void changeItemCount(Long skuId, Integer num) {
BoundHashOperations<String, Object, Object> ops = getCartItemOps();
String cartJson = (String) ops.get(skuId.toString());
CartItemVo cartItemVo = JSON.parseObject(cartJson, CartItemVo.class);
cartItemVo.setCount(num);
ops.put(skuId.toString(),JSON.toJSONString(cartItemVo));
}
@RequestMapping("/deleteItem")
public String deleteItem(@RequestParam("skuId") Long skuId) {
cartService.deleteItem(skuId);
return "redirect:http://cart.gulimall.com/cart.html";
}
@Override
public void deleteItem(Long skuId) {
BoundHashOperations<String, Object, Object> ops = getCartItemOps();
ops.delete(skuId.toString());
}