一、商品上架
1、項目架構圖
2、product-es準備
ES在記憶體中,是以在檢索中優于mysql。
ES也支援叢集
,資料分片存儲。
- 需求:
- 上架的商品才可以在網站展示。
- 上架的商品需要可以被檢索。(
)上架的商品就加入到es去被檢索
- 分析sku在es中如何存儲 商品mapping
- 分析: 商品上架在es中是存sku還是spu?
- 1)檢索的時候
,是需要按照輸入名字
sku的title進行全文檢索
- 2)檢素使用
,規格是商品規格
,每個spu是一樣的spu的公共屬性
- 3)按照
進去的都是直接列出分類id
的,還可以切換。spu
- 4〕我們如果将sku的全量資訊儲存到es中(包括spu屬性〕就太多字段了
- 1)檢索的時候
- 處理方案
-
方案1:
存儲的到
記錄中,每個都帶有對應sku具體的attr屬性一個索引
-
{ skuId:1 spuId:11 skyTitile:華為xx price:999 saleCount:99 attr:[ {尺寸:5}, {CPU:高通945}, {分辨率:全高清} ]
- 缺點:
- 如果每個sku都存儲規格參數(如尺寸),會有
,因為每個spu對應的sku的規格參數都一樣備援存儲
- 如果每個sku都存儲規格參數(如尺寸),會有
-
-
方案2:
存儲到
記錄中,一個存儲對應的spu/sku的id,然後去檢索查詢另一個記錄中的資料,就可以解決上面的備援存儲的問題兩個索引
-
#sku索引 { spuId:1 skuId:11 } #attr索引 { skuId:11 attr:[ {尺寸:5}, {CPU:高通945}, {分辨率:全高清} ] }
- 缺點:
- 先找到4000個符合要求的spu,再根據4000個spu查詢對應的屬性,封裝了4000個id,long 8B*4000=32000B=32KB 1K個人檢索,就是32MB
- 是以會出現如果有大量并發的請求,就會出現一刹那32MB,我們就加兩個0,就會有3g多的資料在網絡傳輸
- 這樣子就會出現
網絡問題
-
-
結論
如果将規格參數單獨建立索引,會出現檢索時出現大量資料傳輸的問題,會引起網絡網絡
是以選用
,以空間換時間方案1
-
-
建立product索引
索引資料模型
其中PUT product { "mappings":{ "properties": { "skuId":{ "type": "long" }, "spuId":{ "type": "keyword" }, # 不可分詞 "skuTitle": { "type": "text", "analyzer": "ik_smart" # 中文分詞器 }, "skuPrice": { "type": "keyword" }, "skuImg" : { "type": "keyword" }, "saleCount":{ "type":"long" }, "hasStock": { "type": "boolean" }, "hotScore": { "type": "long" }, "brandId": { "type": "long" }, "catalogId": { "type": "long" }, "brandName": {"type": "keyword"}, "brandImg":{ "type": "keyword", "index": false, # 不可被檢索,不生成index "doc_values": false # 不可被聚合 }, "catalogName": {"type": "keyword" }, "attrs": { "type": "nested", "properties": { "attrId": {"type": "long" }, "attrName": { "type": "keyword", "index": false, "doc_values": false }, "attrValue": {"type": "keyword" } } } } } }
- “type”: “keyword” 保持資料精度問題,可以檢索,但不分詞
- “index”:false 代表不可被檢索
- “doc_values”: false 不可被聚合,es就不會維護一些聚合的資訊
- 備援存儲的字段: 不用來檢索,也不用來分析,節省空間
- 庫存是bool。
- 檢索品牌id,但是不檢索品牌名字、圖檔 用skuTitle檢索
- nested嵌入式對象
屬性是"type": “nested”,因為是内部的屬性進行檢索
數組類型的對象會被
扁平化處理
(對象的每個屬性會分别存儲到一起)
user.name=["aaa","bbb"]
user.addr=["ccc","ddd"]
這種存儲方式,可能會發生如下錯誤:
錯誤檢索到{aaa,ddd},這個組合是不存在的
數組的扁平化處理會使檢索能檢索到本身不存在的,為了解決這個問題,就采用了
嵌入式屬性
,數組裡是對象時用嵌入式屬性(不是對象無需用嵌入式屬性)
nested閱讀:https://blog.csdn.net/weixin_40341116/article/details/80778599
使用聚合:https://blog.csdn.net/kabike/article/details/101460578
3、正式編碼
- controller
//POST /product/spuinfo/{spuId}/up
//上架
@PostMapping("/{spuId}/up")
public R spuUp(@PathVariable("spuId")Long spuId){
spuInfoService.up(spuId);
return R.ok();
}
- 傳輸to對象
因為這裡的search服務也需要使用到這個to對象,是以我們建立在common中
商品上架需要在es中儲存spu資訊并更新spu的狀态資訊,由于SpuInfoEntity與索引的資料模型并不對應,是以我們要建立專門的vo進行資料傳輸
com.achang.common.to.es.SkuEsModel
@Data
public class SkuEsModel implements Serializable {
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;
}
}
- service
com.achang.achangmall.product.service.impl.SpuInfoServiceImpl
//商品上架
@Override
public void up(Long spuId) {
//1、查詢目前spuId對應的所有sku資訊,品牌的名字
List<SkuInfoEntity> skuInfoEntities = skuInfoService.getSkusBySpuId(spuId);
List<Long> skuIdList = skuInfoEntities.stream().map(item -> {
return item.getSkuId();
}).collect(Collectors.toList());
//todo 查詢目前sku可以被檢索的規格屬性
List<ProductAttrValueEntity> baseAttrList = productAttrValueService.baseAttrList(spuId);
List<Long> attrIdList = baseAttrList.stream().map(item -> {
return item.getAttrId();
}).collect(Collectors.toList());
List<Long> searchAttrIds = attrService.selectSearchAttrIds(attrIdList);
Set<Long> idSet = new HashSet<>(searchAttrIds);
List<SkuEsModel.Attrs> attrsList = baseAttrList.stream().filter(item -> {
return idSet.contains(item.getAttrId());
}).map(s -> {
SkuEsModel.Attrs attrsEs = new SkuEsModel.Attrs();
BeanUtils.copyProperties(s, attrsEs);
return attrsEs;
}).collect(Collectors.toList());
Map<Long, Boolean> stockMap = null;
try {
//發送遠端調用,庫存系統查詢是否有庫存
R r = wareFeignService.getSkuHasStock(skuIdList);
TypeReference<List<SkuHasStockVo>> typeReference = new TypeReference<List<SkuHasStockVo>>() {};
stockMap = r.getData(typeReference).stream().collect(Collectors.toMap(SkuHasStockVo::getSkuId, SkuHasStockVo::getHasStock));
}catch (Exception e){
log.error("庫存服務查詢出現問題:原因【{}】",e);//如果遠端調用失敗
}
//2、封裝每個sku的資訊
Map<Long, Boolean> finalStockMap = stockMap;
List<SkuEsModel> collect = skuInfoEntities.stream().map(i -> {
SkuEsModel model = new SkuEsModel();
BeanUtils.copyProperties(i,model);
model.setSkuPrice(i.getPrice());
model.setSkuImg(i.getSkuDefaultImg());
model.setHasStock( (finalStockMap != null)? finalStockMap.get(i.getSkuId()):true );//設定庫存資訊
//熱度評分 0
model.setHotScore(0L);
//查詢品牌 和 分類名字資訊
BrandEntity brandEntity = brandService.getById(model.getBrandId());
model.setBrandName(brandEntity.getName());
model.setBrandImg(brandEntity.getLogo());
CategoryEntity categoryEntity = categoryService.getById(model.getCatalogId());
model.setCatalogName(categoryEntity.getName());
//設定檢索屬性
model.setAttrs(attrsList);
return model;
}).collect(Collectors.toList());
//發給es進行儲存,遠端調用
R r = esFeignService.productStatusUp(collect);
String code = (String) r.get("code");
if (Integer.parseInt(code) == 0){
//調用成功
//todo 修改spu商品狀态
this.baseMapper.updateSpuStatus(spuId, ProductConsatnt.StatusEnum.SPU_UP.getCode());
}else {
//todo 調用失敗,重複調用,接口幂等性;重試機制
}
}
- 在指定的所有屬性集合中,篩選中檢索屬性
com.achang.achangmall.product.service.impl.AttrServiceImpl
//在指定的所有屬性集合中,篩選中檢索屬性
@Override
public List<Long> selectSearchAttrIds(List<Long> attrIdList) {
List<AttrEntity> attrEntities = this.baseMapper.selectList(new QueryWrapper<AttrEntity>()
.eq("search_type", 1)
.in("attr_id", attrIdList).select("attr_id"));
List<Long> searchAttrIdList = attrEntities.stream().map(item -> {
return item.getAttrId();
}).collect(Collectors.toList());
return searchAttrIdList;
}
- 遠端調用接口—庫存服務
com.achang.achangmall.ware.controller.WareSkuController
//查詢sku是否有庫存
@PostMapping("/hasstock")
public R getSkuHasStock(@RequestBody List<Long> skuIdList){
List<SkuHasStockVo> vos = wareSkuService.getSkuHasStock(skuIdList);
return R.ok().put("data",vos);
}
com.achang.achangmall.ware.vo.SkuHasStockVo
@Data
public class SkuHasStockVo {
private Long skuId;
private Boolean hasStock;
}
com.achang.achangmall.ware.controller.WareSkuController
//查詢sku是否有庫存
@PostMapping("/hasstock")
public R getSkuHasStock(@RequestBody List<Long> skuIdList){
List<SkuHasStockVo> vos = wareSkuService.getSkuHasStock(skuIdList);
return R.ok().setData(vos);
}
com.achang.achangmall.ware.service.impl.WareSkuServiceImpl
@Override
public List<SkuHasStockVo> getSkuHasStock(List<Long> skuIdList) {
List<SkuHasStockVo> collect = skuIdList.stream().map(item -> {
SkuHasStockVo vo = new SkuHasStockVo();
Long count = baseMapper.getSkuStock(item);
vo.setSkuId(item);
vo.setHasStock(count == null?false:count>0);
return vo;
}).collect(Collectors.toList());
return collect;
}
achangmall-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` = #{item}
</select>
遠端調用com.achang.achangmall.product.feign.WareFeignService
@FeignClient("achangmall-ware")
public interface WareFeignService {
@PostMapping("/ware/waresku/hasstock")
public R getSkuHasStock(@RequestBody List<Long> skuIdList);
}
遠端調用ES添加索引功能
com.achang.achangmall.search.controller.ESSaveController
@RequestMapping("/search")
@RestController
@Slf4j
public class ESSaveController {
@Autowired
private ProductSaveService productSaveService;
//上架商品
@PostMapping("/save/product")
public R productStatusUp(@RequestBody List<SkuEsModel> esModels) {
Boolean flag = false;
try {
flag = productSaveService.productStatusUp(esModels);
} catch (IOException e) {
e.printStackTrace();
log.error("ESSaveController,商品上架錯誤:{}",e);
return R.error(BizCodeEnum.PRODUCT_UP_EXCEPTION.getCode(),BizCodeEnum.PRODUCT_UP_EXCEPTION.getMsg());
}
if (!flag){
return R.ok();
}else {
return R.error(BizCodeEnum.PRODUCT_UP_EXCEPTION.getCode(),BizCodeEnum.PRODUCT_UP_EXCEPTION.getMsg());
}
}
}
com.achang.achangmall.search.constant.ESConstant
public class ESConstant {
public static final String PRODUCT_INDEX = "product";//sku資料在es中的索引
}
com.achang.achangmall.search.service.impl.ProductSaveServiceImpl
@Slf4j
@Service
public class ProductSaveServiceImpl implements ProductSaveService {
@Autowired
private RestHighLevelClient restHighLevelClient;
//上架商品
@Override
public Boolean productStatusUp(List<SkuEsModel> esModels) throws IOException {
//給es中建立索引
//給es儲存資料
BulkRequest bulkRequest = new BulkRequest();
esModels.forEach(item->{
//構造儲存請求
IndexRequest indexRequest = new IndexRequest(ESConstant.PRODUCT_INDEX);
String json = JSON.toJSONString(item);
indexRequest.source(json, XContentType.JSON);
indexRequest.id(item.getSkuId().toString());
bulkRequest.add(indexRequest);
});
BulkResponse bulk = restHighLevelClient.bulk(bulkRequest, ElasticsearchConfig.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);
return b;
}
}
com.achang.achangmall.product.feign.ESFeignService
@FeignClient("achangmall-search")
public interface ESFeignService {
@PostMapping("/search/save/product")
public R productStatusUp(@RequestBody List<SkuEsModel> esModels);
}
achangmall-product/src/main/resources/mapper/product/SpuInfoDao.xml
<update id="updateSpuStatus">
update `pms_spu_info`
set publish_status =#{code},update_time = NOW()
where id = #{spuId}
</update>
二、商城業務
明天繼續