谷粒商城--商品上架--高级篇笔记二

谷粒商城–商品上架–高级篇笔记二

1. 新增商品索引

PUT product
{
  "mappings": {
    "properties": {
      "skuId": {
        "type": "long"
      },
      "spuId": {
        "type": "keyword"
      },
      "skuTitle": {
        "type": "text",
        "analyzer": "ik_smart"
      },
      "skuPrice": {
        "type": "keyword"
      },
      "skuImg": {
        "type": "keyword",
        "index": false,
        "doc_values": false
      },
      "saleCount": {
        "type": "long"
      },
      "hasStock": {
        "type": "boolean"
      },
      "hotScore": {
        "type": "long"
      },
      "brandId": {
        "type": "long"
      },
      "catalogId": {
        "type": "long"
      },
      "brandName": {
        "type": "keyword",
        "index": false,
        "doc_values": false
      },
      "brandImg": {
        "type": "keyword",
        "index": false,
        "doc_values": false
      },
      "catalogName": {
        "type": "keyword",
        "index": false,
        "doc_values": false
      },
      "attrs": {
        "type": "nested",
        "properties": {
          "attrId": {
            "type": "long"
          },
          "attrName": {
            "type": "keyword",
            "index": false,
            "doc_values": false
          },
          "attrValue": {
            "type": "keyword"
          }
        }
      }
    }
  }
}

2. 上架接口

gulimall-product/src/main/java/site/zhourui/gulimall/product/controller/SpuInfoController.java

新增上架接口

	/**
     * 商品上架
     * POST /product/spuinfo/{spuId}/up
     */
    @PostMapping("/{spuId}/up")
    // @RequiresPermissions("product:spuinfo:delete")
    public R up(@PathVariable("spuId") Long spuId){
    
    
        spuInfoService.up(spuId);

        return R.ok();
    }

gulimall-product/src/main/java/site/zhourui/gulimall/product/service/SpuInfoService.java

新增接口

    void up(Long spuId);

gulimall-product/src/main/java/site/zhourui/gulimall/product/service/impl/SpuInfoServiceImpl.java

商品上架主要逻辑代码

	@Autowired
    BrandService brandService;
    @Autowired
    CategoryService categoryService;

    @Autowired
    WareFeignService wareFeignService;
    @Autowired
    SearchFeignService searchFeignService;
/**
 * 商品上架【spu商品上架,会上架所有sku商品到es,以skuId作为文档id】
 */
@Override
public void up(Long spuId) {
    
    
    // sku的属性是继承spu的,一个商品下不同的sku,其基本属性是相同的,销售属性不同
    // TODO 4、查询所有sku可以被用来检索 的规格属性
    // 1、根据spuId获取所有Attr,过滤掉不可检索的Attr
    List<ProductAttrValueEntity> baseAttrs = attrValueService.baseAttrlistforspu(spuId);
    // 2、将结果包装成AttrId集合
    List<Long> attrIds = baseAttrs.stream().map(attr -> {
    
    
        return attr.getAttrId();
    }).collect(Collectors.toList());
    // 3、获取所有可检索的AttrId集合,包装成set
    List<Long> searchAttrIds = attrService.selectSearchAttrIds(attrIds);
    HashSet<Long> idSet = new HashSet<>(searchAttrIds);
    // 4、将所有Attr根据set过滤并包装成List<SkuEsModel.Attrs>,最终存入es
    List<SkuEsModel.Attrs> attrsList = baseAttrs.stream().filter(item -> {
    
    
        return idSet.contains(item.getAttrId());
    }).map(item -> {
    
    
        SkuEsModel.Attrs attrs = new SkuEsModel.Attrs();
        BeanUtils.copyProperties(item, attrs);
        return attrs;
    }).collect(Collectors.toList());

    // 6、查出当前spuId对应的所有sku商品信息
    List<SkuInfoEntity> skus = skuInfoService.getSkusBySpuId(spuId);
    // 7、包装所有skuId
    List<Long> skuIds = skus.stream().map(SkuInfoEntity::getSkuId).collect(Collectors.toList());
    // TODO 1、发送远程调用查询库存是否有
    // 8、库存信息,如果为null,库存为true
    Map<Long, Boolean> stockMap = null;
    try {
    
    
        // 9、远程调用,获取所有sku的库存信息
        R r = wareFeignService.getSkusHasStock(skuIds);
        // 10、返回数据泛型,受保护的构造器,使用匿名内部类
        TypeReference<List<SkuHasStockTo>> typeReference = new TypeReference<List<SkuHasStockTo>>() {
    
    
        };
        // 11、封装,skuId为key,hasStock为值
        stockMap = r.getData(typeReference).stream().collect(Collectors.toMap(SkuHasStockTo::getSkuId, item -> item.getHasStock()));
    }catch (Exception e){
    
    
        log.error("库存服务查询异常:原因{}", e);
    }
    Map<Long, Boolean> finalStockMap = stockMap;
    // 12、封装每个sku的信息,List<SkuEsModel>保存到es
    List<SkuEsModel> upProducts = skus.stream().map(sku -> {
    
    
        SkuEsModel esModel = new SkuEsModel();
        BeanUtils.copyProperties(sku, esModel);
        // 单独处理的数据:
        // skuPrice, skuImg, hasstock, hotScore,
        esModel.setSkuPrice(sku.getPrice());
        esModel.setSkuImg(sku.getSkuDefaultImg());
        // 库存为null【远程调用失败,默认为true】
        if (finalStockMap == null) {
    
    
            esModel.setHasStock(true);
        }else {
    
    
            esModel.setHasStock(finalStockMap.get(sku.getSkuId()));
        }
        // TODO 2、热度评分。0
        esModel.setHotScore(0l);
        // TODO 3、查询品牌和分类的名字
        BrandEntity brand = brandService.getById(esModel.getBrandId());
        esModel.setBrandName(brand.getName());
        esModel.setBrandImg(brand.getLogo());

        CategoryEntity category = categoryService.getById(esModel.getCatalogId());
        esModel.setCatalogName(category.getName());
        // 设置检索属性:冗余信息,spu规格,sku共用
        esModel.setAttrs(attrsList);
        return esModel;
    }).collect(Collectors.toList());

    // TODO 5、将数据发送给es进行保存:gulimall-search
    R r = searchFeignService.productStatusUp(upProducts);
    if (r.getCode() == 0) {
    
    
        // 远程调用成功
        // TODO 6、修改当前spu的状态 上架
        baseMapper.updateSpuStatus(spuId, ProductConstant.StatusEnum.SPU_UP.getCode());
    }else {
    
    
        // 远程调用失败
        // TODO 7、重复调用?接口幂等性:重试机制?
        //Feign调用流程
        /**
         * 1、构造请求数据,将对象转为json
         *      RequestTemplate template = buildTemplateFromArgs.create(argv);
         * 2、发送请求进行执行:【执行成功会解码响应数据】
         *      excuteAndDecode(template)
         * 3、执行请求会有重试机制
         *      while(true){
         *          try{
         *              excuteAndDecode(template)
         *          }catch() {
         *              try{
         *                  // 默认重试5次,也有不重试
         *                  retryer.continueOrPropagate(e);
         *               }catch() {
         *                  throw ex;
         *               }
         *              continue;
         *          }
         *      }
         *
         *
         */
    }
}

3.获取所有可检索的AttrId集合,包装成set

gulimall-product/src/main/java/site/zhourui/gulimall/product/service/AttrService.java

    List<Long> selectSearchAttrIds(List<Long> attrIds);

gulimall-product/src/main/java/site/zhourui/gulimall/product/service/impl/AttrServiceImpl.java

    @Override
    public List<Long> selectSearchAttrIds(List<Long> attrIds) {
    
    
        return baseMapper.selectSearchAttrIds(attrIds);
    }

gulimall-product/src/main/java/site/zhourui/gulimall/product/dao/AttrDao.java

    List<Long> selectSearchAttrIds(@Param("attrIds") List<Long> attrIds);

gulimall-product/src/main/resources/mapper/product/AttrDao.xml

    <select id="selectSearchAttrIds" resultType="java.lang.Long">
        SELECT attr_id FROM `pms_attr` WHERE attr_id in
        <foreach collection="attrIds" item="id" separator=", " open="(" close=")">
            #{id}
        </foreach>
        AND search_type = 1
    </select>

4.新增SkuEsModel.Attrs实体类

gulimall-common/src/main/java/site/zhourui/common/to/es/SkuEsModel.java

package site.zhourui.common.to.es;

import lombok.Data;

import java.math.BigDecimal;
import java.util.List;

@Data
public class SkuEsModel {
    
    
    private Long skuId;
    private Long spuId;
    private String skuTitle;
    private BigDecimal skuPrice;
    private String skuImg;
    private Long saleCount;
    private Boolean hasStock;
    private Long hotScore;
    private Long brandId;
    private Long catalogId;
    private String brandName;
    private String brandImg;
    private String catalogName;
    private List<Attrs> attrs;

    @Data
    public static class Attrs {
    
    
        private Long attrId;
        private String attrName;
        private String attrValue;
    }
}

5.查出当前spuId对应的所有sku商品信息

gulimall-product/src/main/java/site/zhourui/gulimall/product/service/SkuInfoService.java

    List<SkuInfoEntity> getSkusBySpuId(Long spuId);

gulimall-product/src/main/java/site/zhourui/gulimall/product/service/impl/SkuInfoServiceImpl.java

    @Override
    public List<SkuInfoEntity> getSkusBySpuId(Long spuId) {
    
    
        return this.list(new QueryWrapper<SkuInfoEntity>().eq("spu_id", spuId));
    }

6.发送远程调用查询库存是否有

6.1 封装SkuHasStockTo对象

gulimall-common/src/main/java/site/zhourui/common/to/SkuHasStockTo.java

gulimall-ware/src/main/java/site/zhourui/gulimall/ware/vo/SkuHasStockVo.java

package site.zhourui.gulimall.ware.vo;

import lombok.Data;



@Data
public class SkuHasStockVo {
    
    

    private Long skuId;

    private Boolean hasStock;

}

6.1在gulimall-ware模块新增接口

gulimall-ware/src/main/java/site/zhourui/gulimall/ware/controller/WareSkuController.java

	/**
     * 查询sku是否有库存
     */
    @PostMapping("/hasstock")
    // @RequiresPermissions("ware:waresku:list")
    public R getSkusHasStock(@RequestBody List<Long> skuIds){
    
    
        // sku_id  stock
        List<SkuHasStockVo> vos = wareSkuService.getSkusHasStock(skuIds);
        return R.ok().setData(vos);
    }

gulimall-ware/src/main/java/site/zhourui/gulimall/ware/service/WareSkuService.java

	/**
     * 判断是否有库存
     */
    List<SkuHasStockVo> getSkusHasStock(List<Long> skuIds);

gulimall-ware/src/main/java/site/zhourui/gulimall/ware/service/impl/WareSkuServiceImpl.java

  /**
     *  检查sku 是否有库存
     */
    @Override
    public List<SkuHasStockVo> getSkusHasStock(List<Long> skuIds) {
    
    
        List<SkuHasStockVo> vos = skuIds.stream().map(skuId -> {
    
    
            SkuHasStockVo vo = new SkuHasStockVo();
            // 1、不止一个仓库有,多个仓库都有库存 sum
            // 2、锁定库存是别人下单但是还没下完
            Long count = baseMapper.getSkuStock(skuId);
            vo.setSkuId(skuId);
            vo.setHasStock(count == null ? false : count > 0);
            return vo;
        }).collect(Collectors.toList());
        return vos;
    }

6.2 新增远程调用接口

gulimall-product/src/main/java/site/zhourui/gulimall/product/feign/WareFeignService.java

package site.zhourui.gulimall.product.feign;


import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import site.zhourui.common.utils.R;

import java.util.List;

@FeignClient("gulimall-ware")
public interface WareFeignService {
    
    
    @PostMapping("/ware/waresku/hasstock")
    R getSkusHasStock(@RequestBody List<Long> skuIds);
}

6.3 修改R

gulimall-common/src/main/java/site/zhourui/common/utils/R.java

新增

	public <T> T getData(String key, TypeReference<T> typeReference) {
    
    
		Object data = get(key);// 默认是map类型,springmvc做的
		String jsonStr = JSON.toJSONString(data);
		T t = JSON.parseObject(jsonStr, typeReference);
		return t;
	}

	// 利用fastJson进行逆转
	// 这里要声明泛型<T>,这个泛型只跟方法有关,跟类无关。
	// 例如类上有个泛型,这里可以使用类上的泛型,就不用声明
	public <T> T getData(TypeReference<T> typeReference) {
    
    
		Object data = get("data");// 默认是map类型,springmvc做的
		String jsonStr = JSON.toJSONString(data);
		T t = JSON.parseObject(jsonStr, typeReference);
		return t;
	}

	public R setData(Object data) {
    
    
		put("data", data);
		return this;
	}

7. 将数据发送给es进行保存

7.1 新增上架异常

gulimall-common/src/main/java/site/zhourui/common/exception/BizCodeEnume.java

新增异常类型

	UNKNOW_EXCEPTION(10000, "系统未知异常"),
    VALID_EXCEPTION(10001, "参数格式验证失败"),
    PRODUCT_UP_EXCEPTION(11000, "商品上架异常");

7.2 新增es常量

gulimall-common/src/main/java/site/zhourui/common/constant/EsConstant.java

package site.zhourui.common.constant;

public class EsConstant {
    
    
    public static final String PRODUCT_INDEX = "gulimall_product";   // sku数据在es中的索引
    public static final Integer PRODUCT_PAGESIZE = 8;   // 检索es 显示数据条数
}

7.3 在gulimall-search模块新增接口

gulimall-search/src/main/java/site/zhourui/gilimall/search/controller/ElasticSaveController.java

package site.zhourui.gilimall.search.controller;


import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import site.zhourui.common.exception.BizCodeEnume;
import site.zhourui.common.to.es.SkuEsModel;
import site.zhourui.common.utils.R;
import site.zhourui.gilimall.search.service.ProductSaveService;

import java.io.IOException;
import java.util.List;

@Slf4j
@RequestMapping("/search/save")
@RestController
public class ElasticSaveController {
    
    

    @Autowired
    ProductSaveService productSaveService;

    // 上架商品
    @PostMapping("/product")
    public R productStatusUp(@RequestBody List<SkuEsModel> skuEsModels) {
    
    
        boolean b = false;
        try {
    
    
            b = productSaveService.productStatusUp(skuEsModels);
        } catch (IOException e) {
    
    
            log.error("ElasticSaveController商品上架错误:{}", e);
            return R.error(BizCodeEnume.PRODUCT_UP_EXCEPTION.getCode(), BizCodeEnume.PRODUCT_UP_EXCEPTION.getMsg());
        }
        if (b) {
    
    
            return R.ok();
        }else {
    
    
            return R.error(BizCodeEnume.PRODUCT_UP_EXCEPTION.getCode(), BizCodeEnume.PRODUCT_UP_EXCEPTION.getMsg());
        }
    }
}

gulimall-search/src/main/java/site/zhourui/gilimall/search/service/ProductSaveService.java

package site.zhourui.gilimall.search.service;

import site.zhourui.common.to.es.SkuEsModel;

import java.io.IOException;
import java.util.List;

/**
 * @author zr
 * @date 2021/10/26 13:50
 */
public interface ProductSaveService {
    
    
    boolean productStatusUp(List<SkuEsModel> skuEsModels) throws IOException;
}

gulimall-search/src/main/java/site/zhourui/gilimall/search/service/impl/ProductSaveServiceImpl.java

package site.zhourui.gilimall.search.service.impl;

import com.alibaba.fastjson.JSON;

import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.xcontent.XContentType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import site.zhourui.common.constant.EsConstant;
import site.zhourui.common.to.es.SkuEsModel;
import site.zhourui.gilimall.search.config.GulimallElasticSearchConfig;
import site.zhourui.gilimall.search.service.ProductSaveService;

import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

@Slf4j
@Service("productSaveService")
public class ProductSaveServiceImpl implements ProductSaveService {
    
    

    @Autowired
    RestHighLevelClient client;

    /**
     * 保存数据到es:
     * 1、创建单个保存请求
     *      IndexRequest indexRequest = new IndexRequest(EsConstant.PRODUCT_INDEX)
     * 2、请求绑定数据
     *      indexRequest.source(JSON.toJSONString(model), XContentType.JSON)
     * 3、循环构建批量保存请求
     *      BulkRequest.add(indexRequest)
     * 4、使用客户端发送批量保存请求
     *      client.bulk(bulkRequest, GulimallElasticSearch.COMMON_OPTIONS)
     */
    @Override
    public boolean productStatusUp(List<SkuEsModel> skuEsModels) throws IOException {
    
    
        // 保存到ES
        // 1、给es中建立索引、product,建立映射关系

        // 2、给es中保存这些数据【使用bulk,不使用index一条条保存】
        // BulkRequest bulkRequest, RequestOptions options
        BulkRequest bulkRequest = new BulkRequest();
        for (SkuEsModel model : skuEsModels) {
    
    
            // 构造保存请求,设置索引+id
            IndexRequest indexRequest = new IndexRequest(EsConstant.PRODUCT_INDEX);
            indexRequest.id(model.getSkuId().toString());
            String s = JSON.toJSONString(model);
            // 绑定请求与数据
            indexRequest.source(s, XContentType.JSON);
            // 添加到批量保存
            bulkRequest.add(indexRequest);
        }
        BulkResponse bulk = client.bulk(bulkRequest, GulimallElasticSearchConfig.COMMON_OPTIONS);
        // TODO 如果批量错误
        boolean b = bulk.hasFailures();
        List<String> collect = Arrays.stream(bulk.getItems()).map(item -> {
    
    
            return item.getId();
        }).collect(Collectors.toList());
        log.info("商品上架完成:{},返回数据:{}", collect, bulk);

        return !b;// 因为b代表的是是否有异常
    }
}

7.4 新增远程调用接口

gulimall-product/src/main/java/site/zhourui/gulimall/product/feign/SearchFeignService.java

package site.zhourui.gulimall.product.feign;


import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import site.zhourui.common.to.es.SkuEsModel;
import site.zhourui.common.utils.R;

import java.util.List;

@FeignClient("gulimall-search")
public interface SearchFeignService {
    
    
    // 上架商品
    @PostMapping("/search/save/product")
    R productStatusUp(@RequestBody List<SkuEsModel> skuEsModels);
}

8. 商品状态枚举

gulimall-common/src/main/java/site/zhourui/common/constant/ProductConstant.java

新增

    public enum StatusEnum {
    
    
        NEW_SPU(0, "新建"),
        SPU_UP(1, "商品上架"),
        SPU_DOWN(2, "商品下架");
        private int code;
        private String msg;

        StatusEnum(int code, String msg) {
    
    
            this.code = code;
            this.msg = msg;
        }

        public int getCode() {
    
    
            return code;
        }

        public void setCode(int code) {
    
    
            this.code = code;
        }

        public String getMsg() {
    
    
            return msg;
        }

        public void setMsg(String msg) {
    
    
            this.msg = msg;
        }
    }

猜你喜欢

转载自blog.csdn.net/qq_31745863/article/details/121331683
今日推荐