Grain Mall Notes + Stepping on the Pit (18) - Shopping Cart

Table of contents

1. Environment construction

1.1, shopping cart module initialization

1.2. Dynamic and static resource processing

1.3, page jump configuration

2. Data model analysis

2.1. Shopping Cart Requirements

2.1.1, offline shopping cart and online shopping cart requirements, database selection redis

2.1.2. Shopping cart data structure

2.2. Model class extraction, Cart and CartItem

2.3, Redis dependency and configuration, SpringSession configuration class

3. ThreadLocal user authentication

3.1. Demand analysis 

3.2. The transfer object encapsulates the temporary user id, userKey, whether there is a temporary user

3.3. Create a shopping cart constant class

3.4, custom interceptor, temporary user information into ThreadLocal<>

3.5. Add the interceptor to the WebMvcConfigurer configuration class

3.6, Controller processing shopping cart request

4. Add items to the shopping cart

4.1. Front-end page modification

4.2、Controller

4.3、service 

4.3.1. Remotely query the combination information of sku

4.3.2, remote query sku combination information

4.3.3, asynchronous arrangement, custom thread pool configuration class

4.3.4, add shopping cart business realization

4.4. Refresh the page and keep sending requests, RedirectAttribute

4.4.0, analysis

4.4.1. Changed to redirect to the successfully added page and query the shopping cart data

4.4.2, Service layer CartServiceImpl implementation class write method to obtain a certain shopping item in the shopping cart

4.4.3, modification of success page

5. Get the shopping cart

6. Select the shopping item [whether selected]

7. Modify the quantity of shopping items

7.1. Front-end cartList.html page modification

7.2. Writing the back-end interface

8. Delete shopping items

8.1, front-end modification

8.2, back-end interface


1. Environment construction

1.1, shopping cart module initialization

The first step is to create the gulimall- cart service and perform version downgrade processing

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.8.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.atguigu</groupId>
<artifactId>gulimall-cart</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>gulimall-cart</name>
<description>购物车</description>
<properties>
    <java.version>1.8</java.version>
    <spring-cloud.version>Greenwich.SR3</spring-cloud.version>
</properties>

insert image description here
insert image description here

The second step, hosts add domain name mapping

# Gulimall Host Start
127.0.0.1 gulimall.cn
127.0.0.1 search.gulimall.cn
127.0.0.1 item.gulimall.cn
127.0.0.1 auth.gulimall.cn
127.0.0.1 cart.gulimall.cn
# Gulimall Host End

The third step is to import public module dependencies

<dependency>
    <groupId>com.atguigu.gulimall</groupId>
    <artifactId>gulimall-common</artifactId>
    <version>0.0.1-SNAPSHOT</version>
</dependency>

Because the database is not currently used, it is excluded

@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class GulimallCartApplication {

    public static void main(String[] args) {
        SpringApplication.run(GulimallCartApplication.class, args);
    }

}

Step 4: Add nacos configuration to bootstrap.yml

server.port=40000

spring.application.name=gulimall-cart
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848

Step 5: Add annotations to the startup class and enable service registration and discovery

@EnableFeignClients
@EnableDiscoveryClient
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class GulimallCartApplication {

    public static void main(String[] args) {
        SpringApplication.run(GulimallCartApplication.class, args);
    }

}

Step 6: Modify the gateway and configure routing for the shopping cart

- id: gulimall_cart_route
  uri: lb://gulimall-cart
  predicates:
  	- Host=cart.gulimall.cn

1.2. Dynamic and static resource processing


  1. Static: Copy all static resources under the shopping cart folder in the data to the server: mydata/nginx/html/static/cartdirectory

  2. Action: Copy the two pages under the shopping cart folder in the data to templatesthe directory

  3. Replace all resource application paths in the web page

1.3, page jump configuration


Requirement: realize page jump

  1. When we item.htmlclick Add to Cart on the product details page, we will jump to the successful addition pagesuccess.html
  2. Click on the shopping cart on the success page success.htmlto enter the shopping cart list pagecartList.html
  3. On the success page success.html, click View product details to jump to the product details page
  4. index.htmlClicking on my shopping cart on the homepage also jumps to the shopping cart list pagecartList.html

Item.html in gulimall-product service

<div class="box-btns-two">
   <a href="http://cart.gulimall.cn/addToCart">
      加入购物车
   </a>
</div>

//......

<div class="nav_top_three"><a href="http://cart.gulimall.cn/cart.html">我的购物车</a><span class="glyphicon glyphicon-shopping-cart"></span>
  <div class="nav_top_three_1">
    <img src="/static/item/img/44.png"/>购物车还没有商品,赶紧选购吧!
  </div>
</div>

success.html page in Gulimall-cart service

<div class="bg_shop">
  <a class="btn-tobback" href="http://item.gulimall.cn/3.html">查看商品详情</a>
  <a class="btn-addtocart" href="http://cart.gulimall.cn/cart.html"
     id="GotoShoppingCart"><b></b>去购物车结算</a>
</div>

success.html page in Gulimall-cart service

<div class="one_top_left">
  <a href="http://gulimall.cn" class="one_left_logo"><img src="/static/cart/img/logo1.jpg"></a>
  <a href="/static/cart#" class="one_left_link">购物车</a>
</div>

//.....

<li>
    <a href="http://gulimall.cn">首页</a>
</li>

Add mapping to CartController class in Gulimall-cart service

@Controller
public class CartController {

    @GetMapping("/cart.html")
    public String cartListPage(){

        return "cartList";
    }

    /**
     * 添加商品到购物车
     * @return
     */
    @GetMapping("/addToCart")
    public String addToCart() {
        return "success";
    }
}


 

2. Data model analysis

2.1. Shopping Cart Requirements


2.1.1, offline shopping cart and online shopping cart requirements, database selection redis

Description of Requirement

Offline shopping cart:

  • Users can add items to the shopping cart [user offline temporary shopping cart] without logging in. Even if the browser is closed , the next time you enter, the temporary shopping cart data will be there .

Online shopping cart: 

  • Users can add items to the shopping cart [User Online Shopping Cart] while logged in . After logging in, all the data in the offline shopping cart will be merged and the offline shopping cart will be cleared ;


  • shopping function
    • Users can use the shopping cart to checkout and place an order together
    • Add item to cart
    • Users can query their own shopping cart
    • Users can modify the quantity of purchased items in the shopping cart
    • Users can delete items in the cart
    • Display product discount information in the shopping cart
    • Prompt shopping cart item price change

data storage:

The shopping cart is a scene with many reads and many writes, so it is not suitable to put it in the database, but the shopping cart needs to be persisted, so here we use the persistence mechanism of Redis to store the shopping cart data.

Redis is a memory database by default, and all data is stored in memory.

2.1.2. Shopping cart data structure


insert image description here

The Hash of the shopping cart is stored in Redis, the key is the user identification code such as gulimall:cart:1 , and the value is each CartItem

Each user's shopping cart in Redis is composed of various shopping items. According to the analysis, it is more appropriate to use Hash for storage here:

  • Map<String k1,Map<String k2,CartltemInfo>>
    • K1: User ID
    • Map<String k2,CartltemInfo>
      • K2: Commodity Id
      • CartltemInfo : shopping item details

2.2. Model class extraction, Cart and CartItem


insert image description here

  • The attribute that Cart
    needs to calculate must rewrite its get method to ensure that the attribute will be calculated every time it is acquired
    • Calculate the total quantity of the item
    • Calculate the number of item types
    • Calculate the total price of the item

Notice:

  • Here, @Data is not used, and the getter and setter methods are generated by ourselves, mainly for custom calculation methods for attributes such as quantity and amount. For example, the quantity of goods in Cart is calculated through the CartItem list to calculate the total quantity.
  • Amount-related data must use the BigDecimal type for precise calculations
package com.atguigu.cart.vo;
/**
 * Description: 整体购物车
 *  这里不用@Data,自己生成getter和setter方法,主要为了数量、金额等属性自定义计算方法。
 *  例如Cart里的商品数量通过CartItem列表计算总数量。
 */
public class Cart {

    /**
     * 购物车子项信息
     */
    List<CartItem> items;
    /**
     * 商品的总数量
     */
    private Integer countNum;
    /**
     * 商品类型数量
     */
    private Integer countType;
    /**
     * 商品总价
     */
    private BigDecimal totalAmount;
    /**
     * 减免价格
     */
    private BigDecimal reduce = new BigDecimal("0");

//需要计算的属性,必须重写它的get方法,保证每次获取属性都会进行计算
    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) {
                countNum += item.getCount();
            }
        }
        return count;
    }


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


    public BigDecimal getTotalAmount() {
        BigDecimal amount = new BigDecimal("0");
        // 1、计算购物项总价
        if (items!=null && items.size()>0) {
            for (CartItem item : items) {
                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;
    }
}
  • CartItem
    • Calculate Subtotal Price
package com.atguigu.cart.vo;
/**
 * Description: 购物项内容。
 *  这里不用@Data,自己生成getter和setter方法,主要为了数量、金额等属性自定义计算方法。
 *  例如Cart里的商品数量通过CartItem列表计算总数量。
 */
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;
    }
}

2.3, Redis dependency and configuration, SpringSession configuration class


1. Import the dependencies of redis and SpringSession

<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. Write configuration

# 配置redis
spring.redis.host=124.222.223.222
spring.redis.port=6379

3. Add SpringSession configuration class (custom Session configuration class)

Function: The configuration class sets the session to use json serialization, and enlarges the scope (custom).

Copy the GulimallSessionConfig.java configuration/com/atguigu/gulimall/auth/config class under the path in the gulimall-auth-server service to the config package of the gulimall-cart service:

package com.atguigu.cart.config;

@Configuration
public class GulimallSessionConfig {

    @Bean
    public CookieSerializer cookieSerializer() {
        DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer();
        cookieSerializer.setDomainName("gulimall.cn");
        cookieSerializer.setCookieName("GULISESSION");

        return cookieSerializer;
    }

    @Bean
    public RedisSerializer<Object> springSessionDefaultRedisSerializer() {
        return new GenericJackson2JsonRedisSerializer();
    }
}


 

3. ThreadLocal user authentication

3.1. Demand analysis 

need:

  • User login , access user information in Session
  • user not logged in
    • If there is a user-key in the cookie, it means that there is a temporary user
    • If there is no user-key in the cookie, it means that there is no temporary user
      • Create a wrapper and return the user-key

ThreadLocal: the same thread shares data

  • The core principle is: Map<Thread, Object> threadLocal
  • A ThreadLocalMap object is created in each thread, and each thread can access the value in its own internal ThreadLocalMap object . Threads do not interfere with each other

A known:

  • A request comes in: interceptor ==>> Controller ==>> Service ==>> dao all use the same thread

insert image description here


(1) User identity authentication method

  • When the user clicks the shopping cart after logging in, the user is logged in
  • When the user clicks on the shopping cart when he is not logged in, a temporary cookie name named user-key will be generated for the temporary user. The expiration time is one month, and the user-key will be carried every time the browser accesses the shopping cart in the future. user-key is used to identify and store temporary shopping cart data

( 2) Use ThreadLocal to transmit user identity authentication information

  • Before calling the interface of the shopping cart, first judge whether to log in through the session information , and encapsulate the user identity information separately
    • If the session has user information, user login userInfoTo.setUserId(member.getId());
    • There is no user information in the session
      • If the user-key is carried in the cookie, it means that there is a temporary user, and the user-key is used to encapsulate the user identity information: userInfoTo.setUserKey(cookie.getValue());
        userInfoTo.setTempUser(true); and identify the carrying user-key
      • If the user-key is not carried in the cookie, it means that there is no temporary user , and it is assigned
  • Pack the information into ThreadLocal
  • After calling the interface of the shopping cart, if the user-key is not carried in the cookie, assign a temporary user and let the browser save the

The user-key is in the cookie to identify the user's identity. When using the shopping cart for the first time, a temporary user information will be given. After the browser saves the cookie, it will get the user-key from the cookie every time it visits.

3.2. The transfer object encapsulates the temporary user id, userKey, whether there is a temporary user

Transfer object, named to. 

package com.atguigu.cart.vo;

@ToString
@Data
public class UserInfoTo {
    private Long userId;
    private String userKey; 

    private boolean tempUser = false;   // 判断是否有临时用户
}

3.3. Create a shopping cart constant class

package com.atguigu.common.constant;

public class CartConstant {
    public static final String TEMP_USER_COOKIE_NAME = "user-key";
    public static final int TEMP_USER_COOKIE_TIMEOUT = 60*60*24*30;
}

3.4, custom interceptor, temporary user information into ThreadLocal<>

Business Process: 

  1. Before executing the target method, check the userKey in the cookie, if not, create a new user transfer object, and set userKey to a random uuid
  2. Encapsulate the user transfer object into ThreadLocal.
  3. After executing the target method, create a cookie and set the scope and expiration time to let the browser save

shopping cart module 

package com.xx.gulimall.cart.interceptor;

/**
 * @Description: 在执行目标方法之前,判断用户的登录状态.并封装传递给controller目标请求
 **/

public class CartInterceptor implements HandlerInterceptor {

//创建ThreadLocal<>对象,同一个线程共享数据
    public static ThreadLocal<UserInfoTo> toThreadLocal = new ThreadLocal<>();

    /***
     * 目标方法执行之前
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        UserInfoTo userInfoTo = new UserInfoTo();

        HttpSession session = request.getSession();
        //1.从session获得当前登录用户的信息
        MemberResponseVo memberResponseVo = (MemberResponseVo) session.getAttribute(LOGIN_USER);

        if (memberResponseVo != null) {
            //2.1 如果用户登录了,给用户传输对象添加id
            userInfoTo.setUserId(memberResponseVo.getId());
        }
//        3.获取cookie
        Cookie[] cookies = request.getCookies();
//        如果cookie不为空,找到和"user-key"同名的cookie,设置userKey,标记临时用户
        if (cookies != null && cookies.length > 0) {
            for (Cookie cookie : cookies) {
                //user-key
                String name = cookie.getName();
                if (name.equals(TEMP_USER_COOKIE_NAME)) {
                    userInfoTo.setUserKey(cookie.getValue());
                    //标记为已是临时用户
                    userInfoTo.setTempUser(true);
                }
            }
        }

        //如果没有临时用户一定分配一个临时用户,userKey是临时id。
        if (StringUtils.isEmpty(userInfoTo.getUserKey())) {
            String uuid = UUID.randomUUID().toString();
            userInfoTo.setUserKey(uuid);
        }

        //目标方法执行之前,将用户传输信息放到ThreadLocal里,同一个线程共享数据。
        toThreadLocal.set(userInfoTo);
        return true;
    }


    /**
     * 业务执行之后,分配临时用户来浏览器保存
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

        //获取当前用户的值
        UserInfoTo userInfoTo = toThreadLocal.get();

        //如果没有临时用户则保存一个临时用户,并延长cookie过期时间,扩大cookie域,实现子域名共享cookie。
        if (!userInfoTo.getTempUser()) {
            //创建一个cookie
            Cookie cookie = new Cookie(TEMP_USER_COOKIE_NAME, userInfoTo.getUserKey());
            //扩大作用域
            cookie.setDomain("gulimall.com");
            //设置过期时间
            cookie.setMaxAge(TEMP_USER_COOKIE_TIMEOUT);
            response.addCookie(cookie);
        }

    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

    }
}

3.5. Add the interceptor to the WebMvcConfigurer configuration class

Add the configuration of the interceptor, you can't just add the interceptor to the container, otherwise the interceptor will not take effect

package com.atguigu.cart.config;

@Configuration
public class GulimallWebConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new CartInterceptor()).addPathPatterns("/**");
    }
}

3.6, Controller processing shopping cart request

package com.atguigu.cart.controller;

@Controller
public class CartController {

    /**
     * 去往用户购物车页面
     *  浏览器有一个cookie:user-key 用来标识用户身份,一个月后过期
     *  如果第一次使用京东的购物车功能,都会给一个临时用户身份;浏览器以后保存,每次访问都会带上这个cookie;
     * 登录:Session有
     * 没登录:按照cookie里面的user-key来做。
     *  第一次:如果没有临时用户,帮忙创建一个临时用户。
     * @return
     */
    @GetMapping(value = "/cart.html")
    public String cartListPage(Model model) throws ExecutionException, InterruptedException {
        //快速得到用户信息:id,user-key
        // UserInfoTo userInfoTo = CartInterceptor.toThreadLocal.get();

        CartVo cartVo = cartService.getCart();
        model.addAttribute("cart",cartVo);
        return "cartList";
    }
}


 

4. Add items to the shopping cart

In the gulimall-product module, modify the add cart button

4.1. Front-end page modification

The first step, modify the item page

When the add to cart button is clicked, a request is sent:

http://cart.gulimall.cn/addToCart?skuId=?&num=?

  • skuId: the skuId of the current item
  • num: the number of current items added to the shopping cart

insert image description here
insert image description here

$("#addToCartA").click(function () {
   var num = $("#numInput").val();
   var skuId = $(this).attr("skuId");
   location.href = "http://cart.gulimall.cn/addToCart?skuId="+skuId+"&num="+num;
});

The second step, modify the success page

insert image description here

Business logic:

  1. The key stored in Redis
    • If the user is already logged in, the key stored in Redis is the user's Id
    • If the user is not logged in, the key stored in Redis corresponds to the temporary useruser-key
  2. Cart save
    • If the current product already exists in the shopping cart, just add the quantity
    • Otherwise, you need to query the information required for the product shopping item and add a new product to the shopping cart

4.2、Controller

1. Write the method of adding products to the shopping cart in the CartController class of the Controller layer interface

/**
 * 添加商品到购物车
 * @param skuId 商品的skuid
 * @param num   添加的商品数量
 * @return
 */
@GetMapping("/addToCart")
public String addToCart(@RequestParam("skuId") Long skuId,
                        @RequestParam("num") Integer num,
                        Model model) throws ExecutionException, InterruptedException {

    CartItem cartItem = cartService.addToCart(skuId,num);
    model.addAttribute("item",cartItem);
    return "success";
}

4.3、service 

4.3.1. Remotely query the combination information of sku


Write a remote call feign interface in the gulimall-cart service

package com.atguigu.cart.feign;

@FeignClient("gulimall-product")
public interface ProductFeignService {
    @GetMapping("/product/skusaleattrvalue/stringlist/{skuId}")
    List<String> getSkuSaleAttrValues(@PathVariable("skuId") Long skuId);
}

4.3.2, remote query sku combination information

Gulimall-product service

  1. The Controller layer writes and queries the combination information of sku
@RestController
@RequestMapping("product/skusaleattrvalue")
public class SkuSaleAttrValueController {
    @Autowired
    private SkuSaleAttrValueService skuSaleAttrValueService;

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

Write methods in the Service layer implementation class SkuSaleAttrValueServiceImpl

@Override
public List<String> getSkuSaleAttrValuesAsStringList(Long skuId) {
    SkuSaleAttrValueDao dao = this.baseMapper;
    return dao.getSkuSaleAttrValuesAsStringList(skuId);
}

SQL statement SkuSaleAttrValueDao.xml of Dao layer xml

<select id="getSkuSaleAttrValuesAsStringList" resultType="java.lang.String">
    SELECT CONCAT(attr_name,":",attr_value) FROM pms_sku_sale_attr_value WHERE sku_id=#{skuId};
</select>

Write a remote call feign interface in the gulimall-cart service

package com.atguigu.cart.feign;

@FeignClient("gulimall-product")
public interface ProductFeignService {

    @RequestMapping("/product/skuinfo/info/{skuId}")
    R getSkuInfo(@PathVariable("skuId") Long skuId);

    @GetMapping("/product/skusaleattrvalue/stringlist/{skuId}")
    List<String> getSkuSaleAttrValues(@PathVariable("skuId") Long skuId);
}

4.3.3, asynchronous arrangement, custom thread pool configuration class

Assuming that it takes 1 second to query the combination information of the sku remotely, and 1.5 seconds to query the combination information of the sku remotely, the total time-consuming is 2.5 seconds.
If asynchronous orchestration is used, it only takes 1.5 seconds.

1. Copy the MyThreadConfig and ThreadPoolConfigProperties classes under com/atguigu/gulimall/product/configthe path to the config path under the gulimall-cart service:

package com.atguigu.cart.config;

@Configuration
public class MyThreadConfig {

    @Bean
    public ThreadPoolExecutor threadPoolExecutor(ThreadPoolConfigProperties pool) {
        return new ThreadPoolExecutor(pool.getCoreSize(),
                pool.getMaxSize(),
                pool.getKeepAliveTime(),
                TimeUnit.SECONDS,
                new LinkedBlockingDeque<>(100000),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());
    }
}
package com.atguigu.cart.config;

@ConfigurationProperties(prefix = "gulimall.thread")    /自动注入
@Component
@Data
public class ThreadPoolConfigProperties {
    private Integer coreSize;
    private Integer maxSize;
    private Integer keepAliveTime;
}

2. Configure the thread pool

# 配置线程池
gulimall.thread.core-size: 20
gulimall.thread.max-size: 200
gulimall.thread.keep-alive-time: 10

4.3.4, add shopping cart business realization

CartServiceImpl  

@Slf4j
@Service
public class CartServiceImpl implements CartService {

    @Autowired
    StringRedisTemplate redisTemplate;

    @Autowired
    ProductFeignService productFeignService;

    @Autowired
    ThreadPoolExecutor executor;

    // 用户标识前缀
    private final String CART_PREFIX = "gulimall:cart:";


@Slf4j
@Service("cartService")
public class CartServiceImpl implements CartService {

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private ProductFeignService productFeignService;

    @Autowired
    private ThreadPoolExecutor executor;

    @Override
    public CartItemVo addToCart(Long skuId, Integer num) throws ExecutionException, InterruptedException {

        //1.拿到要操作的购物车redis操作器信息
        BoundHashOperations<String, Object, Object> cartOps = getCartOps();

        //2.判断Redis是否有该商品的信息
        String productRedisValue = (String) cartOps.get(skuId.toString());

        if (StringUtils.isEmpty(productRedisValue)) {
//2.1如果没有就添加数据
            //添加新的商品到购物车(redis)
            CartItemVo cartItemVo = new CartItemVo();
            //开启第一个异步任务,存商品基本信息
            CompletableFuture<Void> getSkuInfoFuture = CompletableFuture.runAsync(() -> {
                //远程调用商品模块查询当前要添加商品的sku信息
                R productSkuInfo = productFeignService.getInfo(skuId);
                SkuInfoVo skuInfo = productSkuInfo.getData("skuInfo", new TypeReference<SkuInfoVo>() {});
                //sku信息数据赋值给单个CartItem
                cartItemVo.setSkuId(skuInfo.getSkuId());
                cartItemVo.setTitle(skuInfo.getSkuTitle());
                cartItemVo.setImage(skuInfo.getSkuDefaultImg());
                cartItemVo.setPrice(skuInfo.getPrice());
                cartItemVo.setCount(num);
            }, executor);

            //开启第二个异步任务,存商品属性信息
            CompletableFuture<Void> getSkuAttrValuesFuture = CompletableFuture.runAsync(() -> {
                //2、远程查询skuAttrValues组合信息
                List<String> skuSaleAttrValues = productFeignService.getSkuSaleAttrValues(skuId);
                cartItemVo.setSkuAttrValues(skuSaleAttrValues);
            }, executor);

            //等待所有的异步任务全部完成
            CompletableFuture.allOf(getSkuInfoFuture, getSkuAttrValuesFuture).get();

            String cartItemJson = JSON.toJSONString(cartItemVo);
            cartOps.put(skuId.toString(), cartItemJson);

            return cartItemVo;
        } else {
//2.2 购物车有此商品,修改数量即可
            CartItemVo cartItemVo = JSON.parseObject(productRedisValue, CartItemVo.class);
            cartItemVo.setCount(cartItemVo.getCount() + num);
            //修改redis的数据
            String cartItemJson = JSON.toJSONString(cartItemVo);
            cartOps.put(skuId.toString(),cartItemJson);

            return cartItemVo;
        }
    }

    /**
     * 获取到要操作的购物车
     * @return
     */
    private  BoundHashOperations<String, Object, Object> getCartOps() {
        UserInfoTo userInfoTo = CartInterceptor.threadLocal.get();
        // 1、判断用户有没有登录
        String cartKey = "";   
        if (userInfoTo.getUserId() != null){
            // 用户已登录,则存储在Redis中的key 是 用户的Id
            cartKey = CART_PREFIX+userInfoTo.getUserId();
        } else {
            // 用户没有登录,则存在在Redis中的key 是 临时用户对应的 `user-key`
            cartKey = CART_PREFIX+userInfoTo.getUserKey();
        }
        // 绑定hash
        BoundHashOperations<String, Object, Object> operations = redisTemplate.boundHashOps(cartKey);
        return operations;
    }
}

insert image description here


 

4.4. Refresh the page and keep sending requests, RedirectAttribute

4.4.0, analysis

Current problem:

Keep refreshing the "add successfully" page, and the request will continue to increase, and the number will continue to grow:

insert image description here

The solution is to modify the logic here:

  • Add items in the addToCart method of the controller
  • After the product is added, it jumps to the success page. We changed it to another method of redirection, specifically querying the data and jumping to the success page.

4.4.1. Changed to redirect to the successfully added page and query the shopping cart data

/**
 * 添加商品到购物车
 * @param skuId 商品的skuid
 * @param num   添加的商品数量
 * @return
 * RedirectAttributes
 *  ra.addFlashAttribute(, ) :将数据放在session里面可以在页面里取出,但是只能取一次
 *  ra.addAttribute(,); 将数据放在url后面
 */
@GetMapping("/addToCart")
public String addToCart(@RequestParam("skuId") Long skuId,
                        @RequestParam("num") Integer num,
                        RedirectAttributes ra) throws ExecutionException, InterruptedException {
    cartService.addToCart(skuId,num);
    ra.addAttribute("skuId", skuId);
    return "redirect:http://cart.gulimall.cn/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";
}

4.4.2, Service layer CartServiceImpl implementation class write method to obtain a certain shopping item in the shopping cart

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

4.4.3, modification of success page

<div class="success-wrap">
    <div class="w" id="result">
        <div class="m succeed-box">
            <div th:if="${item!=null}" class="mc success-cont">
                <div class="success-lcol">
                    <div class="success-top"><b class="succ-icon"></b>
                        <h3 class="ftx-02">商品已成功加入购物车</h3></div>
                    <div class="p-item">
                        <div class="p-img">
                            <a href="/static/cart/javascript:;" target="_blank"><img
                                    style="height: 60px;width:60px;" th:src="${item.image}"></a>
                        </div>
                        <div class="p-info">
                            <div class="p-name">
                                <a th:href="'http://item.gulimall.cn/'+${item.skuId}+'.html'"
                                   th:text="${item.title}">TCL 55A950C 55英寸32核人工智能 HDR曲面超薄4K电视金属机身(枪色)</a>
                            </div>
                            <div class="p-extra"><span class="txt" th:text="'数量:'+${item.count}">  数量:1</span></div>
                        </div>
                        <div class="clr"></div>
                    </div>
                </div>
                <div class="success-btns success-btns-new">
                    <div class="success-ad">
                        <a href="/#none"></a>
                    </div>
                    <div class="clr"></div>
                    <div class="bg_shop">
                        <a class="btn-tobback" th:href="'http://item.gulimall.cn/'+${item.skuId}+'.html'">查看商品详情</a>
                        <a class="btn-addtocart" href="http://cart.gulimall.cn/cart.html"
                           id="GotoShoppingCart"><b></b>去购物车结算</a>
                    </div>
                </div>
            </div>
            <div th:if="${item==null}" class="mc success-cont">
                <h2>购物车中无商品</h2>
                <a href="http://gulimall.cn">去购物</a>
            </div>
        </div>
    </div>
</div>


 

5. Get the shopping cart

  • If the user is not logged in, use the user-key to get the shopping cart data in Redis

  • If the user logs in, use the userId to get the shopping cart data in Redis, and set

    • The temporary shopping cart data corresponding to user-key and
    • User cart data

    Merge and delete temporary carts.

The first step, the writing method of the CartController class in the Controller layer

@Controller
public class CartController {

    @Autowired
    CartService cartService;

    @GetMapping("/cart.html")
    public String cartListPage(Model model) throws ExecutionException, InterruptedException {
        Cart cart = cartService.getCart();
        model.addAttribute("cart",cart);
        return "cartList";
    }

The second step is to write the Service layer method

package com.atguigu.cart.service;

public interface CartService {
		//....

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

    /**
     * 获取整个购物车
     * @return
     */
    Cart getCart() throws ExecutionException, InterruptedException;

    /**
     * 清空购物车数据
     * @param cartKey
     */
    void clearCart(String cartKey);
}

Implement the class CartServiceImpl method:

@Override
public CartItem getCartItem(Long skuId) {
    BoundHashOperations<String, Object, Object> cartOps = getCartOps();
    String str = (String) cartOps.get(skuId.toString());
    CartItem cartItem = JSON.parseObject(str, CartItem.class);
    return cartItem;
}
    /**
     * 获取购物车里面的数据
     * @param cartKey
     * @return
     */
    private List<CartItemVo> getCartItems(String cartKey) {
        //获取购物车里面的所有商品
        BoundHashOperations<String, Object, Object> operations = redisTemplate.boundHashOps(cartKey);
        List<Object> values = operations.values();
        if (values != null && values.size() > 0) {
            List<CartItemVo> cartItemVoStream = values.stream().map((obj) -> {
                String str = (String) obj;
                CartItemVo cartItem = JSON.parseObject(str, CartItemVo.class);
                return cartItem;
            }).collect(Collectors.toList());
            return cartItemVoStream;
        }
        return null;

    }
@Override
public Cart getCart() throws ExecutionException, InterruptedException {

    Cart cart = new Cart();
    UserInfoTo userInfoTo = CartInterceptor.threadLocal.get();
    if (userInfoTo.getUserId()!=null){
        // 1、登录状态
        String cartKey = CART_PREFIX + userInfoTo.getUserId();
        // 2、如果临时购物车的数据还没有合并,则合并购物车
        String tempCartKey = CART_PREFIX + userInfoTo.getUserKey();
        List<CartItem> tempCartItems = getCartItems(tempCartKey);
        if (tempCartItems!=null) {
            // 临时购物车有数据,需要合并
            for (CartItem item : tempCartItems) {
                addToCart(item.getSkuId(),item.getCount());
            }
            // 清除临时购物车的数据
            clearCart(tempCartKey);
        }
        // 3、删除临时购物车
        // 4、获取登录后的购物车数据
        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;
}

@Override
public void clearCart(String cartKey) {
    // 直接删除该键
    redisTemplate.delete(cartKey);
}

Step 3: Modify the shopping cart front-end page cartList.html

insert image description here

Test Results:

insert image description here


 

6. Select the shopping item [whether selected]

**The first step, **Controller layer method writing

com/atguigu/cart/controller/Add a mapping method to the CartController.java class under the gulimall-cart service path

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

**The second step,** Write the method of whether to select the shopping item in the implementation class method of the Service layer

/**
 * 勾选购物项
 * @param skuId
 * @param check
 */
void checkItem(Long skuId, Integer check);

Implementation class of CartServiceImpl.java under path in gulimall-cartcom/atguigu/cart/service/impl/ service :

@Override
public void checkItem(Long skuId, Integer check) {
    BoundHashOperations<String, Object, Object> cartOps = getCartOps();
    CartItem cartItem = getCartItem(skuId);
    cartItem.setCheck(check==1?true:false);
    String s = JSON.toJSONString(cartItem);
    cartOps.put(skuId.toString(),s);
}

The third step, page modification

insert image description here

$(".itemCheck").click(function () {
    var skuId = $(this).attr("skuId");
    var check = $(this).prop("checked");
    location.href = "http://cart.gulimall.cn/checkItem?skuId="+skuId+"&check="+(check?1:0);
});

insert image description here


 

7. Modify the quantity of shopping items

7.1. Front-end cartList.html page modification

Front-end cartList.html page modification

<li>
    <p style="width:80px" th:attr="skuId=${item.skuId}">
        <span class="countOpsBtn">-</span>
        <span class="countOpsNum" th:text="${item.count}">5</span>
        <span class="countOpsBtn">+</span>
    </p>
</li>
$(".countOpsBtn").click(function () {
    var skuId = $(this).parent().attr("skuId");
    var num = $(this).parent().find(".countOpsNum").text();
    location.href = "http://cart.gulimall.cn/countItem?skuId="+skuId+"&num="+num; 
});

7.2. Writing the back-end interface

Writing the backend interface

  1. Controller layer interface writing

Modify the "com.atguigu.gulimall.cart.controller.CartController" class, the code is as follows:

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

Service layer writing

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

Modify the "com.atguigu.gulimall.cart.service.impl.CartServiceImpl" class, the code is as follows:

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


 

8. Delete shopping items

8.1, front-end modification

insert image description here
insert image description here

8.2, back-end interface

CartController

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

CartServiceImpl.java

/**
 * 删除购物项
 * @param skuId
 */
@Override
public void deleteItem(Long skuId) {
    BoundHashOperations<String, Object, Object> cartOps = getCartOps();
    cartOps.delete(skuId.toString());
}

Guess you like

Origin blog.csdn.net/qq_40991313/article/details/129878528