栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

16.商品业务-商品上架

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

16.商品业务-商品上架

文章目录
  • 1 sku在es中的存储模型分析
    • 1.1 商品Mapping
  • 2 nested数据类型
  • 3 商品上架服务
    • 3.1 远程调用查询库存服务
    • 3.2 实现es存储业务
      • 3.2.1 远程调用接口
    • 3.3 商品上传业务逻辑
    • 3.4 测试

1 sku在es中的存储模型分析

商品上架

上架的商品才可以在网站展示

上架的商品需要可以被检索

1.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的数据,传输阻塞时间会很长,业务更加无法继续。

所以,我们如下设计,这样才是文档区别于关系型数据库的地方,宽表设计,不能去考虑数据库范式。

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
      },
      "attr": {
        "type": "nested",
        "properties": {
          "attrId": {
            "type": "long"
          },
          "attrName": {
            "type": "keyword",
            "index": false,
            "doc_values": false
          },
          "attrValue": {
            "type": "keyword"
          }
        }
      }
    }
  }
}
2 nested数据类型

Es会将存入的数组进行扁平化处理

例如:存入数组

PUT my_index/_doc/1
{
  "group": "fans",
  "user": [
    {
      "first": "John",
      "last": "Smith"
    },
    {
      "first": "Alice",
      "last": "White"
    }
  ]
}

将会被扁平化为:

{
  "group": "fans",
  "user.first": ["Alice", "John"],
  "user.last": ["Smith", "White"]
}

设置为nested类型可以防止扁平化处理

PUT my_index
{
  "mappings": {
    "properties": {
      "user": {
        "type": "nested"
      }
    }
  }
}
3 商品上架服务 3.1 远程调用查询库存服务

1、创建SkuHasStockVo,路径com/atguigu/common/to/es/SkuHasStockVo.java

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

2、创建getSkuHasStock,路径:com/atguigu/gulimall/ware/controller/WareSkuController.java

// 查询sku是否有库存
@RequestMapping("/hasstock")
public R getSkuHasStock(@RequestBody List skuIds){

    List vos = wareSkuService.getSkuHasStock(skuIds);

    return R.ok().put("data", vos);
}

3、实现getSkuHasStock方法

@Override
public List getSkuHasStock(List skuIds) {

    List collect = skuIds.stream().map(skuId -> {
        SkuHasStockVo vo = new SkuHasStockVo();
        // 查询当前sku的总库存量
        Long count = baseMapper.getSkuStock(skuId);
        vo.setSkuId(skuId);
        vo.setHasStock(count != null && count > 0);
        return vo;
    }).collect(Collectors.toList());
    return collect;
}

4、实现getSkuStock方法,接口上记得加上@Param注解


    SELECT SUM(stock-stock_locked) FROM `wms_ware_sku` WHERe sku_id=#{skuId}

5、创建WareFeignService远程调用接口,路径:com/atguigu/gulimall/product/feign/WareFeignService.java

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

    @PostMapping("/ware/waresku/hasstock")
    R getSkuHasStock(@RequestBody List skuIds);
}
3.2 实现es存储业务

1、创建ProductSaveService,路径:com/atguigu/gulimall/search/service/ProductSaveService.java

2、建立常量类EsConstant,路径:com/atguigu/gulimall/search/constant/EsConstant.java

public class EsConstant {
    public static final String PRODUCT_INDEX = "product"; // sku数据在es中的索引
}

3、添加异常枚举,路径:com/atguigu/common/exception/BizCodeEnume.java

public enum BizCodeEnume {
    UNKNOW_EXCEPTION(10000,"系统未知异常"),
    VAILD_EXCEPTION(10001,"参数格式校验失败"),
    PRODUCT_UP_EXCEPTION(11000, "商品上架异常");


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

    public int getCode() {
        return code;
    }

    public String getMsg() {
        return msg;
    }
}

4、创建ElasticSaveController,添加productStatusUp方法,路径:com/atguigu/gulimall/search/controller/ElasticSaveController.java

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

    @Autowired
    ProductSaveService productSaveService;

    @PostMapping("/product")
    public R productStatusUp(@RequestBody List skuEsModels) {
        boolean b = false;
        try {
            b = productSaveService.productStatusUp(skuEsModels);
        } catch (Exception 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());
        }
    }
}

5、建立映射关系,在kibana中执行下列代码

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
      },
      "attr": {
        "type": "nested",
        "properties": {
          "attrId": {
            "type": "long"
          },
          "attrName": {
            "type": "keyword",
            "index": false,
            "doc_values": false
          },
          "attrValue": {
            "type": "keyword"
          }
        }
      }
    }
  }
}

6、实现productStatusUp方法,路径:com/atguigu/gulimall/search/service/impl/ProductSaveServiceImpl.java

@Autowired
RestHighLevelClient restHighLevelClient;

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

    // 1. 给es中建立索引,product,建立好映射关系

    // 2. 保存数据
    BulkRequest bulkRequest = new BulkRequest();
    for (SkuEsModel model : skuEsModels) {
        // 构造保存请求
        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 = restHighLevelClient.bulk(bulkRequest, GulimallElasticSearchConfig.COMMON_OPTIONS);

    boolean b = bulk.hasFailures();
    if (b) {
        bulk.buildFailureMessage();
    } else {
        List collect = Arrays.stream(bulk.getItems()).map(BulkItemResponse::getId).collect(Collectors.toList());
        log.info("商品上架完成: {}", collect);
    }
    return !b;
}
3.2.1 远程调用接口

1、创建SearchFeignService接口,路径:com/atguigu/gulimall/product/feign/SearchFeignService.java

@FeignClient("gulimall-search")
public interface SearchFeignService {
    
    @PostMapping("/search/save/product")
    R productStatusUp(@RequestBody List skuEsModels);
}
3.3 商品上传业务逻辑

1、添加状态码,路径:com/atguigu/common/constant/ProductConstant.java

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  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 String getMsg() {
            return msg;
        }
    }
}

2、在product模块下的SpuInfoController里创建spuUp方法

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

    return R.ok();
}

3、创建SkuEsModel,路径:com/atguigu/common/to/es/SkuEsModel.java

@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;

    @Data
    public static class Attrs{

        private Long attrId;

        private String attrName;

        private String attrValue;
    }
}

4、实现up方法

@Autowired
BrandService brandService;

@Autowired
CategoryService categoryService;

@Autowired
WareFeignService wareFeignService;

@Autowired
SearchFeignService searchFeignService;

@Override
@Transactional
public void up(Long spuId) {
    // 1. 查出当前spuId对应的所有sku信息,品牌的名字
    List skus = skuInfoService.getSkusBySpuId(spuId);

    // 4. 查询当前sku的所有可以被用来检索的规格属性
    List productAttrValueEntities = productAttrValueService.baseAttrListForSpu(spuId);

    // 4.1. 获取所有对应SPUId的attrId
    List attrIds = productAttrValueEntities.stream().map(ProductAttrValueEntity::getAttrId).collect(Collectors.toList());

    // 4.2. 在表pms_attr查找所有对应attr_id的实体
    Collection attrEntities = attrService.listByIds(attrIds);

    // 4.3. 过滤掉所有search_type为0的实体,并封装为SkuEsModel.Attrs
    List skuEsModelAttrs = attrEntities.stream().filter(item -> item.getSearchType() == 1).map(item -> {
        SkuEsModel.Attrs attrs = new SkuEsModel.Attrs();
        BeanUtils.copyProperties(item, attrs);
        attrs.setAttrValue(item.getValueSelect());
        return attrs;
    }).collect(Collectors.toList());

    

    // 获取哦skus中的所有skuId作为集合
    List skuIdList = skus.stream().map(SkuInfoEntity::getSkuId).collect(Collectors.toList());

    // TODO 发送远程调用,库存系统查询是否有库存
    Map stockMap = null;
    try {
        R r = wareFeignService.getSkuHasStock(skuIdList);
        Object data = r.get("data");

        // 需要导入fastjson包
        String s = JSON.toJSONString(data);
        TypeReference> typeReference = new TypeReference>() {};
        // com.alibaba.fastjson.TypeReference;
        List skuHasStockVos = JSON.parseObject(s, typeReference);

        // 将skuHasStockVos映射为map,skuId为键,HasStock为值
        stockMap = skuHasStockVos.stream().collect(Collectors.toMap(SkuHasStockVo::getSkuId, SkuHasStockVo::getHasStock));

    } catch (Exception e) {
        log.error("库存查询服务出现异常,原因{}", e);
    }

    // 2. 封装每个sku的信息
    Map finalStockMap = stockMap;
    List collect = skus.stream().map(sku -> {
        // 组装需要的数据
        SkuEsModel skuEsModel = new SkuEsModel();
        BeanUtils.copyProperties(sku, skuEsModel);

        // 手动赋值其他属性
        // TODO 热度评分
        skuEsModel.setHotScore(0L);
        skuEsModel.setSkuPrice(sku.getPrice());
        skuEsModel.setSkuImg(sku.getSkuDefaultImg());

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

        // TODO 查询品牌和分类的名字信息
        BrandEntity brand = brandService.getById(skuEsModel.getBrandId());
        skuEsModel.setBrandName(brand.getName());
        skuEsModel.setBrandImg(brand.getLogo());

        CategoryEntity category = categoryService.getById(skuEsModel.getCatalogId());
        skuEsModel.setCatalogName(category.getName());

        // 设置检索属性
        skuEsModel.setAttrs(skuEsModelAttrs);

        return skuEsModel;

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

    // TODO 将数据发送给es进行保存
    R r = searchFeignService.productStatusUp(collect);

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

5、实现getSkusBySpuId方法

@Override
public List getSkusBySpuId(Long spuId) {

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

6、实现updateSpuStatus方法,接口上记得加@Param注解






   
    
        
        
        
        
        
        
        
        
        
    
    
        UPDATE `pms_spu_info` SET publish_status=#{code},update_time=NOW() WHERe id=#{spuId}
    



3.4 测试

记得先将数据表pms_attr里的属性设置为可被检索

点击上架

到kibana页面查询索引

GET product/_search

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/856878.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号