微服务实战——购物车模块实战

购物车

1. 数据模型分析

1.1. 需求描述

  • 用户可以在登录状态下将商品添加到购物车【用户购物车/在线购物车】
    • 放入数据库
    • mongodb
    • 放入 redis(采用)
      • 登录以后,会将临时购物车的数据全部合并过来,并清空临时购物车;
  • 用户可以在未登录状态下将商品添加到购物车【游客购物车/离线购物车/临时购物车】
    • 放入 localstorage(客户端存储,后台不存)
    • cookie
    • WebSQL
    • 放入 redis(采用)
      • 浏览器即使关闭,下次进入,临时购物车数据都在
  • 用户可以使用购物车一起结算下单
  • 给购物车添加商品
  • 用户可以查询自己的购物车
  • 用户可以在购物车中修改购买商品的数量。
  • 用户可以在购物车中删除商品。
  • 选中不选中商品
  • 在购物车中展示商品优惠信息
  • - 提示购物车商品价格变化

1.2. 数据存储

购物车是一个读多写多的场景,因此放入数据库并不合适,但购物车又是需要持久化,因此这里我们选用redis存储购物车数据。

1.3. 数据结构

购物项

因此每一个购物项信息,都是一个对象,基本字段包括:

{skuId: 2131241, check: true, title: "Apple iphone.....", defaultImage: "...", price: 4999, count: 1, totalPrice: 4999, skuSaleVO: {...}
}

另外,购物车中不止一条数据,因此最终会是对象的数组。即:

[{...},{...},{...}
]

Redis 有 5 种不同数据结构,这里选择哪一种比较合适呢?Map<String, List<String>>

  • 首先不同用户应该有独立的购物车,因此购物车应该以用户的作为 key 来存储,Value 是用户的所有购物车信息。这样看来基本的`k-v`结构就可以了。
  • 但是,我们对购物车中的商品进行增、删、改操作,基本都需要根据商品 id 进行判断,为了方便后期处理,我们的购物车也应该是`k-v`结构,key 是商品 id,value 才是这个商品的购物车信息。

综上所述,我们的购物车结构是一个双层 Map :Map<String,Map<String,String>>

  • 第一层 Map,Key 是用户 id
  • 第二层 Map,Key 是购物车中商品 id,值是购物项数据

一个购物车是由各个购物项组成的,但是我们用 List进行存储并不合适,因为使用 List查找某个购物项时需要挨个遍历每个购物项,会造成大量时间损耗,为保证查找速度,我们使用 hash进行存储。

1.4. 流程

参照京东

user-key 是随机生成的 id,不管有没有登录都会有这个 cookie 信息。

两个功能:新增商品到购物车、查询购物车。

新增商品:判断是否登录

  • 是:则添加商品到后台 Redis 中,把 user 的唯一标识符作为 key。
  • 否:则添加商品到后台 redis 中,使用随机生成的 user-key 作为 key。

查询购物车列表:判断是否登录

  • 否:直接根据 user-key 查询 redis 中数据并展示
  • 是:已登录,则需要先根据 user-key 查询 redis 是否有数据。
    • 有:需要提交到后台添加到 redis,合并数据,而后查询。
    • 否:直接去后台查询 redis,而后返回。

2. VO编写

2.1. 因此每一个购物项信息,都是一个对象,基本字段包括:

添加“com.cwh.gulimall.cart.vo.CartItem”类,代码如下:

package com.cwh.gulimall.cart.vo;import java.math.BigDecimal;
import java.util.List;/*** @Description: 购物项* @Date: 2024/5/19 19:04* @Version 1.0*/
public class CartItem {/*** 商品id*/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 Long getSkuId() {return skuId;}public void setSkuId(Long skuId) {this.skuId = skuId;}public Boolean getCheck() {return check;}public void setCheck(Boolean check) {this.check = check;}public String getTitle() {return title;}public void setTitle(String title) {this.title = title;}public String getImage() {return image;}public void setImage(String image) {this.image = image;}public List<String> getSkuAttr() {return skuAttr;}public void setSkuAttr(List<String> skuAttr) {this.skuAttr = skuAttr;}public BigDecimal getPrice() {return price;}public void setPrice(BigDecimal price) {this.price = price;}public Integer getCount() {return count;}public void setCount(Integer count) {this.count = count;}/*** 计算当前购物项总价** @return*/public BigDecimal getTotalPrice() {return this.price.multiply(new BigDecimal("" + this.count));}public void setTotalPrice(BigDecimal totalPrice) {this.totalPrice = totalPrice;}
}

添加“com.cwh.gulimall.cart.vo.Cart”类,代码如下:

package com.cwh.gulimall.cart.vo;import java.math.BigDecimal;
import java.util.List;/*** @Description: 整体购物车  需要计算的属性,必须重写他的get方法,保证每次获取属性都会进行计算* @Date: 2024/5/19 19:07* @Version 1.0*/
public class Cart {/*** 购物车子项信息*/List<CartItem> items;/*** 商品数量*/private Integer countNum;/*** 商品类型数量*/private Integer countType;/*** 商品总价*/private BigDecimal totalAmount;/*** 减免价格*/private BigDecimal reduce = new BigDecimal("0.00");public List<CartItem> getItems() {return items;}public void setItems(List<CartItem> items) {this.items = items;}public Integer getCountNum() {int count = 0;if (items != null && items.size() > 0) {for (CartItem item : items) {count += item.getCount();}}return count;}public Integer getCountType() {int count = 0;if (items != null && items.size() > 0) {for (CartItem item : items) {count += 1;}}return count;}public BigDecimal getTotalAmount() {BigDecimal amount = new BigDecimal("0");// 1、计算购物项总价if (items != null && items.size() > 0) {for (CartItem item : items) {if (item.getCheck()) {BigDecimal totalPrice = item.getTotalPrice();amount = amount.add(totalPrice);}}}// 2、减去优惠总价BigDecimal subtract = amount.subtract(getReduce());return subtract;}public BigDecimal getReduce() {return reduce;}public void setReduce(BigDecimal reduce) {this.reduce = reduce;}
}

2.2. 导入redis和SpringSession的依赖

<!--整合SpringSession完成session共享问题-->
<dependency><groupId>org.springframework.session</groupId><artifactId>spring-session-data-redis</artifactId>
</dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

2.3. 配置redis和SpringSession存储类型

#配置redis
spring.redis.host=192.168.119.127
spring.redis.port=6379spring.session.store-type=redis

2.4. 添加SpringSession配置类

添加“com.cwh.gulimall.cart.config.GulimallSessionConfig”类,代码如下:

@EnableRedisHttpSession  //自动开启RedisHttpSession
@Configuration
public class GulimallSessionConfig {@Beanpublic CookieSerializer cookieSerializer(){DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer();cookieSerializer.setDomainName("gulimall.com");cookieSerializer.setCookieName("GULISESSION");return cookieSerializer;}@Beanpublic RedisSerializer<Object> springSessionDefaultRedisSerializer(){return new GenericJackson2JsonRedisSerializer();}
}

3. ThreadLocal用户身份鉴别

3.1. ThreadLocal同一个线程共享数据

3.2. 核心原理

Map<Thread,Object> threadLocal

3.3. 用户身份鉴别方式

参考京东,在点击购物车时,会为临时用户生成一个name为user-key的cookie临时标识,过期时间为一个月,如果手动清除user-key,那么临时购物车的购物项也被清除,所以user-key是用来标识和存储临时购物车数据的。

3.4. 使用ThreadLocal进行用户身份鉴别信息传递

  • 在调用购物车的接口前,先通过session信息判断是否登录,并分别进行用户身份信息的封装,并把user-key放在cookie中
  • 这个功能使用拦截器进行完成

添加“com.cwh.gulimall.cart.vo.UserInfoTo”类,代码如下:

@ToString
@Data
public class UserInfoTo {private Long userId;private String userKey; //一定封装private boolean tempUser = false;  //判断是否有临时用户
}

添加“com.cwh.gulimall.cart.interceptor.CartInterceptor”类,代码如下:

package com.cwh.gulimall.cart.interceptor;import com.cwh.common.constant.AuthServerConstant;
import com.cwh.common.constant.CartConstant;
import com.cwh.common.vo.MemberResponseVO;
import com.cwh.gulimall.cart.vo.UserInfoTo;
import org.apache.commons.lang.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.util.UUID;/*** @Description: 在执行目标方法之前,判断用户的登录状态。并封装传递给目标请求* @Date: 2024/5/19 19:39* @Version 1.0*/
public class CartInterceptor implements HandlerInterceptor {// ThreadLocal同一个线程共享数据public static ThreadLocal<UserInfoTo> threadLocal = new ThreadLocal<>();/*** 在目标方法执行之前拦截** @param request* @param response* @param handler* @return* @throws Exception*/@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {UserInfoTo userInfoTo = new UserInfoTo();HttpSession session = request.getSession();MemberResponseVO member = (MemberResponseVO) session.getAttribute(AuthServerConstant.LOGIN_USER);// 1、用户登录,封装用户idif (member != null) {userInfoTo.setUserId(member.getId());}// 2、如果有临时用户,封装临时用户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);}}}// 3、如果没有临时用户,一定保存一个临时用户if (StringUtils.isEmpty(userInfoTo.getUserKey())) {String uuid = UUID.randomUUID().toString();userInfoTo.setUserKey(uuid);}// 目标方法执行之前,将用户信息保存到ThreadLocalthreadLocal.set(userInfoTo);return true;}/*** 业务执行之后 分配临时用户,让浏览器保存** @param request* @param response* @param handler* @param modelAndView* @throws Exception*/@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {UserInfoTo userInfoTo = threadLocal.get();// 如果没有临时用户,第一次访问购物车就添加临时用户if (!userInfoTo.isTempUser()) {// 持续的延长用户的过期时间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);}}
}

添加拦截器的配置,不能只把拦截器加入容器中,不然拦截器不生效的

添加“com.cwh.gulimall.cart.config.GulimallWebConfig”类,代码如下:

@Configuration
public class GulimallWebConfig implements WebMvcConfigurer {/*** 添加拦截器的配置,不能只把拦截器加入容器中,不然拦截器不生效的* @param registry*/@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new CartInterceptor()).addPathPatterns("/**");}
}

添加“com.cwh.gulimall.cart.controller.CartController”类,代码如下:

@Controller
public class CartController {/*** 浏览器有一个cookie;user-key:标识用户身份,一个月后过期* 如果第一次使用jd购物车功能,都会给一个临时的用户身份* 浏览器保存,每次访问都会带上有这个cookies** 登录  session有* 没登录,按照cookie里面带来的user-key来做* 第一次,如果没有临时用户,帮忙创建一个临时用户*/@GetMapping("/cart.html")public String cartListPage(){//快速得到用户信息,id,user-keyUserInfoTo userInfoTo = CartInterceptor.threadLocal.get();System.out.println(userInfoTo);return "cartList";}
}

4. 添加商品到购物车

在gulimall-product模块,修改“加入购物车”按钮


修改“com.cwh.gulimall.cart.controller.CartController”类,代码如下:

    /*** 添加商品到购物车*/@GetMapping("/addToCart")public String addToCart(@RequestParam("skuId") Long skuId,@RequestParam("num") Integer num,Model model) {CartItem cartItem = cartService.addToCart(skuId,num);model.addAttribute("item", cartItem);return "success";}

修改“com.cwh.gulimall.cart.service.CartService”类,代码如下:

public interface CartService {CartItem addToCart(Long skuId, Integer num) throws ExecutionException, InterruptedException;
}

业务逻辑

  • 若当前商品已经存在购物车,只需增添数量
  • 否则需要查询商品购物项所需信息,并添加新商品至购物车

修改“com.cwh.gulimall.cart.service.impl.CartServiceImpl”类,代码如下:

@Slf4j
@Service
public class CartServiceImpl implements CartService {private final String CART_PREFIX = "gulimall:cart";@AutowiredStringRedisTemplate stringRedisTemplate;@AutowiredProductFeignService productFeignService;@AutowiredThreadPoolExecutor executor;@Overridepublic CartItem addToCart(Long skuId, Integer num) throws ExecutionException, InterruptedException {// 获取我们要操作的购物车,临时购物车、用户购物车BoundHashOperations<String, Object, Object> cartOps = getCartOps();String res = (String) cartOps.get(skuId.toString());// 1、添加新商品到购物车(购物车无此商品)if (StringUtils.isEmpty(res)) {CartItem cartItem = new CartItem();/*** 异步查询*/CompletableFuture<Void> getSkuInfo = CompletableFuture.runAsync(() -> {// 1.1、远程查询要添加的商品信息R skuInfo = productFeignService.getSkuInfo(skuId);SkuInfoVo data = skuInfo.getData("skuInfo", new TypeReference<SkuInfoVo>() {});cartItem.setCheck(true);cartItem.setCount(1);cartItem.setImage(data.getSkuDefaultImg());cartItem.setTitle(data.getSkuTitle());cartItem.setSkuId(skuId);cartItem.setPrice(data.getPrice());}, executor);CompletableFuture<Void> getSkuSaleAttrValues = CompletableFuture.runAsync(() -> {// 1.2、远程查询sku的组合信息List<String> values = productFeignService.getSkuSaleAttrValues(skuId);cartItem.setSkuAttr(values);}, executor);CompletableFuture.allOf(getSkuInfo, getSkuSaleAttrValues).get();String jsonString = JSON.toJSONString(cartItem);cartOps.put(skuId.toString(), jsonString);return cartItem;} else {// 2、购物车有此商品,将数据取出修改数量即可CartItem cartItem = JSON.parseObject(res, CartItem.class);cartItem.setCount(cartItem.getCount() + num);cartOps.put(skuId.toString(), JSON.toJSONString(cartItem));return cartItem;}}/*** 获取我们要操作的购物车,临时购物车、用户购物车** @return*/private BoundHashOperations<String, Object, Object> getCartOps() {// 得到用户信息 账号用户 、临时用户UserInfoTo userInfoTo = CartInterceptor.threadLocal.get();// 1、userInfoTo.getUserId()不为空表示账号用户,反之临时用户  然后决定用临时购物车还是用户购物车// 放入缓存的keyString cartKey = "";if (userInfoTo.getUserId() != null) {cartKey = CART_PREFIX + userInfoTo.getUserId();} else {cartKey = CART_PREFIX + userInfoTo.getUserKey();}BoundHashOperations<String, Object, Object> operations = stringRedisTemplate.boundHashOps(cartKey);return operations;}
}
4.1.1. 配置线程池

application.properties添加线程池配置

gulimall.thread.core= 20
gulimall.thread.max-size= 200
gulimall.thread.keep-alive-time= 10

添加“com.cwh.gulimall.cart.config.ThreadPoolConfigProperties”类,代码如下:

@ConfigurationProperties(prefix = "gulimall.thread")
@Component
@Data
public class ThreadPoolConfigProperties {private Integer core;private Integer maxSize;private Integer keepAliveTime;
}

添加“com.cwh.gulimall.cart.config.MyThreadConfig”类,代码如下:

//如果ThreadPoolConfigProperties.class类没有加上@Component注解,那么我们在需要的配置类里开启属性配置的类加到容器中
//@EnableConfigurationProperties(ThreadPoolConfigProperties.class)
@Configuration
public class MyThreadConfig {@Beanpublic ThreadPoolExecutor threadPoolExecutor(ThreadPoolConfigProperties pool){return new ThreadPoolExecutor(pool.getCore(),pool.getMaxSize(),pool.getKeepAliveTime(),TimeUnit.SECONDS,new LinkedBlockingQueue<>(100000),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());}
}
4.1.2. 远程查询要添加的商品信息

添加“com.cwh.gulimall.cart.vo.SkuInfoVo”类,代码如下

@Data
public class SkuInfoVo {private Long skuId;/*** spuId*/private Long spuId;/*** sku名称*/private String skuName;/*** sku介绍描述*/private String skuDesc;/*** 所属分类id*/private Long catalogId;/*** 品牌id*/private Long brandId;/*** 默认图片*/private String skuDefaultImg;/*** 标题*/private String skuTitle;/*** 副标题*/private String skuSubtitle;/*** 价格*/private BigDecimal price;/*** 销量*/private Long saleCount;
}

添加“com.cwh.gulimall.cart.feign.ProductFeignService”类,代码如下:

@FeignClient("gulimall-product")
public interface ProductFeignService {@RequestMapping("/product/skuinfo/info/{skuId}")R getSkuInfo(@PathVariable("skuId") Long skuId);@GetMapping("product/skusaleattrvalue/stringlist/{skuId}")public List<String> getSkuSaleAttrValues(@PathVariable("skuId") Long skuId);
}
4.1.3. 远程查询sku的组合信息

修改“com.cwh.gulimall.product.app.SkuSaleAttrValueController”类,代码如下:

    @GetMapping("stringlist/{skuId}")public List<String> getSkuSaleAttrValues(@PathVariable("skuId") Long skuId){return skuSaleAttrValueService.getSkuSaleAttrValuesAsStringList(skuId);}

修改“com.cwh.gulimall.product.service.SkuSaleAttrValueService”,代码如下:

List<String> getSkuSaleAttrValuesAsStringList(Long skuId);

修改“com.cwh.gulimall.product.service.impl.SkuSaleAttrValueServiceImpl”类,代码如下:

     @Overridepublic List<String> getSkuSaleAttrValuesAsStringList(Long skuId) {return this.baseMapper.getSkuSaleAttrValuesAsStringList(skuId);}

修改“com.cwh.gulimall.product.dao.SkuSaleAttrValueDao”类,代码如下:

List<String> getSkuSaleAttrValuesAsStringList(@Param("skuId") Long skuId);
    <select id="getSkuSaleAttrValuesAsStringList" resultType="java.lang.String">select CONCAT(attr_name,":",attr_value) from pms_sku_sale_attr_valuewhere sku_id = #{skuId}</select>
4.1.4. bug:不断刷新页面会一直增加数量

不断刷新页面会一直增加数量,所以我们修改逻辑在controller的addToCart方法里添加商品,商品添加完跳转到成功页面我们改为改成重定向另一个方法,专门查询数据跳转到成功页面

修改”com.cwh.gulimall.cart.controller.CartController“类,代码如下:

    /*** 添加商品到购物车* <p>* RedirectAttributes attributes* attributes.addFlashAttribute();将数据放在session里面可以在页面取出,但只能取一次* attributes.addAttribute("skuId",skuId); 将数据放在url后面** @return*/@GetMapping("/addToCart")public String addToCart(@RequestParam("skuId") Long skuId,@RequestParam("num") Integer num,RedirectAttributes attributes) throws ExecutionException, InterruptedException {cartService.addToCart(skuId, num);attributes.addAttribute("skuId", skuId);return "redirect:http://cart.gulimall.com/addToCartSuccess.html";}/*** 跳转到成功页** @param skuId* @param model* @return*/@GetMapping("/addToCartSuccess.html")public String addToCartSuccessPage(@RequestParam("skuId") Long skuId, Model model) {CartItem cartItem = cartService.getCartItem(skuId);model.addAttribute("item", cartItem);return "success";}

修改”com.cwh.gulimall.cart.service.CartService“类,代码如下:

    /*** 获取购物车中某个购物项** @param skuId* @return*/CartItem getCartItem(Long skuId);

修改”com.cwh.gulimall.cart.service.impl.CartServiceImpl“类,代码如下:

    @Overridepublic CartItem getCartItem(Long skuId) {BoundHashOperations<String, Object, Object> cartOps = getCartOps();String s = (String) cartOps.get(skuId.toString());CartItem cartItem = JSON.parseObject(s, CartItem.class);return cartItem;}

5. 获取&合并购物车

5.1. 获取

  • 若用户未登录,则直接使用user-key获取购物车数据
  • 否则使用userId获取购物车数据,并将user-key对应临时购物车数据与用户购物车数据合并,并删除临时购物车

修改“com.cwh.gulimall.cart.controller.CartController”类,代码如下

    @GetMapping("/cart.html")public String cartListPage(Model model) throws ExecutionException, InterruptedException {// 快速得到用户信息,id,user-key
//        UserInfoTo userInfoTo = CartInterceptor.threadLocal.get();Cart cart = cartService.getCart();model.addAttribute("cart", cart);return "cartList";}

修改“com.cwh.gulimall.cart.service.CartService”类,代码如下:

    /*** 获取整个购物车* * @return*/Cart getCart() throws ExecutionException, InterruptedException;修改“com.cwh.gulimall.cart.service.impl.CartServiceImpl”类,代码如下:@Overridepublic Cart getCart() throws ExecutionException, InterruptedException {Cart cart = new Cart();// 1、登录UserInfoTo userInfoTo = CartInterceptor.threadLocal.get();if (userInfoTo.getUserId() != null) {String cartKey = CART_PREFIX + userInfoTo.getUserId();// 1.1、如果临时购物车的数据还没有合并【合并购物车】String tempCartKey = CART_PREFIX + userInfoTo.getUserKey();List<CartItem> tempsCartItems = getCartItems(tempCartKey);if (tempsCartItems != null) {// 临时购物车有数据,需要合并for (CartItem item : tempsCartItems) {addToCart(item.getSkuId(), item.getCount());}// 清除临时购物车的数据clearCart(tempCartKey);}// 1.2、获取登录后的购物车数据【包含合并过来的临时购物车的数据,和登录后的购物车数据】List<CartItem> cartItems = getCartItems(cartKey);cart.setItems(cartItems);} else {// 2、没登录String cartKey = CART_PREFIX + userInfoTo.getUserKey();// 获取临时购物车的所有购物项List<CartItem> cartItems = getCartItems(cartKey);cart.setItems(cartItems);}return cart;}/*** 获取购物项* * @param cartKey* @return*/private List<CartItem> getCartItems(String cartKey){BoundHashOperations<String, Object, Object> operations = stringRedisTemplate.boundHashOps(cartKey);List<Object> values = operations.values();if (values != null && values.size() > 0){List<CartItem> collect = values.stream().map(obj -> {String str = (String) obj;CartItem cartItem = JSON.parseObject(str, CartItem.class);return cartItem;}).collect(Collectors.toList());return collect;}return null;}

5.2. 清空购物车数据

修改“com.cwh.gulimall.cart.service.CartService”类,代码如下:

    /*** 清空购物车数据* * @param cartKey*/void clearCart(String cartKey);修改“com.cwh.gulimall.cart.service.CartService”类,代码如下:@Overridepublic void clearCart(String cartKey) {stringRedisTemplate.delete(cartKey);}

6. 选中购物车项

修改“com.cwh.gulimall.cart.service.CartService”类,代码如下:

/**

     * 勾选购物项* @param skuId* @param check*/void checkItem(Long skuId, Integer check);
修改“com.cwh.gulimall.cart.service.impl.CartServiceImpl”类,代码如下:@Overridepublic void checkItem(Long skuId, Integer check) {BoundHashOperations<String, Object, Object> cartOps = getCartOps();CartItem cartItem = getCartItem(skuId);cartItem.setCheck(check == 1 ? true : false);String jsonString = JSON.toJSONString(cartItem);cartOps.put(skuId.toString(), jsonString);}/*** 获取我们要操作的购物车,临时购物车、用户购物车** @return*/private BoundHashOperations<String, Object, Object> getCartOps() {// 得到用户信息 账号用户 、临时用户UserInfoTo userInfoTo = CartInterceptor.threadLocal.get();// 1、userInfoTo.getUserId()不为空表示账号用户,反之临时用户  然后决定用临时购物车还是用户购物车// 放入缓存的keyString cartKey = "";if (userInfoTo.getUserId() != null) {cartKey = CART_PREFIX + userInfoTo.getUserId();} else {cartKey = CART_PREFIX + userInfoTo.getUserKey();}BoundHashOperations<String, Object, Object> operations = stringRedisTemplate.boundHashOps(cartKey);return operations;}

7. 修改购物项数量

修改“com.cwh.gulimall.cart.controller.CartController”类,代码如下:

    @GetMapping("/changeItemCount")public String changeItemCount(@RequestParam("skuId") Long skuId, @RequestParam("num") Integer num){cartService.changeItemCount(skuId,num);return "redirect:http://cart.gulimall.com/cart.html";}

修改“com.cwh.gulimall.cart.service.CartService”类,代码如下:

     /*** 修改购物项数量* * @param skuId* @param num*/void changeItemCount(Long skuId, Integer num);

修改“com.cwh.gulimall.cart.service.impl.CartServiceImpl”类,代码如下:

@Overridepublic void changeItemCount(Long skuId, Integer num) {CartItem cartItem = getCartItem(skuId);cartItem.setCount(num);BoundHashOperations<String, Object, Object> cartOps = getCartOps();cartOps.put(skuId.toString(),JSON.toJSONString(cartItem));}

8. 删除购物车项

修改“com.cwh.gulimall.cart.controller.CartController”类,代码如下:

    @GetMapping("/deleteItem")public String deleteItem(@RequestParam("skuId") Long skuId){cartService.deleteItem(skuId);return "redirect:http://cart.gulimall.com/cart.html";}

修改“com.cwh.gulimall.cart.service.CartService”类,代码如下:

    /*** 删除购物项* @param skuId*/void deleteItem(Long skuId);

修改“com.cwh.gulimall.cart.service.impl.CartServiceImpl”类,代码如下:

@Override
public void deleteItem(Long skuId) {BoundHashOperations<String, Object, Object> cartOps = getCartOps();cartOps.delete(skuId.toString());
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/501498.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

1961-2022年中国大陆多干旱指数数据集(SPI/SPEI/EDDI/PDSI/SC-PDSI/VPD)

DOI: 10.5194/essd-2024-270 干旱指数对于评估和管理缺水和农业风险至关重要;然而&#xff0c;现有数据集中缺乏统一的数据基础&#xff0c;导致不一致&#xff0c;对干旱指数的可比性提出了挑战。本研究致力于创建CHM_Drought&#xff0c;这是一个创新且全面的长期气象干旱数…

xilinx的高速接口构成原理和连接结构及ibert工具的使用-以k7 GTX为例

一、相关简介 Xilinx的高速接口称之为transceivers(高速收发器&#xff09;&#xff0c;这部分的电路是专用电路&#xff0c;供电等都是独立的&#xff0c;根据速率可以分为GTP/GTX/GTH/GTY/GTM等。 Xilinx的高速接口是QUAD为单位的&#xff0c;没一个QUAD由一个时钟COMMON资…

机器学习之模型评估——混淆矩阵,交叉验证与数据标准化

目录 混淆矩阵 交叉验证 数据标准化 0-1标准化 z 标准化 混淆矩阵 混淆矩阵&#xff08;Confusion Matrix&#xff09;是一种用于评估分类模型性能的工具。 它是一个二维表格&#xff0c;其中行表示实际的类别&#xff0c;列表示模型预测的类别。 假设我们有一个二分类问题&…

第R3周:RNN-心脏病预测

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 文章目录 一、前言二、代码流程1、导入包&#xff0c;设置GPU2、导入数据3、数据处理4、构建RNN模型5、编译模型6、模型训练7、模型评估 电脑环境&#xff1a;…

40% 降本:多点 DMALL x StarRocks 的湖仓升级实战

小编导读&#xff1a; 多点 DMALL 成立于2015年&#xff0c;持续深耕零售业&#xff0c;为企业提供一站式全渠道数字零售解决方案 DMALL OS。作为 DMALL OS 数字化能力的技术底座&#xff0c;大数据平台历经多次迭代平稳支撑了公司 To B 业务的快速开展。随着国家产业升级和云原…

C语言——字符函数和内存函数

目录 前言 字符函数 1strlen 模拟实现 2strcpy 模拟实现 3strcat 模拟实现 4strcmp 模拟实现 5strncpy 模拟实现 6strncat 模拟实现 7strncmp 模拟实现 8strstr 模拟实现 9strtok 10strerror 11大小写字符转换函数 内存函数 1memcpy 模拟实现 2…

职场常用Excel基础04-二维表转换

大家好&#xff0c;今天和大家一起分享一下excel的二维表转换相关内容~ 在Excel中&#xff0c;二维表&#xff08;也称为矩阵或表格&#xff09;是一种组织数据的方式&#xff0c;其中数据按照行和列的格式进行排列。然而&#xff0c;在实际的数据分析过程中&#xff0c;我们常…

软考教材重点内容 信息安全工程师 第 12 章网络安全审计技术原理与应用

12.1.1 网络安全审计概念 网络安全审计是指对网络信息系统的安全相关活动信息进行获取、记录、存储、分析和利用的工作。网络安全审计的作用在于建立“事后”安全保障措施&#xff0c;保存网络安全事件及行为信息&#xff0c;为网络安全事件分析提供线索及证据&#xff0c;以便…

TT100K数据集, YOLO格式, COCO格式

TT100K交通标志数据集, 标签txt&#xff0c;图像已经分好了测试集&#xff0c;验证集&#xff0c;训练集 1️⃣可以直接导入YOLO进行训练&#xff0c;没有细分类&#xff0c;里面有的类&#xff0c; 闲鱼9.9 解君愁 &#xff0c;明人不说暗话 https://m.tb.cn/h.T7Ossey?tk…

更改element-plus的table样式

表头样式&#xff1a; <el-table :data"props.tableData" style"width: 100%" :header-cell-style"headerCellStyle" :cell-style"cellStyle"> </el-table>样式&#xff1a; // 表头样式 const headerCellStyle {backgro…

“善弈者”也需妙手,Oclean欧可林:差异化不是说说而已

作者 | 曾响铃 文 | 响铃说 俗话说&#xff0c;“牙痛不是病&#xff0c;痛起来要人命”。这话意思大家都知道&#xff0c;牙痛虽不是什么大病&#xff0c;可一旦发作却是极难忍受。 前几日&#xff0c;Oclean欧可林举办了一场AirPump A10氧气啵啵冲牙器新品品鉴会&#xff…

数字货币支付系统开发搭建:构建未来的区块链支付生态

随着数字货币的迅猛发展&#xff0c;越来越多的企业和机构开始关注如何搭建一个高效、安全、可扩展的数字货币支付系统。区块链技术因其去中心化、安全性高、透明性强等优势&#xff0c;已成为开发数字货币支付系统的首选技术。本文将深入探讨数字货币支付系统的开发和搭建过程…

K8s高可用集群之Kubernetes集群管理平台、命令补全工具、资源监控工具部署、常用命令

K8s高可用集群之Kubernetes管理平台、补全命令工具、资源监控工具部署 1.Kuboard可视化管理平台2.kubectl命令tab补全工具3.MetricsServer资源监控工具4.Kubernetes常用命令 1.Kuboard可视化管理平台 可以选择安装k8s官网的管理平台&#xff1b;我这里是安装的其他开源平台Kub…

cka考试-02-节点维护

一.解答答案 kubectl config use-context ek8s kubectl cordon k8s-node1 kubectl drain k8s-node1 --delete-emptydir-data --ignore-daemonsets --force 二.解答思路 记住这2个cordon,drain,使用kubectl -h 查询使用方法 [root@master ~]# kubectl -h |grep -E cordon…

【pytorch】现代循环神经网络-2

1 双向循环神经网络&#xff08;Bi-RNN&#xff09; 具有单个隐藏层的双向循环神经网络的架构如图所示&#xff1a; 对于任意时间步t&#xff0c;给定一个小批量的输入数据 Xt ∈ Rnd &#xff08;样本数n&#xff0c;每个示例中的输入数d&#xff09;&#xff0c;并且令隐藏层…

服务器等保测评日志策略配置

操作系统日志 /var/log/message 系统启动后的信息和错误日志&#xff0c;是Red Hat Linux中最常用的日志之一 /var/log/secure 与安全相关的日志信息 /var/log/maillog 与邮件相关的日志信息 /var/log/cron 与定时任务相关的日志信息 /var/log/spooler 与UUCP和news设备相关的…

Flutter-插件 scroll-to-index 实现 listView 滚动到指定索引位置

scroll-to-index 简介 scroll_to_index 是一个 Flutter 插件&#xff0c;用于通过索引滚动到 ListView 中的某个特定项。这个库对复杂滚动需求&#xff08;如动态高度的列表项&#xff09;非常实用&#xff0c;因为它会自动计算需要滚动的目标位置。 使用 安装插件 flutte…

我用AI学Android Jetpack Compose之开篇

最近突发奇想&#xff0c;想学一下Jetpack Compose&#xff0c;打算用Ai学&#xff0c;学最新的技术应该要到官网学&#xff0c;不过Compose已经出来一段时间了&#xff0c;Ai肯定学过了&#xff0c;用Ai来学&#xff0c;应该问题不大&#xff0c;学习过程记录下来&#xff0c;…

PHP框架+gatewayworker实现在线1对1聊天--发送消息(6)

文章目录 发送消息原理说明发送功能实现html部分javascript代码PHP代码 发送消息原理说明 接下来我们发送聊天的文本信息。点击发送按钮的时候&#xff0c;会自动将文本框里的内容发送出去。过程是我们将信息发送到服务器&#xff0c;服务器再转发给对方。文本框的id为msgcont…

网络安全 | 信息安全管理体系(ISMS)认证与实施

网络安全 | 信息安全管理体系&#xff08;ISMS&#xff09;认证与实施 一、前言二、信息安全管理体系&#xff08;ISMS&#xff09;概述2.1 ISMS 的定义与内涵2.2 ISMS 的核心标准 ——ISO/IEC 27001 三、信息安全管理体系&#xff08;ISMS&#xff09;认证3.1 认证的意义与价值…