商城-购物车

购物车

购物车分为用户登录购物车和未登录购物车操作,国内知名电商京东用户登录和不登录都可以操作

购物车,如果用户不登录,操作购物车可以将数据存储到Cookie或者

WebSQL或者SessionStorage中,用户登录后购物车数据可以存储到Redis中,再将之前未登录加

入的购物车合并到Redis中即可。

淘宝天猫则采用了另外一种实现方案,用户要想将商品加入购物车,必须先登录才能操作购物车。

我们今天实现的购物车是天猫解决方案,即用户必须先登录才能使用购物车功能。

购物车分析

(1)需求分析

用户在商品详细页点击加入购物车,提交商品SKU编号和购买数量,添加到购物车。购物车展示页

面如下:

(2)购物车实现思路

我们实现的是用户登录后的购物车,用户将商品加入购物车的时候,直接将要加入购物车的详情存

入到Redis即可。每次查看购物车的时候直接从Redis中获取。

(3)表结构分析

用户登录后将商品加入购物车,需要存储商品详情以及购买数量,购物车详情表如下:

changgou_order数据中tb_order_item表:

CREATE TABLE `tb_order_item` (
  `id` varchar(20) COLLATE utf8_bin NOT NULL COMMENT 'ID',
  `category_id1` int(11) DEFAULT NULL COMMENT '1级分类',
  `category_id2` int(11) DEFAULT NULL COMMENT '2级分类',
  `category_id3` int(11) DEFAULT NULL COMMENT '3级分类',
  `spu_id` varchar(20) COLLATE utf8_bin DEFAULT NULL COMMENT 'SPU_ID',
  `sku_id` bigint(20) NOT NULL COMMENT 'SKU_ID',
  `order_id` bigint(20) NOT NULL COMMENT '订单ID',
  `name` varchar(200) COLLATE utf8_bin DEFAULT NULL COMMENT '商品名称',
  `price` int(20) DEFAULT NULL COMMENT '单价',
  `num` int(10) DEFAULT NULL COMMENT '数量',
  `money` int(20) DEFAULT NULL COMMENT '总金额',
  `pay_money` int(11) DEFAULT NULL COMMENT '实付金额',
  `image` varchar(200) COLLATE utf8_bin DEFAULT NULL COMMENT '图片地址',
  `weight` int(11) DEFAULT NULL COMMENT '重量',
  `post_fee` int(11) DEFAULT NULL COMMENT '运费',
  `is_return` char(1) COLLATE utf8_bin DEFAULT NULL COMMENT '是否退货',
  PRIMARY KEY (`id`),
  KEY `item_id` (`sku_id`),
  KEY `order_id` (`order_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;

购物车详情表其实就是订单详情表结构,只是目前临时存储数据到Redis,等用户下单后才将数据

从Redis取出存入到MySQL中。

借用它的实体bean,不能用它的表,数据往redis中存,存Hash类型。

订单购物车微服务

我们先搭建一个订单购物车微服务工程,按照如下步骤实现即可。

(1)搭建订单购物车微服务changgou-service-order

依赖引入:

<dependencies>
        <dependency>
            <groupId>com.changgou</groupId>
            <artifactId>changgou-service-order-api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
</dependencies>

后面查询商品会用到goods,这里先补上依赖

API工程:

(2)application.yml配置

在changgou-service-order的resources中添加application.yml配置文件,代码如下:

server:
  port: 18088
spring:
  application:
    name: order
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://192.168.2.132:3306/changgou_order?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
    username: root
    password: 123456
  redis:
    host: 192.168.2.132
    port: 6379
  main:
    allow-bean-definition-overriding: true

eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:7001/eureka
  instance:
    prefer-ip-address: true
feign:
  hystrix:
    enabled: true

(3)创建启动类

package com.changgou;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import tk.mybatis.spring.annotation.MapperScan;

@SpringBootApplication
@EnableEurekaClient
@MapperScan(basePackages = {"com.changgou.order.dao"})
public class OrderApplication {

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

代码生成器生成pojo,拷贝到api工程中来

其他controller、service、dao拷贝到订单购物车微服务中(出现红叉,手动导入Date包即可,

import java.util.Date;)

添加购物车

用户添加购物车,只需要将要加入购物车的商品存入到Redis中即可。一个用户可以将多件商品加

入购物车,存储到Redis中的数据可以采用Hash类型。

选Hash类型可以将用户的用户名作为namespace的一部分,将指定商品加入购物车,则往对应的

namespace中增加一个key和value,

key是商品ID,value是加入购物车的商品详情,如下图:

代码实现

feign创建,下订单需要调用feign查看商品信息

新建一个SpuFeign

在changgou-service-order微服务中创建com.changgou.order.controller.CartController,

在changgou-service-order微服务中创建com.changgou.order.service.CartService接口,

在changgou-service-order微服务中创建接口实现类com.changgou.order.service.impl.CartServiceImpl,

代码如下:

package com.changgou.order.controller;

import com.changgou.order.service.CartService;
import entity.Result;
import entity.StatusCode;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@CrossOrigin
@RequestMapping(value = "/cart")
public class CartController {

    @Autowired
    private CartService cartService;

    /***
     * 加入购物车
     * @param num:购买的数量
     * @param id:购买的商品(SKU)ID
     * @return
     */
    @RequestMapping(value = "/add")
    public Result add(Integer num, Long id){
        //用户名
        String username="szitheima";
        //将商品加入购物车
        cartService.add(num,id,username);
        return new Result(true, StatusCode.OK,"加入购物车成功!");
    }
}




package com.changgou.order.service;

public interface CartService {
    /***
     * 添加购物车
     * @param num:购买商品数量
     * @param id:购买ID
     * @param username:购买用户
     * @return
     */
    void add(Integer num, Long id, String username);
}



package com.changgou.order.service.impl;

import com.changgou.goods.feign.SkuFeign;
import com.changgou.goods.feign.SpuFeign;
import com.changgou.goods.pojo.Sku;
import com.changgou.goods.pojo.Spu;
import com.changgou.order.pojo.OrderItem;
import com.changgou.order.service.CartService;
import entity.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

@Service
public class CartServiceImpl implements CartService {

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private SkuFeign skuFeign;

    @Autowired
    private SpuFeign spuFeign;


    @Override
    public void add(Integer num, Long id, String username) {
        //查询SKU
        Result<Sku> resultSku = skuFeign.findById(id);
        if(resultSku!=null && resultSku.isFlag()){
            //获取SKU
            Sku sku = resultSku.getData();
            //获取SPU
            Result<Spu> resultSpu = spuFeign.findById(sku.getSpuId());

            //将SKU转换成OrderItem
            OrderItem orderItem = sku2OrderItem(sku,resultSpu.getData(), num);

            /******
             * 购物车数据存入到Redis
             * namespace = Cart_[username]
             * key=id(sku)
             * value=OrderItem
             */
            redisTemplate.boundHashOps("Cart_"+username).put(id,orderItem);
        }
    }


    /***
     * SKU转成OrderItem
     * @param sku
     * @param num
     * @return
     */
    private OrderItem sku2OrderItem(Sku sku,Spu spu,Integer num){
        OrderItem orderItem = new OrderItem();
        orderItem.setSpuId(sku.getSpuId());
        orderItem.setSkuId(sku.getId());
        orderItem.setName(sku.getName());
        orderItem.setPrice(sku.getPrice());
        orderItem.setNum(num);
        orderItem.setMoney(num*orderItem.getPrice());       //单价*数量
        orderItem.setPayMoney(num*orderItem.getPrice());    //实付金额
        orderItem.setImage(sku.getImage());
        orderItem.setWeight(sku.getWeight()*num);           //重量=单个重量*数量

        //分类ID设置
        orderItem.setCategoryId1(spu.getCategory1Id());
        orderItem.setCategoryId2(spu.getCategory2Id());
        orderItem.setCategoryId3(spu.getCategory3Id());
        return orderItem;
    }
}

测试:启动注册中心,goods微服务,订单微服务

启动报错:

注意@Service注解不要少了,不然会报错:

启动报错:

排查了一顿之后,发现没有@EnableFeignClients注解

加上即可:

package com.changgou;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import tk.mybatis.spring.annotation.MapperScan;

@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients(basePackages = {"com.changgou.goods.feign"})
@MapperScan(basePackages = {"com.changgou.order.dao"})
public class OrderApplication {

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

注册中心启动成功: 

http://localhost:18088/cart/add?num=6&id=1148477873175142400

查看redis缓存中的数据,已经有数据了,成功


购物车列表

因为存的时候是根据用户名往Redis中存储用户的购物车数据的,所以我们这里可以将用户的名字

作为key去Redis中查询对应的数据。

代码实现

控制层

修改changgou-service-order微服务的com.changgou.order.controller.CartController类,添加购物

车列表查询方法,代码如下:

/***
 * 查询用户购物车列表
 * @return
 */
@GetMapping(value = "/list")
public Result list(){
    //用户名
    String username="szitheima";
    List<OrderItem> orderItems = cartService.list(username);
    return new Result(true,StatusCode.OK,"购物车列表查询成功!",orderItems);
}

业务层接口

修改changgou-service-order微服务的com.changgou.order.service.CartService接口,添加购物车

列表方法,代码如下:

/***
 * 查询用户的购物车数据
 * @param username
 * @return
 */
List<OrderItem> list(String username);

业务层接口实现类

修改changgou-service-order微服务的com.changgou.order.service.impl.CartServiceImpl类,添加

购物车列表实现方法,代码如下:

/***
 * 查询用户购物车数据
 * @param username
 * @return
 */
@Override
public List<OrderItem> list(String username) {
    //查询所有购物车数据
    List<OrderItem> orderItems = redisTemplate.boundHashOps("Cart_"+username).values();
    return orderItems;
}

测试:

http://localhost:18090/cart/list


问题处理

我们发现个问题,就是用户将商品加入购物车,无论数量是正负,都会执行添加购物车,如果数量如果<=0,应该移除该商品的。

(1)删除商品购物车

修改changgou-service-order的com.changgou.order.service.impl.CartServiceImpl的add方法,添加如下代码:

(2)数据精度丢失问题

SkuId是Long类型,在页面输出的时候会存在精度丢失问题,我们只需要在OrderItem的SkuId上加上字符串序列化类型就可以了,

代码如下:

Springboot自定义序列化器@JsonSerialize 格式化后端数据,使用 @JsonSerialize(using = 自定义序列化器类.class)

去序列化指定的属性。例如:

Guess you like

Origin blog.csdn.net/ZHOU_VIP/article/details/118498691