✅P130-135_商城业务-商品上架-完整流程

gong_yz大约 6 分钟谷粒商城

商品上架接口

url

POST:/product/spuinfo/{spuId}/up

请求参数

响应数据

{
	"msg": "success",
	"code": 0
}

构造基本数据

cfmall-common/src/main/java/com/gyz/common/es/SkuEsModel.java

package com.gyz.common.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;

    }
}

编写商品上架逻辑

Controller

cfmall-product/src/main/java/com/gyz/cfmall/product/controller/SpuInfoController.java

@Autowired
private SpuInfoService spuInfoService;

@RequestMapping("/{spuId}/up")
public R spuUp(@PathVariable("spuId") Long spuId) {
    spuInfoService.up(spuId);
    return R.ok();
}

Service

    /**
     * 商品上架
     * @param spuId :
     * @return void
     */
    void up(Long spuId);

cfmall-product/src/main/java/com/gyz/cfmall/product/service/impl/SpuInfoServiceImpl.java

    /**
     * @param spuId :
     * @return void
     */
    @GlobalTransactional(rollbackFor = Exception.class)
    @Override
    public void up(Long spuId) {
        //1、查出当前spuId对应的所有sku信息,品牌的名字
        List<SkuInfoEntity> skuInfoEntities = skuInfoService.getSkusBySpuId(spuId);

        // 2、查出当前sku的所有可以被用来检索的规格属性
        List<ProductAttrValueEntity> baseAttrs = productAttrValueService.baseAttrListforspu(spuId);

        List<Long> attrIds = baseAttrs.stream().map(attr -> {
            return attr.getAttrId();
        }).collect(Collectors.toList());

        //3、查询可检索的属性
        List<Long> searchAttrIds = attrService.selectSearchAttrs(attrIds);
        //转换为Set集合
        Set<Long> idSet = searchAttrIds.stream().collect(Collectors.toSet());

        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());

        List<Long> skuIdList = skuInfoEntities.stream()
                .map(SkuInfoEntity::getSkuId)
                .collect(Collectors.toList());
        //4、发送远程调用,库存系统查询是否有库存
        Map<Long, Boolean> stockMap = null;

        try {
            R skuHasStock = wareFeignService.getSkuHasStock(skuIdList);
            TypeReference<List<SkuHasStockVo>> typeReference = new TypeReference<List<SkuHasStockVo>>() {
            };
            stockMap = skuHasStock.getData(typeReference).stream()
                    .collect(Collectors.toMap(SkuHasStockVo::getSkuId, item -> item.getHasStock()));
        } catch (Exception e) {
            log.error("库存服务查询异常:原因{}", e);
        }

        //5、封装每个sku的信息
        Map<Long, Boolean> finalStockMap = stockMap;
        List<SkuEsModel> collect = skuInfoEntities.stream().map(sku -> {
            //1、组装需要的数据
            SkuEsModel esModel = new SkuEsModel();
            esModel.setSkuPrice(sku.getPrice());
            esModel.setSkuImg(sku.getSkuDefaultImg());

            //设置库存信息
            if (finalStockMap == null) {
                esModel.setHasStock(true);
            } else {
                esModel.setHasStock(finalStockMap.get(sku.getSkuId()));
            }

            // 2、热度评分。0
            esModel.setHotScore(0L);

            // 3、查询品牌和分类的名字信息
            BrandEntity brandEntity = brandService.getById(sku.getBrandId());
            esModel.setBrandName(brandEntity.getName());
            esModel.setBrandId(brandEntity.getBrandId());
            esModel.setBrandImg(brandEntity.getLogo());

            CategoryEntity categoryEntity = categoryService.getById(sku.getCatalogId());
            esModel.setCatalogId(categoryEntity.getCatId());
            esModel.setCatalogName(categoryEntity.getName());

            //设置检索属性
            esModel.setAttrs(attrsList);

            BeanUtils.copyProperties(sku, esModel);

            return esModel;
        }).collect(Collectors.toList());

        // 6、将数据发给es进行保存:cfmall-search
        R r = searchFeignService.productStatusUp(collect);

        if (r.getCode() == 0) {
            //远程调用成功
            // 7、修改当前spu的状态
            this.baseMapper.updaSpuStatus(spuId, ProductConstant.ProductStatusEnum.SPU_UP.getCode());
        } else {
            //远程调用失败
            //TODO 8、重复调用?接口幂等性:重试机制
        }
    }

1、查出当前spuId对应的所有sku信息,品牌的名字

cfmall-product/src/main/java/com/gyz/cfmall/product/service/SkuInfoService.java

    List<SkuInfoEntity> getSkusBySpuId(Long spuId);

cfmall-product/src/main/java/com/gyz/cfmall/product/service/impl/SkuInfoServiceImpl.java

    @Override
    public List<SkuInfoEntity> getSkusBySpuId(Long spuId) {

        List<SkuInfoEntity> skuInfoEntities = this.list(new QueryWrapper<SkuInfoEntity>().eq("spu_id", spuId));
        return skuInfoEntities;
    }

2、查出当前sku的所有可以被用来检索的规格属性

cfmall-product/src/main/java/com/gyz/cfmall/product/service/ProductAttrValueService.java

List<ProductAttrValueEntity> baseAttrListforspu(Long spuId);

cfmall-product/src/main/java/com/gyz/cfmall/product/service/impl/ProductAttrValueServiceImpl.java

    @Override
    public List<ProductAttrValueEntity> baseAttrListforspu(Long spuId) {

        List<ProductAttrValueEntity> attrValueEntityList = this.baseMapper.selectList(
                new QueryWrapper<ProductAttrValueEntity>().eq("spu_id", spuId));

        return attrValueEntityList;
    }

3、查询可检索的属性

cfmall-product/src/main/java/com/gyz/cfmall/product/service/AttrService.java

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

cfmall-product/src/main/java/com/gyz/cfmall/product/service/impl/AttrServiceImpl.java

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

cfmall-product/src/main/java/com/gyz/cfmall/product/dao/AttrDao.java

@Mapper
public interface AttrDao extends BaseMapper<AttrEntity> {
    List<Long> selectSearchAttrIds(@Param("attrIds") List<Long> attrIds);
}

cfmall-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、发送远程调用,库存系统查询是否有库存

cfmall-product/src/main/java/com/gyz/cfmall/product/feign/WareFeignService.java

package com.gyz.cfmall.product.feign;

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

import java.util.List;

@FeignClient("cfmall-ware")
public interface WareFeignService {

    @PostMapping(value = "/ware/waresku/hasStock")
    R getSkuHasStock(@RequestBody List<Long> skuIds);

}

cfmall-product/src/main/java/com/gyz/cfmall/product/feign/SearchFeignService.java

@FeignClient("cfmall-search")
public interface SearchFeignService {

    @PostMapping(value = "/search/save/product")
    public R productStatusUp(@RequestBody List<SkuEsModel> skuEsModels);

}

远程查询库存

分析:一个商品可以存放在多个仓库中,因此,需要累加,但是还需要减去被锁定的商品即未支付的商品数量

cfmall-ware/src/main/java/com/gyz/cfmall/ware/controller/WareSkuController.java

    @Resource
    private WareSkuService wareSkuService;

	/**
     * 查询sku是否有库存
     * @return
     */
    @PostMapping(value = "/hasStock")
    public R getSkuHasStock(@RequestBody List<Long> skuIds) {
        //skuId stock
        List<SkuHasStockVo> vos = wareSkuService.getSkuHasStock(skuIds);

        return R.ok().setData(vos);
    }

cfmall-common/src/main/java/com/gyz/common/to/SkuHasStockVo.java

package com.gyz.common.to;

import lombok.Data;

@Data
public class SkuHasStockVo {
    private Long skuId;
    private Boolean hasStock;
}

cfmall-ware/src/main/java/com/gyz/cfmall/ware/service/WareSkuService.java

    /**
     * 判断是否有库存
     * @param skuIds
     * @return
     */
    List<SkuHasStockVo> getSkuHasStock(List<Long> skuIds);

cfmall-ware/src/main/java/com/gyz/cfmall/ware/service/impl/WareSkuServiceImpl.java

    @Override
    public List<SkuHasStockVo> getSkuHasStock(List<Long> skuIds) {
        List<SkuHasStockVo> skuHasStockVos = skuIds.stream().map(item -> {
            Long count = this.baseMapper.getSkuStock(item);
            SkuHasStockVo skuHasStockVo = new SkuHasStockVo();
            skuHasStockVo.setSkuId(item);
            skuHasStockVo.setHasStock(count == null ? false : count > 0);
            return skuHasStockVo;
        }).collect(Collectors.toList());
        return skuHasStockVos;
    }

cfmall-ware/src/main/java/com/gyz/cfmall/ware/dao/WareSkuDao.java

@Mapper
public interface WareSkuDao extends BaseMapper<WareSkuEntity> {
    Long getSkuStock(Long item);
}

cfmall-ware/src/main/resources/mapper/ware/WareSkuDao.xml

    <select id="getSkuStock" resultType="java.lang.Long">
        SELECT SUM(stock - stock_locked) FROM wms_ware_sku WHERE sku_id = #{skuId}
    </select>

ES服务保存上架商品

6、将数据发给es进行保存:cfmall-search

cfmall-search/src/main/java/com/gyz/cfmall/search/controller/ElasticSaveController.java

    @Autowired
    private ProductSaveService productSaveService;

	/**
     * 上架商品
     * @param skuEsModels
     * @return
     */
    @PostMapping(value = "/product")
    public R productStatusUp(@RequestBody List<SkuEsModel> skuEsModels) {

        boolean status = false;
        try {
            status = productSaveService.productStatusUp(skuEsModels);
        } catch (IOException e) {
            //log.error("商品上架错误{}",e);

            return R.error(BizCodeEnum.PRODUCT_UP_EXCEPTION.getCode(), BizCodeEnum.PRODUCT_UP_EXCEPTION.getMessage());
        }

        if (status) {
            return R.error(BizCodeEnum.PRODUCT_UP_EXCEPTION.getCode(), BizCodeEnum.PRODUCT_UP_EXCEPTION.getMessage());
        } else {
            return R.ok();
        }

    }
public interface ProductSaveService {

    boolean productStatusUp(List<SkuEsModel> skuEsModels) throws IOException;
}

cfmall-search/src/main/java/com/gyz/cfmall/search/service/impl/ProductSaveServiceImpl.java

    @Autowired
    private RestHighLevelClient esRestClient;

	@Override
    public boolean productStatusUp(List<SkuEsModel> skuEsModels) throws IOException {

        //1.在es中建立索引,建立号映射关系(doc/json/product-mapping.json)

        //2. 在ES中保存这些数据
        BulkRequest bulkRequest = new BulkRequest();
        for (SkuEsModel skuEsModel : skuEsModels) {
            //构造保存请求
            IndexRequest indexRequest = new IndexRequest(EsConstant.PRODUCT_INDEX);
            indexRequest.id(skuEsModel.getSkuId().toString());
            String jsonString = JSON.toJSONString(skuEsModel);
            indexRequest.source(jsonString, XContentType.JSON);
            bulkRequest.add(indexRequest);
        }


        BulkResponse bulk = esRestClient.bulk(bulkRequest, CfMallElasticSearchConfig.COMMON_OPTIONS);

        //TODO 如果批量错误
        boolean hasFailures = bulk.hasFailures();

        List<String> collect = Arrays.asList(bulk.getItems()).stream().map(item -> {
            return item.getId();
        }).collect(Collectors.toList());

        log.info("商品上架完成:{}", collect);

        return hasFailures;
    }

创建映射

在kibana的Dev Tools中执行

PUT product
{
  "mappings": {
    "properties": {
      "skuId": {
        "type": "long"
      },
      "spuId": {
        "type": "long"
      },
      "skuTitle": {
        "type": "text",
        "analyzer": "ik_smart"
      },
      "skuPrice": {
        "type": "keyword"
      },
      "skuImg": {
        "type": "keyword",
        "index": false,
        "doc_values": false
      },
      "saleCount": {
        "type": "long"
      },
      "hosStock": {
        "type": "boolean"
      },
      "hotScore": {
        "type": "long"
      },
      "brandId": {
        "type": "long"
      },
      "catelogId": {
        "type": "long"
      },
      "brandName": {
        "type": "keyword",
        "index": false,
        "doc_values": false
      },
      "brandImg": {
        "type": "keyword",
        "index": false,
        "doc_values": false
      },
      "catelogName": {
        "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"
          }
        }
      }
    }
  }
}

IDEA-Compound:一键启动多个服务

配置Compound

配置完成后,点击运行即可,服务会不断地启动完成


测试

未上架之前查询商品信息

上架之后查询索引product


问题

磁盘空间不足问题

在上架时发现商品保存到ES出现错误如下:

{"index":"product","type":"_doc","id":"6","cause":{"type":"exception","reason":"Elasticsearch exception [type=cluster_block_exception, reason=index [product] blocked by: [FORBIDDEN/12**/index read-only** / allow delete (api)];]"},"status":403}

原因为磁盘空间不足,索引为只读状态,不可写入。

解决办法:对磁盘空间进行扩容

参考:Ubuntu系统根磁盘空间扩容