购物车模块实现(类比京东和天猫)

购物车

购物车分为用户登录购物车和未登录购物车操作,国内知名电商京东用户登录和不登录都可以操作购物车,如果用户不登录,操作购物车可以将数据存储到Cookie,用户登录后购物车数据可以存储到Redis中,再将之前未登录加入的购物车合并到Redis中即可。

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

一、购物车业务逻辑

(1)需求分析

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

(2)购物车实现思路

登录情况(天猫)

用户登录,记录都保存在服务端redis,可以进行一些数据分析和广告推送什么的,下次用户登录的时候。

cookie 保存格式
id orderItem
id orderItem
redis 保存格式
hash field value
用户id 商品id orderItem

不登录情况(京东)

用户不登录,操作购物车可以将数据存储到Cookie中,用户登录后把购物车数据存储到Redis中。用户在未登录的情况下有已添加购物车商品,在登录后需cookie和redis做个合并,最终保存在redis中,并清理对应的cookie

(3)表结构分析

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

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;

二、实现思路和代码实现

先定义一个类 统一cookie和redis的名字

public class Contatns {
    public static final String CART_COOKIE_NAME="cart";
    public static final String CART_REDIS_NAME="cart_";
}

总的接口信息 不登录和登录都有了


public interface CartService {

    //添加购物车
    void addCart(String skuId,Integer num,String username);

    //查询购物车数据
    Map list(String username);
    //
    Map<String,OrderItem> addCart2(String skuId,Integer num,Map<String,OrderItem> oldCart);
    //把购物车保存到redis
    void saveCartToRedis(String username, Map<String, OrderItem> newCartMap);
    //从购物车拿到redis
    Map<String,OrderItem> getCartFromRedis(String username);
    //合并cookie和redis
    Map<String,OrderItem> merge(Map<String, OrderItem> cookieCartMap, Map<String, OrderItem> redisCartMap);
}

分为两块,添加购物车和查询购物车

天猫登录情况下实现

天猫是基于登录的情况下去实现购物车的添加,所以直接保存在redis中

添加
 @Override
    public void addCart(String skuId, Integer num, String username) {
        //1.查询redis中相对应的商品信息
        OrderItem orderItem = (OrderItem) redisTemplate.boundHashOps(CART+username).get(skuId);
        if (orderItem != null){
            //2.如果当前商品在redis中的存在,则更新商品的数量与价钱
            orderItem.setNum(orderItem.getNum()+num);
            //也有可能是减的情况,小于0要删除不然会出负数
            if (orderItem.getNum()<=0){
                //删除该商品
                redisTemplate.boundHashOps(CART+username).delete(skuId);
                return;
            }
            orderItem.setMoney(orderItem.getNum()*orderItem.getPrice());
            orderItem.setPayMoney(orderItem.getNum()*orderItem.getPrice());
        }else {
            //3.如果当前商品在redis中不存在,将商品添加到redis中
            //需要注入喔
            Sku sku = skuFeign.findById(skuId).getData();
            Spu spu = spuFeign.findSpuById(sku.getSpuId()).getData();

            //封装orderItem
            orderItem = this.sku2OrderItem(sku,spu,num);
        }

        //3.将orderitem添加到redis中
        redisTemplate.boundHashOps(CART+username).put(skuId,orderItem);
    }

sku2OrderItem抽取出来使用

 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(orderItem.getPrice()*num);
        orderItem.setPayMoney(orderItem.getPrice()*num);
        orderItem.setImage(sku.getImage());
        orderItem.setWeight(sku.getWeight()*num);
        //分类信息
        orderItem.setCategoryId1(spu.getCategory1Id());
        orderItem.setCategoryId2(spu.getCategory2Id());
        orderItem.setCategoryId3(spu.getCategory3Id());
        return orderItem;
    }
查询
//查询购物车列表数据
    @Override
    public Map list(String username) {
        Map map = new HashMap();

        List<OrderItem> orderItemList = redisTemplate.boundHashOps(CART + username).values();
        map.put("orderItemList",orderItemList);

        //商品的总数量与总价格
        Integer totalNum = 0;
        Integer totalMoney = 0;

        for (OrderItem orderItem : orderItemList) {
            totalNum+=orderItem.getNum();
            totalMoney+=orderItem.getMoney();
        }

        map.put("totalNum",totalNum);
        map.put("totalMoney",totalMoney);

        return map;
    }

京东不登录情况下实现

因为没有登录就可以实现购物车的添加,所以先保存在cookie中,登录后再把cookie和redis数据进行合并,并清除对应的cookie

添加购物车
    /**
     * 添加购物车,不关系购物车存储位置
     * @param skuId
     * @param num
     * @param oldCartMap 老购物车
     * @return 新购物车
     */
    @Override
    public Map<String, OrderItem> addCart2(String skuId, Integer num, Map<String, OrderItem> oldCartMap) {
        //购物车是否存在 现在购物项 根据商品名去找
        OrderItem orderItem = oldCartMap.get(skuId);
        if(orderItem!=null){//存在,数量,金额
            orderItem.setNum(orderItem.getNum()+num);
            if(orderItem.getNum()<=0){//数量小于等于0,删除此购物项,从购物车中
                oldCartMap.remove(skuId);
            }
            orderItem.setMoney(orderItem.getNum()*orderItem.getPrice());
            orderItem.setPayMoney(orderItem.getNum()*orderItem.getPrice());//p补充:money-优惠金额=payMoney
        }else{新购物项添加到购物车
            //3.如果当前商品在redis中不存在,将商品添加到redis中
            Sku sku = skuFeign.findById(skuId).getData();
            Spu spu = spuFeign.findSpuById(sku.getSpuId()).getData();

            //封装orderItem:新的购物项,
            orderItem = this.sku2OrderItem(sku,spu,num);
            //放到购物车
            oldCartMap.put(skuId,orderItem);
        }
        return oldCartMap;
    }
查询

当点击查询,需要做合并了

  /**
     * 合并购物车
     * @param oldCart
     * @param newCart
     * @return
     */
    @Override
    public Map<String, OrderItem> merge(Map<String, OrderItem> oldCart, Map<String, OrderItem> newCart) {
        for (String key : newCart.keySet()) {
            OrderItem newOrderItem = newCart.get("key");//新的购物项
            //判断老的购物车是否存在 新的购物项,商品
            OrderItem oldOrderItem = oldCart.get(newOrderItem.getSkuId());
            if(oldOrderItem!=null){//如果存在 ,数量改变,价格,

                oldOrderItem.setNum(oldOrderItem.getNum()+newOrderItem.getNum());//改变数量

                oldOrderItem.setMoney(oldOrderItem.getNum()*oldOrderItem.getPrice());//改变小计

                oldOrderItem.setPayMoney(oldOrderItem.getNum()*oldOrderItem.getPrice());//改变小计

            }else{//如果不存在,直接追加
              newCart.put(newOrderItem.getId(),newOrderItem);
            }
        }
        return oldCart;//返回合并后的购物车,如果newCart不存在,直接返回oldCart,如果newCart存在,正常进循环,向oldCart中添加 ,  返回合并后的oldCart
    }

总的ServiceImpl

@Service
public class CartServiceImpl implements CartService {

    private static final String CART="cart_";

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private SkuFeign skuFeign;

    @Autowired
    private SpuFeign spuFeign;


    @Override
    public void addCart(String skuId, Integer num, String username) {
        //1.查询redis中相对应的商品信息
        OrderItem orderItem = (OrderItem) redisTemplate.boundHashOps(CART+username).get(skuId);
        if (orderItem != null){
            //2.如果当前商品在redis中的存在,则更新商品的数量与价钱
            orderItem.setNum(orderItem.getNum()+num);
            //也有可能是减的情况,小于0要删除不然会出负数
            if (orderItem.getNum()<=0){
                //删除该商品
                redisTemplate.boundHashOps(CART+username).delete(skuId);
                return;
            }
            orderItem.setMoney(orderItem.getNum()*orderItem.getPrice());
            orderItem.setPayMoney(orderItem.getNum()*orderItem.getPrice());
        }else {
            //3.如果当前商品在redis中不存在,将商品添加到redis中
            Sku sku = skuFeign.findById(skuId).getData();
            Spu spu = spuFeign.findSpuById(sku.getSpuId()).getData();

            //封装orderItem
            orderItem = this.sku2OrderItem(sku,spu,num);
        }

        //3.将orderitem添加到redis中
        redisTemplate.boundHashOps(CART+username).put(skuId,orderItem);
    }

    //查询购物车列表数据
    @Override
    public Map list(String username) {
        Map map = new HashMap();

        List<OrderItem> orderItemList = redisTemplate.boundHashOps(CART + username).values();
        map.put("orderItemList",orderItemList);

        //商品的总数量与总价格
        Integer totalNum = 0;
        Integer totalMoney = 0;

        for (OrderItem orderItem : orderItemList) {
            totalNum+=orderItem.getNum();
            totalMoney+=orderItem.getMoney();
        }

        map.put("totalNum",totalNum);
        map.put("totalMoney",totalMoney);

        return map;
    }




    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(orderItem.getPrice()*num);
        orderItem.setPayMoney(orderItem.getPrice()*num);
        orderItem.setImage(sku.getImage());
        orderItem.setWeight(sku.getWeight()*num);
        //分类信息
        orderItem.setCategoryId1(spu.getCategory1Id());
        orderItem.setCategoryId2(spu.getCategory2Id());
        orderItem.setCategoryId3(spu.getCategory3Id());
        return orderItem;
    }

    /**
     * 添加购物车,不关系购物车存储位置
     * @param skuId
     * @param num
     * @param oldCartMap 老购物车
     * @return 新购物车
     */
    @Override
    public Map<String, OrderItem> addCart2(String skuId, Integer num, Map<String, OrderItem> oldCartMap) {
        //购物车是否存在 现在购物项 根据商品名去找
        OrderItem orderItem = oldCartMap.get(skuId);
        if(orderItem!=null){//存在,数量,金额
            orderItem.setNum(orderItem.getNum()+num);
            if(orderItem.getNum()<=0){//数量小于等于0,删除此购物项,从购物车中
                oldCartMap.remove(skuId);
            }
            orderItem.setMoney(orderItem.getNum()*orderItem.getPrice());
            orderItem.setPayMoney(orderItem.getNum()*orderItem.getPrice());//p补充:money-优惠金额=payMoney
        }else{新购物项添加到购物车
            //3.如果当前商品在redis中不存在,将商品添加到redis中
            Sku sku = skuFeign.findById(skuId).getData();
            Spu spu = spuFeign.findSpuById(sku.getSpuId()).getData();

            //封装orderItem:新的购物项,
            orderItem = this.sku2OrderItem(sku,spu,num);
            //放到购物车
            oldCartMap.put(skuId,orderItem);
        }
        return oldCartMap;
    }

    /**
     * 把购物车存储到redis中
     * @param username
     * @param newCartMap
     */
    @Override
    public void saveCartToRedis(String username, Map<String, OrderItem> newCartMap) {
        redisTemplate.boundHashOps(Contatns.CART_REDIS_NAME+username).putAll(newCartMap);
    }

    /**
     * 根据用户名 从redis中获取购物车
     * @param username
     * @return
     */
    @Override
    public Map<String, OrderItem> getCartFromRedis(String username) {
        Map<String, OrderItem>  cart = redisTemplate.boundHashOps(Contatns.CART_REDIS_NAME + username).entries();
        if(cart==null || cart.size()<=0){
            cart=new HashMap<>();
        }
        return cart;
    }

    /**
     * 合并购物车
     * @param oldCart
     * @param newCart
     * @return
     */
    @Override
    public Map<String, OrderItem> merge(Map<String, OrderItem> oldCart, Map<String, OrderItem> newCart) {
        for (String key : newCart.keySet()) {
            OrderItem newOrderItem = newCart.get("key");//新的购物项
            //判断老的购物车是否存在 新的购物项,商品
            OrderItem oldOrderItem = oldCart.get(newOrderItem.getSkuId());
            if(oldOrderItem!=null){//如果存在 ,数量改变,价格,

                oldOrderItem.setNum(oldOrderItem.getNum()+newOrderItem.getNum());//改变数量

                oldOrderItem.setMoney(oldOrderItem.getNum()*oldOrderItem.getPrice());//改变小计

                oldOrderItem.setPayMoney(oldOrderItem.getNum()*oldOrderItem.getPrice());//改变小计

            }else{//如果不存在,直接追加
              newCart.put(newOrderItem.getId(),newOrderItem);
            }
        }
        return oldCart;//返回合并后的购物车,如果newCart不存在,直接返回oldCart,如果newCart存在,正常进循环,向oldCart中添加 ,  返回合并后的oldCart
    }
}

接下来是controller层了

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

    @Autowired
    private CartService cartService;
//
    @Autowired
    private TokenDecode tokenDecode;

    @GetMapping("/addCart")
    public Result addCart(@RequestParam("skuId") String skuId, @RequestParam("num") Integer num){

        //动态获取当前人信息,暂时静态
//        String username = "itcast";
        String username = tokenDecode.getUserInfo().get("username");
        cartService.addCart(skuId,num,username);
        return new Result(true, StatusCode.OK,"加入购物车成功");
    }

    @GetMapping("/list")
    public Map list(){
        //动态获取当前人信息,暂时静态
//        String username = "itcast";
        String username = tokenDecode.getUserInfo().get("username");
        Map map = cartService.list(username);
        return map;
    }

    /**
     * 添加购物车
     * @param skuId
     * @param num
     * @return
     */
    @GetMapping("addCart2")
    public Result addCart2(@RequestParam("skuId") String skuId, @RequestParam("num") Integer num,
        HttpServletRequest request,HttpServletResponse response){
        String username = tokenDecode.getUserInfo().get("username");
        //获取购物车
        Map<String, OrderItem> oldCartMap = this.getCart(request,response);
        //添加购物车业务
        Map<String, OrderItem> newCartMap = cartService.addCart2(skuId, num, oldCartMap);
        if("anonymousUser".equals(username)){//未登录,存cookie
            //新购物车转转json,
            String newCartJson = JSON.toJSONString(newCartMap);
            //写到cookie中
            CookieUtil.setCookie(request,response, Contatns.CART_COOKIE_NAME,newCartJson,60*60*24*7,"utf-8");
        }else{//登录,存redis中
            cartService.saveCartToRedis(username,newCartMap);
        }
        return  new Result(true, StatusCode.OK,"加入购物车成功",newCartMap);
    }

    /**
     * 获取老的购物车
     * @return
     */
    private  Map<String, OrderItem> getCart(HttpServletRequest request,HttpServletResponse response){
        String username = tokenDecode.getUserInfo().get("username");
        //从cookie获取购物车
        String cartValue = CookieUtil.getCookieValue(request, Contatns.CART_COOKIE_NAME, "utf-8");
        if(StringUtils.isEmpty(cartValue)){
            cartValue="{}";
        }
        //转换为map结构  这里因为value是自定义对象map.class会转化异常
        Map<String, OrderItem>  cookieCartMap = JSON.parseObject(cartValue, new TypeReference<Map<String,OrderItem>>(){});
         //判断是否登录,登录不成功
        if("anonymousUser".equals(username)){
            System.out.println("从cookie中获取购物车,并返回");
            return cookieCartMap;
        }else{//登录从redis中获取
            Map<String, OrderItem>  redisCartMap=cartService.getCartFromRedis(username);
            if(cookieCartMap!=null && cookieCartMap.size()>0){
                //合并购物车
                redisCartMap= cartService.merge(cookieCartMap,redisCartMap);
                //合并后的购物车存到redis中
                cartService.saveCartToRedis(username,redisCartMap);
                //清理cookie中购物车
                CookieUtil.deleteCookie(request,response,Contatns.CART_COOKIE_NAME);
            }
            return redisCartMap;
        }
    }

    /**
     * 查询购物车
     * @param request
     * @return
     */
    @GetMapping("list2")
    public Result list2(HttpServletRequest request,HttpServletResponse response){
        Map<String, OrderItem> cart = this.getCart(request,response);
        return new Result(true, StatusCode.OK,"查询购物车成功",cart);
    }
}

这里的anonymousUser是token鉴权的时候,如果类型是游客,就会放username和anonymousUser放进去

public class TokenDecode {
    //公钥
    private static final String PUBLIC_KEY = "public.key";

    private static String publickey="";

    /***
     * 获取用户信息
     * @return
     */
    public Map<String,String> getUserInfo(){
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if(authentication instanceof AnonymousAuthenticationToken){

            Object principal = authentication.getPrincipal();
            Map<String,String> map=new HashMap<>();
            map.put("username",principal.toString());
            return map;
        }
        //获取授权信息
        OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) SecurityContextHolder.getContext().getAuthentication().getDetails();
        //令牌解码
        return dcodeToken(details.getTokenValue());
    }

    /***
     * 读取令牌数据
     */
    public Map<String,String> dcodeToken(String token){
        //校验Jwt
        Jwt jwt = JwtHelper.decodeAndVerify(token, new RsaVerifier(getPubKey()));

        //获取Jwt原始内容
        String claims = jwt.getClaims();
        return JSON.parseObject(claims,Map.class);
    }


    /**
     * 获取非对称加密公钥 Key
     * @return 公钥 Key
     */
    public String getPubKey() {
        if(!StringUtils.isEmpty(publickey)){
            return publickey;
        }
        Resource resource = new ClassPathResource(PUBLIC_KEY);
        try {
            InputStreamReader inputStreamReader = new InputStreamReader(resource.getInputStream());
            BufferedReader br = new BufferedReader(inputStreamReader);
            publickey = br.lines().collect(Collectors.joining("\n"));
            return publickey;
        } catch (IOException ioe) {
            return null;
        }
    }
}

网关这一块记得放行,在昨天的代码中AuthFilter需要放行这一块的内容

@Component
public class AuthFilter implements GlobalFilter, Ordered {

    private static final String LOGIN_URL="http://localhost:8001/api/oauth/toLogin";

    @Autowired
    private AuthService authService;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();

        //1.判断当前请求路径是否为登录请求,如果是,则直接放行
        String path = request.getURI().getPath();
        if ("/api/oauth/login".equals(path) || !UrlFilter.hasAuthorize(path) ){
            //直接放行
            return chain.filter(exchange);
        }else if("/api/cart/list2".equals(path) || "/api/cart/addCart2".equals(path)){
            //2.从cookie中获取jti的值,如果该值不存在,拒绝本次访问
            String jti = authService.getJtiFromCookie(request);
            if(StringUtils.isNotEmpty(jti)){
                //3.从redis中获取jwt的值,如果该值不存在,拒绝本次访问
                String jwt = authService.getJwtFromRedis(jti);
                if(StringUtils.isNotEmpty(jwt)){
                    //4.对当前的请求对象进行增强,让它会携带令牌的信息
                    request.mutate().header("Authorization","Bearer "+jwt);
                }
            }
            return chain.filter(exchange);//不登录放行
        }

        //2.从cookie中获取jti的值,如果该值不存在,拒绝本次访问
        String jti = authService.getJtiFromCookie(request);
        if (StringUtils.isEmpty(jti)){
            //拒绝访问
            /*response.setStatusCode(HttpStatus.UNAUTHORIZED);
            return response.setComplete();*/
            //跳转登录页面
            return this.toLoginPage(LOGIN_URL+"?FROM="+request.getURI().getPath(),exchange);
        }

        //3.从redis中获取jwt的值,如果该值不存在,拒绝本次访问
        String jwt = authService.getJwtFromRedis(jti);
        if (StringUtils.isEmpty(jwt)){
            //拒绝访问
            /*response.setStatusCode(HttpStatus.UNAUTHORIZED);
            return response.setComplete();*/
            return  this.toLoginPage(LOGIN_URL,exchange);
        }

        //4.对当前的请求对象进行增强,让它会携带令牌的信息
        request.mutate().header("Authorization","Bearer "+jwt);
        return chain.filter(exchange);
    }

    //跳转登录页面
    private Mono<Void> toLoginPage(String loginUrl, ServerWebExchange exchange) {
        ServerHttpResponse response = exchange.getResponse();
        response.setStatusCode(HttpStatus.SEE_OTHER);
        response.getHeaders().set("Location",loginUrl);
        return response.setComplete();
    }

    @Override
    public int getOrder() {
        return 0;
    }
}

三、购物车渲染

3.1 购物车渲染服务搭建

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>

application.yml配置

server:
  port: 9011
spring:
  application:
    name: order-web
  main:
    allow-bean-definition-overriding: true   #当遇到同样名字的时候,是否允许覆盖注册
  thymeleaf:
    cache: false
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:6868/eureka
  instance:
    prefer-ip-address: true
feign:
  hystrix:
    enabled: true
  client:
    config:
      default:   #配置全局的feign的调用超时时间  如果 有指定的服务配置 默认的配置不会生效
        connectTimeout: 60000 # 指定的是 消费者 连接服务提供者的连接超时时间 是否能连接  单位是毫秒
        readTimeout: 80000  # 指定的是调用服务提供者的 服务 的超时时间()  单位是毫秒
#hystrix 配置
hystrix:
  command:
    default:
      execution:
        timeout:
          #如果enabled设置为false,则请求超时交给ribbon控制
          enabled: true
        isolation:
          strategy: SEMAPHORE
          thread:
            # 熔断器超时时间,默认:1000/毫秒
            timeoutInMilliseconds: 80000
#请求处理的超时时间
ribbon:
  ReadTimeout: 4000
  #请求连接的超时时间
  ConnectTimeout: 3000

启动类

@SpringBootApplication
@EnableEurekaClient
public class OrderWebApplication {

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

3.2 购物车列表渲染

3.2.1 Feign创建

在changgou_service_order_api中添加CartFeign接口,并在接口中创建添加购物车和查询购物车列表,代码如下:

@FeignClient(name="order")
public interface CartFeign {

    /**
     * 添加购物车
     * @param skuId
     * @param num
     * @return
     */
    @GetMapping("/cart/add")
    public Result add(@RequestParam("skuId") String skuId, @RequestParam("num") Integer num);

    /***
     * 查询用户购物车列表
     * @return
     */
    @GetMapping(value = "/cart/list")
    public Map list();
}
3.2.2 后台代码

在changgou_web_order中创建com.changgou.order.controller.CartController,并添加查询购物车集合方法和添加购物车方法,代码如下:

@Controller
@RequestMapping("/wcart")
public class CartController {

    @Autowired
    private CartFeign cartFeign;

    /**
     * 查询
     */
    @GetMapping("/list")
    public String list(Model model){
        Map map = cartFeign.list();
        model.addAttribute("items",map);
        return "cart";
    }

    /**
     * 添加购物车
     */
    @GetMapping("/add")
    @ResponseBody
    public Result<Map> add(String id, Integer num){

        cartFeign.add(id, num);

        Map map = cartFeign.list();
        return new Result<>(true, StatusCode.OK,"添加购物车成功",map);
    }
}
3.2.3 前端页面

(1)加载列表数据

我们可以先在网页下面添加一个js脚本,用于加载购物车数据,并用一个对象存储,代码如下:

<!-- vue loadlist -->
						<div class="cart-list" v-for="item in items.orderItemList" :key="item.index">
							<ul class="goods-list yui3-g">
								<li class="yui3-u-1-24">
									<input type="checkbox" name="chk_list" id="" value="" />
								</li>
								<li class="yui3-u-6-24">
									<div class="good-item">
										<div class="item-img">
											<img :src="item.image" />
										</div>
										<div class="item-msg"></div>
									</div>
								</li>
								<li class="yui3-u-5-24">
									<div class="item-txt">{{item.name}}</div>
								</li>
								<li class="yui3-u-1-8">
									<span class="price">{{item.price}}</span>
								</li>
								<li class="yui3-u-1-8">
									<a href="javascript:void(0)" @click="add(item.skuId,-1)" class="increment mins">-</a>
									<input autocomplete="off" type="text" v-model="item.num" @blur="add(item.skuId,item.num)" value="1" minnum="1" class="itxt" />
									<a href="javascript:void(0)" @click="add(item.skuId,1)" class="increment plus">+</a>
								</li>
								<li class="yui3-u-1-8">
									<span class="sum">{{item.num*item.price}}</span>
								</li>
								<li class="yui3-u-1-8">
									<a href="#none">删除</a>
									<br />
									<a href="#none">移到收藏</a>
								</li>
							</ul>
						</div>

					</div>
				</div>
			</div>
			<div class="cart-tool">
				<div class="select-all">
					<input class="chooseAll" type="checkbox" name="" id="" value="" />
					<span>全选</span>
				</div>
				<div class="option">
					<a href="#none">删除选中的商品</a>
					<a href="#none">移到我的关注</a>
					<a href="#none">清除下柜商品</a>
				</div>
				<div class="money-box">
					<div class="chosed">已选择
						<span>{{items.totalNum}}</span>件商品</div>
					<div class="sumprice">
						<span>
							<em>总价(不含运费) :</em>
							<i class="summoney">¥{{items.totalMoney}}</i>
						</span>
						<span>
							<em>已节省:</em>
							<i>-¥20.00</i>
						</span>
					</div>
					<div class="sumbtn">
						<a class="sum-btn" href="getOrderInfo.html" target="_blank">结算</a>
					</div>
				</div>
			</div>
<script th:inline="javascript">
		var app = new Vue({
			el: '#app',
			data() {
				return {
					items: [[${items}]]
				}
			},
			methods:{
				add:function (skuId, num) {
					axios.get("/wcart/add?skuId="+skuId+"&num="+num).then(function (response) {
						if (response.data.flag){
							app.items=response.data.data
						}
					})
				}
			}
		})
	</script>
3.2.4 购物车渲染服务、订单服务对接网关
  1. 修改微服务网关changgou-gateway-web的application.yml配置文件,添加order的路由过滤配置,配置如下:
            #订单微服务
            - id: changgou_order_route
              uri: lb://order
              predicates:
              - Path=/api/cart/**,/api/categoryReport/**,/api/orderConfig/**,/api/order/**,/api/orderItem/**,/api/orderLog/**,/api/preferential/**,/api/returnCause/**,/api/returnOrder/**,/api/returnOrderItem/**
              filters:
              - StripPrefix=1
            #购物车订单渲染微服务
            - id: changgou_order_web_route
              uri: lb://order-web
              predicates:
              - Path=/api/wcart/**,/api/worder/**
              filters:
              - StripPrefix=1

3.3 商品数量变更

​ 用户可以点击+号或者-号,或者手动输入一个数字,然后更新购物车列表,我们可以给-+号一个点击事件,给数字框一个失去焦点事件,然后调用后台,实现购物车的更新。

请求后台方法:

在js里面创建一个请求后台的方法,代码如下:

添加事件:

在±号和数字框那里添加点击事件和失去焦点事件,然后调用上面的add方法,代码如下:

3.4微服务间认证


如上图:因为微服务之间并没有传递头文件,所以我们可以定义一个拦截器,每次微服务调用之前都先检查下头文件,将请求的头文件中的令牌数据再放入到header中,再调用其他微服务即可。

3.4.1feign拦截器实现微服务间认证

(1)创建拦截器

在changgou_common服务中创建一个com.changgou.interceptor.FeignInterceptor拦截器,并将所有头文件数据再次加入到Feign请求的微服务头文件中,代码如下:

@Component
public class FeignInterceptor implements RequestInterceptor {

    @Override
    public void apply(RequestTemplate requestTemplate) {

        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();

        if (requestAttributes!=null){

            HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
            if (request!=null){
                Enumeration<String> headerNames = request.getHeaderNames();
                if (headerNames!=null){
                    while (headerNames.hasMoreElements()){
                        String headerName = headerNames.nextElement();
                        if (headerName.equals("authorization")){
                            String headerValue = request.getHeader(headerName);
                            requestTemplate.header(headerName,headerValue);
                        }
                    }
                }
            }
        }
        }
}
  1. 更改changgou_order_web启动类,添加拦截器声明
@Bean
public FeignInterceptor feignInterceptor(){
    return new FeignInterceptor();
}

3配置包扫描

@EnableFeignClients(basePackages = {"com.changgou.user.feign"})

3.5登录页面跳转

直接访问购物车,先进入网关,网关一看还没登录就定向到Oauth服务去登录,登录的时候拼接一下url,比如在url加入key value 这样就能携带过去了,前端通过指定的key去实现登录成功后要跳转的页面

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值