谷粒商城-商城业务(商品上架)

商品上架

在商城中搜索商品,只能搜索到已上架的商品。

而商品上架时,需要把数据也同步到elasticsearch中以供搜索。

但是肯定不能把完整的数据全部存到es中,因为es中的数据是存储在内存中的,就算es是分布式的,理论上可以存储非常多的数据,但是内存产品终究是比硬盘贵的。所以考虑到经济效益,我们是要取某些数据存到es中,而不是完整数据都存进去。

哪些数据存储在es中?

sku在es中存储模型分析

es中应存储能够被检索的条件。

image-20221010140546355

比如这个商城能够用于检索的条件就有:

分类品牌综合销量价格区间CPU型号,运行内存,机身内存(这些应该是每种分类特有的属性)、等等

分析:商品上架在 es 中是存 sku 还是 spu?

答:搜索一般是搜索sku的信息,因为一个spu中包含多个sku,每个sku的情况都不一样,搜索的时候可能一个spu里的某几个sku符合条件,几个sku不符合条件。(我的理解)

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

text类型和keyword类型的区别:

text:会分词,然后进行索引

​ 支持模糊、精确查询

​ 不支持聚合

keyword:不进行分词,直接索引

​ 支持模糊、精确查询

​ 支持聚合

字段含义
index默认 true,如果为 false,表示该字段不会被索引,但是检索结果里面有,但字段本身不能
doc_values默认 true,设置为 false,表示不可以做排序、聚合以及脚本操作,这样更节省磁盘空间。还可以通过设定 doc_values 为 true,index 为 false 来让字段不能被搜索但可以用于排序、聚合以及脚本操作

es的扁平化处理

image-20221011114555064

字段含义
nested嵌入式的字段,就可以不使用es的扁平化处理了。

构造数据实体类

由于这些数据是要在多个服务之间进行传输的,所以定义为to类型。为了方便每一个服务调用,我们直接创建在common模块中。

// 根据分析出来的模型构建
@Data
public class SkuEsTo {
    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<Attr> attrs;

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

商品上架功能实现

具体步骤

  1. 获取spu下的所有所属sku
  2. 获取每一个sku中的需要使用的信息
    • 库存信息
    • 可检索属性
  3. 组装SkuEsTo的数据到集合中。
  4. List<SkuEsTo>发送给es,让es保存,达到上架的状态。
  5. 修改数据库中spu的发布状态(1-上架)

具体实现

  1. 因为可检索属性是每一个sku都相同的,所以能在一开始直接获取,不要在循环内获取。
  2. 库存信息需要调用其他服务,这种操作速度应该会比较慢,所以也在循环外一次性获取,需要新建一个存储库存信息的TO实体类。

以下列出部分代码。(因为代码太多了,只列出一些关键的)

  1. 商品上架的逻辑处理层
@Override
public void up(Long spuId) {
    // 1.获取该spu的所有可检索属性。
    // attrs;因为spu的属性是每个sku共享的,用空间换时间,所以在每一个sku里面都存一遍!!过滤出需要检索的属性,0为不需要检索,1为需要检索
    List<ProductAttrValueEntity> productAttrValueList = productAttrValueService.getAttrsBySpuId(spuId);
    // 获取spu下所有属性id
    List<Long> attrIds = productAttrValueList.stream().map(ProductAttrValueEntity::getAttrId).collect(Collectors.toList());
    // 得到spu下所有的可检索属性id
    List<Long> searchAttrIds = attrService.selectSearchAttrIds(attrIds);
    // 过滤出所有可检索的属性信息
    List<SkuEsTo.Attr> searchAttrList = productAttrValueList.stream()
        .filter(attr -> searchAttrIds.contains(attr.getAttrId()))
        // 组装skuEsTo中的attrs
        .map(searchAttrItem -> {
            SkuEsTo.Attr attr = new SkuEsTo.Attr();
            attr.setAttrId(searchAttrItem.getAttrId());
            attr.setAttrName(searchAttrItem.getAttrName());
            attr.setAttrValue(searchAttrItem.getAttrValue());
            return attr;
        }).collect(Collectors.toList());

    // 2.获取sku库存信息
    List<SkuInfoEntity> skuInfoEntityList = skuInfoService.getInfoBySpuId(spuId);
    // 得到所有的skuId,调用远程服务,获取库存信息
    List<Long> skuIds = skuInfoEntityList.stream().map(SkuInfoEntity::getSkuId).collect(Collectors.toList());
    Object skuStockInfoList = null;
    try {
        R r = wareFeignService.hasStock(skuIds);
        skuStockInfoList = r.get("skuStockToList");
    }catch (Exception e){
        log.error("库存服务查询失败,原因:{}", e.getMessage());
    }

    Object finalSkuStockInfoList = skuStockInfoList;
    List<SkuEsTo> esProductList = skuInfoEntityList.stream().map(skuInfoEntity -> {
        SkuEsTo skuEsTo = new SkuEsTo();
        BeanUtils.copyProperties(skuInfoEntity, skuEsTo);

        // skuPrice;skuImg;
        skuEsTo.setSkuPrice(skuInfoEntity.getPrice());
        skuEsTo.setSkuImg(skuInfoEntity.getSkuDefaultImg());

        // hasStock;hotScore;
        if (finalSkuStockInfoList != null){
            // 做一个类型转换,把list中的数据放到map中,map<skuId, 是否有库存>
            Map<Long, Boolean> stockInfoMap = TypeConversion.objListToMapOfLongBoolean(finalSkuStockInfoList);
            Boolean hasStock = stockInfoMap.get(skuInfoEntity.getSkuId());
            skuEsTo.setHasStock(hasStock);
        } else {
            skuEsTo.setHasStock(true);
        }

        // todo:热度评分,默认新上架的商品评分为0,给钱就高分
        skuEsTo.setHotScore(0L);

        // brandName;brandImg;
        BrandEntity brandEntity = brandService.getById(skuInfoEntity.getBrandId());
        skuEsTo.setBrandName(brandEntity.getName());
        skuEsTo.setBrandImg(brandEntity.getLogo());

        // catalogName;
        CategoryEntity categoryEntity = categoryService.getById(skuInfoEntity.getCatalogId());
        skuEsTo.setCatalogName(categoryEntity.getName());

        // attrs;
        skuEsTo.setAttrs(searchAttrList);
        return skuEsTo;
    }).collect(Collectors.toList());

    // 3.将数据发给es,让es做一个保存
    R r = searchFeignService.productStatusUp(esProductList);
    if (r.getCode() == 0){
        boolean status = (boolean) r.get("status");
        // 4.上架成功,修改数据库中的上架状态
        spuInfoDao.updateSpuPublishStatus(spuId, ProductConstant.StatusEnum.SPU_UP.getCode());
    }else {
        log.error("商品上架失败");
        // TODO: 重复调用?接口幂等性?重试机制?
    }
}
  1. 在common模块中再新建一个存储库存信息的TO实体类。
@Data
public class SkuStockTo {
    /**
     * skuId
     */
    private Long skuId;
    /**
     * 是否有库存
     */
    private Boolean hasStock;
}
  1. 批量将数据存储到es
@Override
public boolean productStatusUp(List<SkuEsTo> skuEsToList) {
    boolean success = true;
    // 1.给es中建立索引。product,建立好映射关系。
    // 2.在es中保存这些数据。数据多,不可能一个一个组装,使用bulk批量处理
    // BulkRequest bulkRequest, RequestOptions options
    BulkRequest bulkRequest = new BulkRequest();
    for (SkuEsTo skuEsTo : skuEsToList) {
        // 3.组装数据
        IndexRequest request = new IndexRequest(SearchConstant.IndexEnum.PRODUCT_INDEX.getIndex());
        // 指定id
        request.id(String.valueOf(skuEsTo.getSkuId()));
        String skuEsToJson = JSON.toJSONString(skuEsTo);
        // 指定json数据
        System.out.println(skuEsTo);
        request.source(skuEsToJson, XContentType.JSON);
        bulkRequest.add(request);
    }
    try {
        // 4.执行
        BulkResponse response = client.bulk(bulkRequest, GulimallElasticsearchConfig.COMMON_OPTIONS);
        if (response.hasFailures()){
            success = false;
            List<String> failureList = Arrays.stream(response.getItems()).filter(item -> item.getFailure() != null).map(BulkItemResponse::getId).collect(Collectors.toList());
            log.error("批量操作错误项:{}", failureList);
        }
    } catch (IOException e) {
        log.error("批量操作遇到错误,原因:{}", e.getMessage());
    }
    return success;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
网上图书商城是一个基于SSM(Spring+SpringMVC+MyBatis)框架开发的应用程序,使用MySQL作为数据库。 SSM框架是目前比较流行的Java Web开发框架,它集成了Spring、SpringMVC和MyBatis三个轻量级框架的优点,可以提供高效、稳定和可扩展的应用程序。 MySQL是一种关系型数据库管理系统,具有开源、免费和易于使用的特点。它支持高并发、高可用性和灵活的数据存储。 在网上图书商城中,SSM框架负责处理请求和相应的控制层(Controller),通过请求映射和处理器适配器来调用服务层(Service),对数据进行处理和业务逻辑的实现,然后将数据传递给持久层(Dao)。 持久层使用MyBatis框架来与MySQL数据库进行交互,包括插入、更新、删除和查询数据等操作。MyBatis是一个基于Java的持久层框架,它可以将Java对象映射到数据库表,通过XML文件或注解方式进行配置。 使用MySQL作为数据库,可以对图书信息、用户信息、订单信息等进行存储和管理。可以创建表来存储图书的属性(例如书名、作者、价格等),用户的属性(例如用户名、密码、地址等),以及订单的属性(例如订单号、购买日期、付款方式等)。 通过SSM框架和MySQL数据库的结合,网上图书商城可以实现用户注册、登录、图书浏览、购物车管理、订单生成和支付等功能,提供给用户一个方便快捷的网上购书平台。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值