日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 综合教程 >内容正文

综合教程

谷粒商城分布式高级(三)—— 商城业务(商品上架)

發布時間:2024/1/3 综合教程 45 生活家
生活随笔 收集整理的這篇文章主要介紹了 谷粒商城分布式高级(三)—— 商城业务(商品上架) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

一、商品上架

上架的商品才可以在網站展示。 
上架的商品需要可以被檢索。

1、商品 Mapping

分析:商品上架在 es 中是存 sku 還是 spu?
(1)檢索的時候輸入名字,是需要按照 sku 的 title 進行全文檢索的
(2)檢索使用商品規格,規格是 spu 的公共屬性,每個 spu 是一樣的
(3)按照分類 id 進去的都是直接列出 spu 的,還可以切換
(4)我們如果將 sku 的全量信息保存到 es 中(包括 spu 屬性)就太多量字段了
(5)我們如果將 spu 以及他包含的 sku 信息保存到 es 中,也可以方便檢索,但是 sku 屬于 spu 的級聯動,在 es 中需要 nested 模型,這種性能差點
(6)但是存儲與檢索我們必須性能折中
(7)如果我們分拆存儲,spu 和 attr 一個索引,sku 單獨一個所以可能涉及的問題:檢索商品的名字,如“手機”,對應的 spu 有很多,我們要分析出這些 spu 的所有級聯屬性,再做一次查詢,就必須將所有 spu_id 都發出去。假設有 1 萬個數據,數據傳輸一次就 10000*4=4MB;并發情況下假設 1000 檢索請求,那就是 4GB 的數據,傳輸阻塞時間會很長,業務更加無法繼續。

方案1:

{  
  skuId: 1,
  spuId: 11,
  skyTitile: 華為xx,
  price: 999,
  saleCount: 99,
  attr: [{     
    尺寸: 5    
  }, {      
    CPU: 高通945    
  }, {      
    分辨率: 全高清    
  }]
}

  缺點:如果每個sku都存儲規格參數(如尺寸),會有冗余存儲,因為每個 spu 對應的 sku 的規格參數都一樣

  方案2:

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

  結論:如果將規格參數單獨建立索引,會出現檢索時出現大量數據傳輸的問題,會引起網絡網絡 因此選用方案1,以空間換時間


所以,我們如下設計,這樣才是文檔區別于關系型數據庫的地方,寬表設計,不能去考慮數據庫范式。
建立product索引
最終選用的數據模型:
(1)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,
        "doc_values": false
      },
      "catalogName": {
        "type": "keyword"
      },
      "attrs": {
        "type": "nested",
        "properties": {
          "attrId": {
            "type": "long"
          },
          "attrName": {
            "type": "keyword",
            "index": false,
            "doc_values": false
          },
          "attrValue": {
            "type": "keyword"
          }
        }
      }
    }
  }
}

其中

(1)“type”: “keyword” 保持數據精度問題,可以檢索,但不分詞

(2)“index”:false 代表不可被檢索

(3)“doc_values”: false 不可被聚合,es就不會維護一些聚合的信息 冗余存儲的字段:不用來檢索,也不用來分析,節省空間

  庫存是bool。 檢索品牌id,但是不檢索品牌名字、圖片 用skuTitle檢索

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

2、上架細節

上架是將后臺的商品放在 es 中可以提供檢索和查詢功能
(1)hasStock:代表是否有庫存。默認上架的商品都有庫存。如果庫存無貨的時候才需要更新一下 es
(2)庫存補上以后,也需要重新更新一下 es
(3)hotScore 是熱度值,我們只模擬使用點擊率更新熱度。點擊率增加到一定程度才更新熱度值
(4)下架就是從 es 中移除檢索項,以及修改 mysql 狀態
商品上架步驟:
(1)先在 es 中按照之前的 mapping 信息,建立 product 索引。
(2)點擊上架,查詢出所有 sku 的信息,保存到 es 中
(3)es 保存成功返回,更新數據庫的上架狀態信息

3、數據一致性

(1)商品無庫存的時候需要更新 es 的庫存信息
(2)商品有庫存也要更新 es 的信息

4、上架流程

product里組裝好,search里上架

(1)gulimall-common 更新文件 com.atguigu.common.utils.R
  將R設置成泛型的
package com.atguigu.common.utils;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import org.apache.http.HttpStatus;

import java.util.HashMap;
import java.util.Map;

public class R extends HashMap<String, Object> {
private static final long serialVersionUID = 1L;

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

//利用fastjson進行反序列化
public <T> T getData(TypeReference<T> typeReference) {
Object data = get("data"); //默認是map
String jsonString = JSON.toJSONString(data);
T t = JSON.parseObject(jsonString, typeReference);
return t;
}

//利用fastjson進行反序列化
public <T> T getData(String key,TypeReference<T> typeReference) {
Object data = get(key); //默認是map
String jsonString = JSON.toJSONString(data);
T t = JSON.parseObject(jsonString, typeReference);
return t;
}

public R() {
put("code", 0);
put("msg", "success");
}

public static R error() {
return error(HttpStatus.SC_INTERNAL_SERVER_ERROR, "未知異常,請聯系管理員");
}

public static R error(String msg) {
return error(HttpStatus.SC_INTERNAL_SERVER_ERROR, msg);
}

public static R error(int code, String msg) {
R r = new R();
r.put("code", code);
r.put("msg", msg);
return r;
}

public static R ok(String msg) {
R r = new R();
r.put("msg", msg);
return r;
}

public static R ok(Map<String, Object> map) {
R r = new R();
r.putAll(map);
return r;
}

public static R ok() {
return new R();
}

public R put(String key, Object value) {
super.put(key, value);
return this;
}

public Integer getCode() {

return (Integer) this.get("code");
}

}

(2)gulimall-common 新增文件 com.atguigu.common.to.SkuHasStockVo
package com.atguigu.common.to;

import lombok.Data;

@Data
public class SkuHasStockVo {
private Long skuId;

private Boolean hasStock;
}

(3)gulimall-common 新增文件 com.atguigu.common.to.es.SkuEsModel
商品上架需要在 es 中保存spu信息并更新spu的狀態信息,由于SpuinfoEntity與索引的數據模型并不對應,所以我們要建立專門的vo進行數據傳輸
package com.atguigu.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;
}
}
(4)gulimall-common 更新文件 com.atguigu.common.constant.ProductConstant
package com.atguigu.common.constant;

public class ProductConstant {
public enum AttrEnum{
ATTR_TYPE_BASE(1,"基本屬性"),ATTR_TYPE_SALE(0,"銷售屬性");

private int code;
private String msg;

AttrEnum(int code, String msg){
this.code = code;
this.msg = msg;
}
public int getCode() {
return code;
}
public String getMsg() {
return msg;
}
}

public enum ProductStatusEnum {
NEW_SPU(0,"新建"),
SPU_UP(1,"商品上架"),
SPU_DOWN(2,"商品下架"),
;

private int code;

private String msg;

public int getCode() {
return code;
}

public String getMsg() {
return msg;
}

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

}
}
(5)gulimall-product 的 com.atguigu.gulimall.product.controller.SpuInfoController 新增上架方法 upSpuForSearch
/**
* 商品上架
* @param spuId
* @return
*/
///product/spuinfo/{spuId}/up
@PostMapping("{spuId}/up")
public R upSpuForSearch(@PathVariable("spuId") Long spuId){
spuInfoService.upSpuForSearch(spuId);
return R.ok();
}

(6)gulimall-product 的 com.atguigu.gulimall.product.service.impl.SpuInfoServiceImpl 新增接口和實現類方法 upSpuForSearch
/**
* 商品上架
* @param spuId
*/
@Override
public void upSpuForSearch(Long spuId) {
//1、查出當前spuId對應的所有sku信息,品牌的名字
List<SkuInfoEntity> skuInfoEntities = skuInfoService.getSkusBySpuId(spuId);

//TODO 4、查出當前sku的所有可以被用來檢索的規格屬性
List<ProductAttrValueEntity> baseAttrs = attrValueService.baseAttrlistforspu(spuId);

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

List<Long> searchAttrIds = attrService.selectSearchAttrsIds(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());
//TODO 1、發送遠程調用,庫存系統查詢是否有庫存
Map<Long, Boolean> stockMap = null;
try {
R skuHasStock = wareFeignService.getSkuHasStocks(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);
}

//2、封裝每個sku的信息
Map<Long, Boolean> finalStockMap = stockMap;
List<SkuEsModel> collect = skuInfoEntities.stream().map(sku -> {
//組裝需要的數據
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()));
}

//TODO 2、熱度評分。0
esModel.setHotScore(0L);

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

//TODO 5、將數據發給es進行保存:gulimall-search
R r = searchFeignService.saveProductAsIndices(collect);

if (r.getCode() == 0) {
//遠程調用成功
//TODO 6、修改當前spu的狀態
this.baseMapper.updateSpuStatus(spuId, ProductConstant.ProductStatusEnum.SPU_UP.getCode());
} else {
//遠程調用失敗
//TODO 7、重復調用?接口冪等性:重試機制
}
}

(7)gulimall-product 的 com.atguigu.gulimall.product.service.impl.SkuInfoServiceImpl 新增接口和實現類方法 getSkusBySpuId
/**
* 查出當前spuid對應的所有sku信息
* @param spuId
* @return
*/
@Override
public List<SkuInfoEntity> getSkusBySpuId(Long spuId) {
List<SkuInfoEntity> skus = this.list(new QueryWrapper<SkuInfoEntity>().eq("spu_id", spuId));
return skus;
}

(8)gulimall-product 的 com.atguigu.gulimall.product.service.impl.AttrServiceImpl 新增接口和實現類方法 selectSearchAttrsIds
/**
* 在指定的所有屬性集合里面,挑出檢索屬性
* @param attrIds
* @return
*/
@Override
public List<Long> selectSearchAttrsIds(List<Long> attrIds) {
/**
* SELECT attr_id FROM pms_attr WHERE attr_id in (?) AND search_type=1
*/
return baseMapper.selectSearchAttrsIds(attrIds);
}

(9)gulimall-product 的 com.atguigu.gulimall.product.dao.AttrDao 新增接口 selectSearchAttrsIds
/**
* 在指定的所有屬性集合里面,挑出檢索屬性
* @param attrIds
* @return
*/
List<Long> selectSearchAttrsIds(@Param("attrIds") List<Long> attrIds);

(10)gulimall-product 的 AttrDao.xml 新增方法 selectSearchAttrsIds
<!--在指定的所有屬性集合里面,挑出檢索屬性-->
<select id="selectSearchAttrsIds" 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>

(11)gulimall-product 創建遠程調用文件 com.atguigu.gulimall.product.feign.WareFeignService
package com.atguigu.gulimall.product.feign;

import com.atguigu.common.to.SkuHasStockVo;
import com.atguigu.common.utils.R;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;

import java.util.List;

@Component
@FeignClient("gulimall-ware")
public interface WareFeignService {

/**
* 1、R設計的時候可以加上泛型
* 2、直接返回我們想要的結果
* 3、自己封裝解析結果
* @return
*/
@PostMapping("/ware/waresku/hasStock")
R getSkuHasStocks(@RequestBody List<Long> skuIds);
}

(12)gulimall-ware 編寫遠程服務查詢庫存接口
com.atguigu.gulimall.ware.controller.WareSkuController 新增方法 getSkuHasStocks
/**
* 查詢sku是否有庫存
* 返回skuId 和 stock庫存量
*/
@PostMapping("/hasStock")
public R getSkuHasStocks(@RequestBody List<Long> SkuIds){
List<SkuHasStockVo> vos = wareSkuService.getSkuHasStock(SkuIds);
return R.ok().setData(vos);
}

com.atguigu.gulimall.ware.service.impl.WareSkuServiceImpl 新增接口和實現方法
/**
* 查詢sku是否有庫存
* @param skuIds
* @return
*/
@Override
public List<SkuHasStockVo> getSkuHasStock(List<Long> skuIds) {
List<SkuHasStockVo> collect = skuIds.stream().map(skuId -> {
SkuHasStockVo vo = new SkuHasStockVo();
//查看當前sku的總庫存量
//SELECT SUM(stock-stock_locked) FROM wms_ware_sku WHERE sku_id=1
Long count = baseMapper.getSkuStock(skuId);
vo.setHasStock(count==null?false:count>0);
vo.setSkuId(skuId);
return vo;
}).collect(Collectors.toList());
System.out.println(collect);
return collect;
}

com.atguigu.gulimall.ware.dao.WareSkuDao 新增方法 getSkuStock
/**
* 查看當前sku的總庫存量
* @param skuId
* @return
*/
Long getSkuStock(@Param("skuId") Long skuId);

WareSkuDao.xml 新增xml實現方法
<!--查看當前sku的總庫存量-->
<select id="getSkuStock" resultType="java.lang.Long">
SELECT SUM(stock-stock_locked) FROM wms_ware_sku WHERE sku_id=#{skuId}
</select>
(13)gulimall-product 創建遠程調用文件 com.atguigu.gulimall.product.feign.SearchFeignService
package com.atguigu.gulimall.product.feign;

import com.atguigu.common.to.es.SkuEsModel;
import com.atguigu.common.utils.R;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;

import java.util.List;

@Component
@FeignClient("gulimall-search")
public interface SearchFeignService {

@PostMapping("/search/save/product")
R saveProductAsIndices(@RequestBody List<SkuEsModel> skuEsModels);
}
(14)gulimall-search 編寫遠程服務es上架商品接口

新增文件 com.atguigu.gulimall.search.controller.ElasticSaveController
package com.atguigu.gulimall.search.controller;

import com.atguigu.common.exception.BizCodeEnum;
import com.atguigu.common.to.es.SkuEsModel;
import com.atguigu.common.utils.R;
import com.atguigu.gulimall.search.service.ProductSaveService;
import lombok.extern.slf4j.Slf4j;
import org.apache.tomcat.jni.BIOCallback;
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 java.util.List;

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

@Autowired
ProductSaveService productSaveService;

//上架商品
@PostMapping("/product")
public R saveProductAsIndices(@RequestBody List<SkuEsModel> skuEsModels){
boolean b = false;
try {
System.out.println(skuEsModels);
b = productSaveService.productStatusUp(skuEsModels);
System.out.println(b);
}catch (Exception e){
e.printStackTrace();
log.error("ElasticSaveController商品上架錯誤:{}",e);
return R.error(BizCodeEnum.PRODUCT_UP_EXCEPTION.getCode(), BizCodeEnum.PRODUCT_UP_EXCEPTION.getMessage());
}
if(!b){
return R.ok();
}else{
return R.error(BizCodeEnum.PRODUCT_UP_EXCEPTION.getCode(), BizCodeEnum.PRODUCT_UP_EXCEPTION.getMessage());
}

}
}

新增接口和實現類文件 com.atguigu.gulimall.search.service.impl.ProductSaveServiceImpl
package com.atguigu.gulimall.search.service.impl;

import com.alibaba.fastjson.JSON;
import com.atguigu.common.to.es.SkuEsModel;
import com.atguigu.gulimall.search.config.GulimallElasticSearchConfig;
import com.atguigu.gulimall.search.constant.EsConstant;
import com.atguigu.gulimall.search.service.ProductSaveService;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.action.bulk.BulkItemResponse;
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.stereotype.Service;

import javax.annotation.Resource;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

@Slf4j
@Service
public class ProductSaveServiceImpl implements ProductSaveService {

@Resource
private RestHighLevelClient restHighLevelClient;


/**
* 上架商品
* @param skuEsModels
*/
@Override
public boolean productStatusUp(List<SkuEsModel> skuEsModels) throws IOException {
// 1.給ES建立一個索引 product
BulkRequest bulkRequest = new BulkRequest();
// 2.構造保存請求
for (SkuEsModel esModel : skuEsModels) {
// 設置索引
IndexRequest indexRequest = new IndexRequest(EsConstant.PRODUCT_INDEX);
// 設置索引id
indexRequest.id(esModel.getSkuId().toString());
String jsonString = JSON.toJSONString(esModel);
indexRequest.source(jsonString, XContentType.JSON);
// add
bulkRequest.add(indexRequest);
}
// bulk批量保存
BulkResponse bulk = restHighLevelClient.bulk(bulkRequest, GulimallElasticSearchConfig.COMMON_OPTIONS);
// TODO 是否擁有錯誤
boolean hasFailures = bulk.hasFailures();
List<String> collect = Arrays.stream(bulk.getItems()).map(item -> item.getId()).collect(Collectors.toList());
log.info("商品上架完成:{},返回數據",collect,bulk.toString());
return hasFailures;
}
}

總結

以上是生活随笔為你收集整理的谷粒商城分布式高级(三)—— 商城业务(商品上架)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。