微服务实战——ElasticSearch(保存)

news2024/10/3 19:42:21

商品上架——ElasticSearch(保存)

0.商城架构图

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 单独一个索引可能涉及的问题。
    1. 检索商品的名字,如“手机”,对应的 spu 有很多,我们要分析出这些 spu 的所有关联属性,再做一次查询,就必须将所有 spu_id 都发出去。假设有 1 万个数据,数据传输一次就10000*4=4MB;并发情况下假设 1000 检索请求,那就是 4GB 的数据,传输阻塞时间会很长,业务更加无法继续。
    2. 所以,我们如下设计,这样才是文档区别于关系型数据库的地方,宽表设计,不能去考虑数据库范式。
    3. 向ES添加商品属性映射

向ES添加商品属性映射

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"
                    }
                }
            }
        }
    }
}

index :

默认 true ,如果为 false ,表示该字段不会被索引,但是检索结果里面有,但字段本身不能

当做检索条件。

doc_values :

默认 true ,设置为 false ,表示不可以做排序、聚合以及脚本操作,这样更节省磁盘空间。

还可以通过设定 doc_values 为 true , index 为 false 来让字段不能被搜索但可以用于排序、聚合以及脚本操作:

spu在es中的存储模型分析总结

如果每个sku都存储规格参数,会有冗余存储,因为每个spu对应的sku的规格参数都一样。但是如果将规格参数单独建立索引会出现检索时出现大量数据传输的问题,会阻塞网络因此我们选用第一种存储模型,以空间换时间。

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.ES中的数组扁平化

关于“nested”,Nested datatype | Elasticsearch Guide [7.6] | Elastic

ES中数组的扁平化处理:

对象数组的扁平化:

内部对象字段数组的工作方式与您预期的不同。Lucene没有内部对象的概念,所以Elasticsearch将对象层次结构简化为字段名和值的简单列表。例如,以下文件:

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" ]
}

查询my_index的映射

GET my_index/_mapping
{
  "my_index" : {
    "mappings" : {
      "properties" : {
        "group" : {
          "type" : "text",
          "fields" : {
            "keyword" : {
              "type" : "keyword",
              "ignore_above" : 256
            }
          }
        },
        "user" : {
          "properties" : {
            "first" : {
              "type" : "text",
              "fields" : {
                "keyword" : {
                  "type" : "keyword",
                  "ignore_above" : 256
                }
              }
            },
            "last" : {
              "type" : "text",
              "fields" : {
                "keyword" : {
                  "type" : "keyword",
                  "ignore_above" : 256
                }
              }
            }
          }
        }
      }
    }
  }
}

user.first和user.last字段被平铺成多值字段,alice和white之间的关联也丢失了。在查询alice和smith时,这个文档将将发生错误的匹配

GET my_index/_search
{
  "query": {
    "bool": {
      "must": [
        { "match": { "user.first": "Alice" }},
        { "match": { "user.last":  "Smith" }}
      ]
    }
  }
}

所想要的只是user.first="Alice",user.last="Smith",本身是查询不到的,但是却查询出来了两条结果:

{
  "took" : 49,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 0.5753642,
    "hits" : [
      {
        "_index" : "my_index",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 0.5753642,
        "_source" : {
          "group" : "fans",
          "user" : [
            {
              "first" : "John",
              "last" : "Smith"
            },
            {
              "first" : "Alice",
              "last" : "White"
            }
          ]
        }
      }
    ]
  }
}

删除“my_index”索引

DELETE my_index

重新创建my_index索引

PUT my_index
{
  "mappings": {
    "properties": {
      "user": {
        "type": "nested" 
      }
    }
  }
}

重新插入数据

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

再次查询user.first="Alice",user.last="Smith"时,查询不到数据

GET my_index/_search
{
  "query": {
    "bool": {
      "must": [
        { "match": { "user.first": "Alice" }},
        { "match": { "user.last":  "Smith" }}
      ]
    }
  }
}

查询结果:

{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 0,
      "relation" : "eq"
    },
    "max_score" : null,
    "hits" : [ ]
  }
}

5.商品上架接口实现

商品上架需要在es中保存spu信息并更新spu的状态信息,由于SpuInfoEntity与索引的数据模型并不对应,所以我们要建立专门的vo进行数据传输

1、商品上架接口

接口文档:商品系统 - 20、商品上架

POST /product/spuinfo/{spuId}/up

请求参数

分页数据

响应数据

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

功能效果

新增“com.cwh.common.to.es.SkuEsModel”类,代码如下:

package com.cwh.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;
    @Data
    public static class Attr{
        private Long attrId;
        private String attrName;
        private String attrValue;
    }
}

编写商品上架的接口

修改“com.cwh.gulimall.product.controller.SpuInfoController”类,代码如下:

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

    return R.ok();
}

修改“com.cwh.gulimall.product.service.SpuInfoService”类,代码如下:

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

由于每个spu对应的各个sku的规格参数相同,因此我们要将查询规格参数提前,只查询一次

修改“com.cwh.gulimall.product.service.impl.SpuInfoServiceImpl”类,代码如下:

@Override
public void up(Long spuId) {
    // 1、查出当前spuId对应的sku信息,品牌名字
    List<SkuInfoEntity> skus = skuInfoService.getSkuBySpuId(spuId);
    List<Long> skuIdList = skus.stream().map(SkuInfoEntity::getSkuId).collect(Collectors.toList());

    // 2.1、发送远程调用,库存系统查询是否有库存
    Map<Long, Boolean> stockMap = null;
    try {
        R r = wareFeignService.getSkusHasStock(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.4、查询当前sku的所有可以被用来检索的规格属性
    List<ProductAttrValueEntity> baseAttrs = productAttrValueService.baseAttrListForSpu(spuId);
    List<Long> attrIds = baseAttrs.stream().map(attr -> attr.getAttrId()).collect(Collectors.toList());
    List<Long> searchAttrIds = attrService.selectSearchAttrs(attrIds);
    Set<Long> idSet = new HashSet<>(searchAttrIds);
    List<SkuEsModel.Attrs> attrsList = baseAttrs.stream().filter(item -> idSet.contains(item.getAttrId())).map(item -> {
        SkuEsModel.Attrs attrs1 = new SkuEsModel.Attrs();
        BeanUtils.copyProperties(item, attrs1);
        return attrs1;
    }).collect(Collectors.toList());

    // 2、封装每个sku的信息
    Map<Long, Boolean> finalStockMap = stockMap;
    List<SkuEsModel> upProducts = skus.stream().map(sku -> {
        // 组装需要的数据
        SkuEsModel esModel = new SkuEsModel();
        BeanUtils.copyProperties(sku, esModel);
        esModel.setSkuPrice(sku.getPrice());
        esModel.setSkuImg(sku.getSkuDefaultImg());
        // 2.1、是否有库存 hasStock,hotScore
        if (finalStockMap == null) {
            esModel.setHasStock(true);
        } else {
            esModel.setHasStock(finalStockMap.get(sku.getSkuId()));
        }
        // 2.2、热度评分。0
        esModel.setHotScore(0L);
        // 2.3、查询品牌和分类的名字信息
        BrandEntity brand = brandService.getById(esModel.getBrandId());
        esModel.setBrandName(brand.getName());
        esModel.setBrandImg(brand.getLogo());
        CategoryEntity category = categoryService.getById(esModel.getCatalogId());
        esModel.setCatalogName(category.getName());
        // 2.4、设置检索属性
        esModel.setAttrs(attrsList);
        System.out.println("======================esModel" + esModel);
        return esModel;
    }).collect(Collectors.toList());

    // 3、将数据发送给es进行保存
    R r = searchFeignService.productStatusUp(upProducts);
    System.out.println("=========================" + r);
    if (r.getCode() == 0) {
        //远程调用成功
        // 3.1、修改当前spu的状态
        System.out.println("修改当前spu的状态");
        baseMapper.updateSpuStatus(spuId, ProductConstant.StatusEnum.SPU_UP.getCode());
    } else {
        // 远程调用失败
        // TODO 3.2、重复调用?接口幂等性;重试机制
        /**
         * Feign调用流程:
         * 1、构造请求数据,将对象转为json
         *      RequestTemplate template = buildTemplateFromArgs.create(argv);
         * 2、发送请求进行执行(执行成功会解码响应数据)
         *      executeAndDecode(template)
         * 3、执行请求会有重试机制
         *      while(true){
         *          try{
         *              executeAndDecode(template);
         *          }catch(){
         *              retryer.continueOrPropagate(e);
         *              throw ex;
         *              continue;
         *          }
         *      }
         */
    }
}
2、查出当前spuId对应的sku信息,品牌名字

修改“com.cwh.gulimall.product.service.SkuInfoService”类,代码如下:

/**
 * 查出当前spuId对应的sku信息
 *
 * @param spuId
 * @return
 */
List<SkuInfoEntity> getSkuBySpuId(Long spuId);

修改“com.cwh.gulimall.product.service.impl.SkuInfoServiceImpl”类,代码如下:

@Override
public List<SkuInfoEntity> getSkuBySpuId(Long spuId) {
    List<SkuInfoEntity> list = this.list(new QueryWrapper<SkuInfoEntity>().eq("spu_id", spuId));
    return list;
}
3、封装每个sku的信息
3.1、发送远程调用,库存系统查询是否有库存

修改“com.cwh.gulimall.product.feign.WareFeignService”类,代码如下:

package com.cwh.gulimall.product.feign;
import com.cwh.common.utils.R;
import com.cwh.gulimall.product.vo.SkuHasStockVo;
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("gulimall-ware")
public interface WareFeignService {
    /**
 * 1、R设计的时候可以加上泛型
 * 2、直接返回我们想要的结果
 * 3、自己封装返回结果
 * @param skuIds
 * @return
 */
    @PostMapping("/ware/waresku/hasStock")
    R getSkusHasStock(@RequestBody List<Long> skuIds);
}

修改”com.cwh.gulimall.ware.controller.WareSkuController”,代码如下:

/**
 * 查询sku是否有库存
 */
@PostMapping("hasStock")
public R getSkusHasStock(@RequestBody List<Long> skuIds){
    // sku_id, stock
    List<SkuHasStockVo> vos = wareSkuService.getSkusHasStock(skuIds);
    return R.ok().setData(vos);
}

修改”com.cwh.gulimall.ware.service.WareSkuService”类,代码如下:

List getSkusHasStock(List skuIds);  

修改”com.cwh.gulimall.ware.service.WareSkuService”类,代码如下:

@Override
public List<SkuHasStockVo> getSkusHasStock(List<Long> skuIds) {
    List<SkuHasStockVo> collect = skuIds.stream().map(skuId -> {
        SkuHasStockVo vo = new SkuHasStockVo();
        // 查询sku的总库存量
        Long count = baseMapper.getSkuStock(skuId);
        vo.setSkuId(skuId);
        vo.setHasStock(count == null ? false : count > 0);
        return vo;
    }).collect(Collectors.toList());
    return collect;
}

修改“com.cwh.gulimall.ware.dao.WareSkuDao”类,代码如下

 Long getSkuStock(Long skuId);

修改“com.cwh.gulimall.ware.dao.WareSkuDao.xml”类,代码如下

<select id="getSkuStock" resultType="java.lang.Long">
  select sum(stock - stock_locked) from wms_ware_sku where sku_id=#{sku_id}
</select>

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

修改“com.cwh.gulimall.product.service.AttrService”类,代码如下:

/**
 * 在指定的所有属性集合里面,挑出检索属性
 *
 * @param attrIds
 * @return
 */
List<Long> selectSearchAttrs(List<Long> attrIds);

修改“com.cwh.gulimall.product.service.impl.AttrServiceImpl”类,代码如下:

@Override
public List<Long> selectSearchAttrs(List<Long> attrIds) {
    return baseMapper.selectSearchAttrIds(attrIds);
}
4、将数据发送给es进行保存

修改“com.cwh.gulimall.product.feign.SearchFeignService”类,代码如下:


package com.cwh.gulimall.product.feign;
import com.cwh.common.to.es.SkuEsModel;
import com.cwh.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("gulimall-search")
public interface SearchFeignService {
@PostMapping("search/save/product")
public R productStatusUp(@RequestBody List<SkuEsModel> skuEsModels);
}
4.1、创建gulimall-search

1、添加pom

<parent>
  <groupId>org.springframework.boot</groupId>

  <artifactId>spring-boot-starter-parent</artifactId>

  <version>2.3.5.RELEASE</version>

  <relativePath/> <!-- lookup parent from repository -->
</parent>

<groupId>com.cwh.gulimall</groupId>

<artifactId>gulimall-search</artifactId>

<version>0.0.1-SNAPSHOT</version>

<name>gulimall-search</name>

<description>ElasticSearch检索服务</description>


<properties>
  <java.version>1.8</java.version>

  <elasticsearch.version>7.4.2</elasticsearch.version>

</properties>


<dependencies>
  <!--导入es的rest-high-level-client-->
  <dependency>
    <groupId>org.elasticsearch.client</groupId>

    <artifactId>elasticsearch-rest-high-level-client</artifactId>

    <version>7.4.2</version>

  </dependency>


  <dependency>
    <groupId>com.auguigu.gulimall</groupId>

    <artifactId>gulimall-commom</artifactId>

    <version>0.0.1-SNAPSHOT</version>

  </dependency>


  <dependency>
    <groupId>org.springframework.boot</groupId>

    <artifactId>spring-boot-starter-web</artifactId>

  </dependency>


  <dependency>
    <groupId>org.springframework.boot</groupId>

    <artifactId>spring-boot-starter-test</artifactId>

    <scope>test</scope>

    <exclusions>
      <exclusion>
        <groupId>org.junit.vintage</groupId>

        <artifactId>junit-vintage-engine</artifactId>

      </exclusion>

    </exclusions>

  </dependency>

</dependencies>


<build>
  <plugins>
    <plugin>
      <groupId>org.springframework.boot</groupId>

      <artifactId>spring-boot-maven-plugin</artifactId>

    </plugin>

  </plugins>

</build>

2、修改yml

spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848 
spring.application.name=gulimall-search 
server.port=12000 

3、添加主配置类

package com.cwh.gulimall.search;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@EnableDiscoveryClient
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class GulimallSearchApplication {

    public static void main(String[] args) {
        SpringApplication.run(GulimallSearchApplication.class, args);
    }

}

4、配置ElaseaticSearch

修改“com.cwh.gulimall.search.config.GulimallElasticSearchConfig”类,代表如下:

package com.cwh.gulimall.search.config;
 
import org.apache.http.HttpHost;
import org.elasticsearch.client.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestOperations;
 
/**
 * 1、导入依赖
 * 2、编写配置,给容器中注入一个RestHighLevelClient
 * 3、参照API操作
 */
 
@Configuration
public class GulimallElasticSearchConfig {
 
    public static final RequestOptions COMMON_OPTIONS;
 
    static {
        RequestOptions.Builder builder = RequestOptions.DEFAULT.toBuilder();
//        builder.addHeader("Authorization", "Bearer " + TOKEN);
//        builder.setHttpAsyncResponseConsumerFactory(
//                new HttpAsyncResponseConsumerFactory
//                        .HeapBufferedResponseConsumerFactory(30 * 1024 * 1024 * 1024));
        COMMON_OPTIONS = builder.build();
    }
 
    @Bean
    public RestHighLevelClient restHighLevelClient() {
        RestClientBuilder builder = RestClient.builder(new HttpHost("192.168.43.125", 9200, "http"));
        return new RestHighLevelClient(builder);
    }
}

修改“com.cwh.gulimall.search.controller.ElasticSaveController”类,代表如下:

package com.cwh.gulimall.search.controller;
 
import com.cwh.common.constant.ProductConstant;
import com.cwh.common.exception.BizCodeEnume;
import com.cwh.common.to.es.SkuEsModel;
import com.cwh.common.utils.R;
import com.cwh.gulimall.search.service.ProductSaveService;
import lombok.extern.slf4j.Slf4j;
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;
 
@RequestMapping("/search/save")
@RestController
@Slf4j
public class ElasticSaveController {
 
    @Autowired
    ProductSaveService productSaveService;
 
    /**
     * 上架商品
     */
    @PostMapping("/product")
    public R productStatusUp(@RequestBody List<SkuEsModel> skuEsModels) {
        boolean b;
        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());
        }
 
    }
}

修改“com.cwh.gulimall.search.service.ProductSaveService”类,代表如下:

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

修改“com.cwh.gulimall.search.service.impl.ProductSaveServiceImpl”类,代表如下:

package com.cwh.gulimall.search.service.impl;
 
import com.alibaba.fastjson.JSON;
import com.cwh.common.to.es.SkuEsModel;
import com.cwh.gulimall.search.config.GulimallElasticSearchConfig;
import com.cwh.gulimall.search.constant.EsConstant;
import com.cwh.gulimall.search.service.ProductSaveService;
import lombok.extern.slf4j.Slf4j;
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.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
 
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
 
 
@Slf4j
@Service
public class ProductSaveServiceImpl implements ProductSaveService {
 
    @Autowired
    RestHighLevelClient restHighLevelClient;
 
    @Override
    public boolean productStatusUp(List<SkuEsModel> skuEsModels) throws IOException {
        // 保存到es
        // 1、给es中建立索引。product,建立好映射关系
 
        // 2、给es中保存这些数据
        // BulkRequest bulkRequest, RequestOptions options
        BulkRequest bulkRequest = new BulkRequest();
        for (SkuEsModel model : skuEsModels) {
            // 1、构造保存请求
            IndexRequest indexRequest = new IndexRequest(EsConstant.PRODUCT_INDEX);
            indexRequest.id(model.getSkuId().toString());
            String jsonString = JSON.toJSONString(model);
            indexRequest.source(jsonString, XContentType.JSON);
            bulkRequest.add(indexRequest);
        }
        BulkResponse bulk = restHighLevelClient.bulk(bulkRequest, GulimallElasticSearchConfig.COMMON_OPTIONS);
        // TODO 如果批量错误
        boolean b = bulk.hasFailures();
        List<String> collect = Arrays.stream(bulk.getItems()).map(item -> item.getId()).collect(Collectors.toList());
        log.info("商品上架完成:{},返回数据:{}", collect, bulk.toString());
 
        return b;
    }
 
}
4.2、修改当前spu的状态

修改"com.cwh.gulimall.product.dao.SpuInfoDao"类,代码如下:

void updateSpuStatus(@Param("spuId") Long spuId,@Param("code") int code);

修改"com.cwh.gulimall.product.dao.SpuInfoDao.xml"类,代码如下:

<update id="updateSpuStatus">
  update pms_spu_info set publish_status=#{code},update_time=NOW() where id =#{spuId}
</update>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2186768.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于Springboot+Vue的小区停车场管理系统登录(含源码数据库)

1.开发环境 开发系统:Windows10/11 架构模式:MVC/前后端分离 JDK版本: Java JDK1.8 开发工具:IDEA 数据库版本: mysql5.7或8.0 数据库可视化工具: navicat 服务器: SpringBoot自带 apache tomcat 主要技术: Java,Springboot,mybatis,mysql,vue 2.视频演示地址 3.功能 在这个…

uniapp 微信发布注意事项

uniapp的微信播放不支持本地文件&#xff0c;起始微信原生语言是支持的 所以在编写uniapp代码时 要写两套逻辑 // #ifdef MP-WEIXIN 微信原封不变的自己写法 //#endif // #ifndef MP-WEIXIN 其他写法 //#endif 这样可实现 发布到微信后 微信原封不动的使用自己写…

初识算法 · 双指针(3)

目录 前言&#xff1a; 和为s的两数之和 题目解析&#xff1a; ​编辑 算法原理&#xff1a; 算法编写&#xff1a; 三数之和 题目解析 算法原理 算法编写 前言&#xff1a; 本文通过介绍和为S的两数之和&#xff0c;以及三数之和&#xff0c;对双指针算法进行深一步…

进度条(倒计时)Linux

\r回车(回到当前行开头) \n换行 行缓冲区概念 什么现象&#xff1f; 什么现象&#xff1f;&#xff1f; 什么现象&#xff1f;&#xff1f;&#xff1f; 自己总结&#xff1a; #pragma once 防止头文件被重复包含 倒计时 在main.c中&#xff0c;windows.h是不可以用的&…

Windows 环境搭建 CUDA 和 cuDNN 详细教程

CUDA CUDA&#xff08;Compute Unified Device Architecture&#xff09;是由NVIDIA公司推出的一个并行计算平台和编程模型&#xff0c;它允许开发者使用NVIDIA GPU进行通用计算&#xff08;即GPGPU&#xff09;&#xff0c;从而加速各种计算密集型任务。CUDA提供了一套基于C/C…

linux文件编程_线程

1. 基本概念 1.1. 进程与线程的概念 典型的UNIX/linux进程可以看成是只有一个控制线程&#xff0c;一个进程在同一时刻只做一件事情&#xff0c;有了多个控制线程后&#xff0c;在程序设计时可以把进程设计成在同一时刻做不止一件事&#xff0c;每个线程各自处理独立的任务。…

Web安全 - 文件上传漏洞(File Upload Vulnerability)

文章目录 OWASP 2023 TOP 10导图定义攻击场景1. 上传恶意脚本2. 目录遍历3. 覆盖现有文件4. 文件上传结合社会工程攻击 防御措施1. 文件类型验证2. 文件名限制3. 文件存储位置4. 文件权限设置5. 文件内容检测6. 访问控制7. 服务器配置 文件类型验证实现Hutool的FileTypeUtil使用…

STM32使用Keil5 在运行过程中不复位进入调试模式

一、选择Options for Target进入设置 二、选择所使用的调试器&#xff0c;这里以ST-Link为例。取消勾选Load Application at Startup 可以在进入调试模式的时候不会从新加载程序&#xff01;从而不破坏现场 三、点击Setting进入 四、取消勾选Reset after Connect 使得调试器连接…

探索 aMQTT:Python中的AI驱动MQTT库

文章目录 探索 aMQTT&#xff1a;Python中的AI驱动MQTT库背景介绍aMQTT是什么&#xff1f;如何安装aMQTT&#xff1f;简单库函数使用方法场景应用常见问题及解决方案总结 探索 aMQTT&#xff1a;Python中的AI驱动MQTT库 背景介绍 在物联网和微服务架构的浪潮中&#xff0c;MQ…

Redis:string类型

Redis&#xff1a;string类型 string命令设置与读取SETGETMSETMGET 数字操作INCRINCRBYDECRDECRBYINCRBYFLOAT 字符串操作APPENDSTRLENGETRANGESETRANGE 内部编码intembstrraw 在Redis中&#xff0c;字符串string存储的是二进制&#xff0c;以byte为单位&#xff0c;输入的二进…

ICPC-day1(NTT)

NTT经典例题 CCPC-Winter-Camp-day6-A——NTT经典例题 对于上面格式&#xff0c;如果想求出每个i的值可以使用卷积求出&#xff0c;因为阶乘j和阶乘i-j相乘的值为(i(i-j))i 补充一个二次剩余定理 P5491 【模板】二次剩余 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) //#in…

【MySQL】DML数据操作语句和基本的DQL语句

目录 一、Mysql对数据的增删改 1. 增加数据 2. 修改数据&#xff08;UPDATE语句&#xff09; 3. 删除 3.1 delete、truncate、drop区别 二、DQL语言&#xff08;重点&#xff09; 1. 单表查询 1.1 最简单的查询 1.2 从表中获取数据 1.3 字段名起别名 1.4 添加字段 1…

[20231103消息] 大模型商业化模式详解:烧钱之后如何挣钱?

距ChatGPT3.5发布已近一年&#xff0c;大模型狂热开始逐步降温&#xff1a;GPU禁运及长期烧钱的事实&#xff0c;让国内的大模型企业&#xff0c;不得不加速商业化考量。 目前&#xff0c;大模型的B端应用已经出现各种定价方法&#xff0c;包括按照时间段收费、按调用量收费以…

class 030 异或运算的骚操作

这篇文章是看了“左程云”老师在b站上的讲解之后写的, 自己感觉已经能理解了, 所以就将整个过程写下来了。 这个是“左程云”老师个人空间的b站的链接, 数据结构与算法讲的很好很好, 希望大家可以多多支持左程云老师, 真心推荐. https://space.bilibili.com/8888480?spm_id_f…

Java中Map和Set详细介绍,哈希桶的实现

大家好呀&#xff0c;前一节我们接触了二叉搜索树&#xff0c;那么紧接着&#xff0c;我们要学习一种十分重要而且也是我们在初阶数据结构中接触的最后一种数据结构—Map和Set&#xff0c;本篇博客将会详细介绍两种数据结构&#xff0c;并且针对哈希表底层实现一个哈希桶&#…

基于元神操作系统实现NTFS文件操作(三)

1. 背景 本文主要介绍DBR的读取和解析&#xff0c;并提供了基于元神操作系统的实现代码。由于解析DBR的目的是定位到NTFS磁盘分区的元文件$Root进行文件操作&#xff0c;所以只解析了少量的部分&#xff0c;其它部分可以参考相关文档进行理解。 DBR存在于磁盘分区的第一个扇区…

《数据结构》--链表【包含跳表概念】

不知道大家对链表熟悉还是陌生&#xff0c;我们秉着基础不牢&#xff0c;地动山摇的原则&#xff0c;会一点点的介绍链表的&#xff0c;毕竟链表涉及的链式存储也很重要的。在这之前&#xff0c;我们认识过顺序存储的顺序表&#xff0c;它其实就是一个特殊的数组。那链表到底是…

树莓派 AI 摄像头(Raspberry Pi AI Camera)教程

系列文章目录 前言 人们使用 Raspberry Pi 产品构建人工智能项目的时间几乎与我们生产 Raspberry Pi 的时间一样长。随着我们发布功能越来越强大的设备&#xff0c;我们能够支持的原生应用范围也在不断扩大&#xff1b;但无论哪一代产品&#xff0c;总会有一些工作负载需要外部…

SpringBoot介绍及整合Mybatis Plus

目录 SpringBoot背景及特点 SpringBoot整合Mybatis Plus SpringBoot背景及特点 SpringBoot的设计目是抛弃之前Spring、SpringMVC繁杂的配置过程&#xff0c;简化开发过程。之前的Spring框架需要大量的手动配置&#xff0c;包括XML配置文件或Java配置类&#xff0c;配置过程繁…