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

商品上架——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.mfbz.cn/a/888493.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

No package nodejs available.No package npm available.

安装nodejs时出现的报错 这个错误的原因是当前的 yum 源没有包含 Node.js 和 npm 的安装包。 解决方法 使用 NodeSource 仓库 curl -fsSL https://rpm.nodesource.com/setup_14.x | bash -运行 yum install 安装 Node.js 和 npm&#xff1a; yum install -y nodejs使用 E…

深入了解Oracle OCP认证,开启数据库专业之旅

使用Oracle数据库的公司内部&#xff0c;经常有员工们在讨论OCP认证(Oracle Certified Professional&#xff0c;Oracle认证专家)&#xff0c;这是甲骨文Oracle公司提供的一种专业认证&#xff0c;认证用于使用者在Oracle技术领域的专业知识和技能。 在这里&#xff0c;有一点…

华为、华三、锐捷网络设备的常用命令整理

华为&#xff08;Huawei&#xff09;、华三&#xff08;H3C&#xff09;、锐捷&#xff08;Ruijie&#xff09;常用网络设备命令&#xff1a; 华为&#xff08;Huawei&#xff09; 查看设备的信息&#xff0c;可执行“display version”命令。 查看当下的配置&#xff0c;则…

动手学深度学习9.3. 深度循环神经网络-笔记练习(PyTorch)

本节课程地址&#xff1a;58 深层循环神经网络【动手学深度学习v2】_哔哩哔哩_bilibili 本节教材地址&#xff1a;9.3. 深度循环神经网络 — 动手学深度学习 2.0.0 documentation (d2l.ai) 本节开源代码&#xff1a;...>d2l-zh>pytorch>chapter_multilayer-perceptr…

计算机毕业设计Tensorflow交通标志识别检测 车流量预测 车速检测 自动驾驶 机器学习 深度学习 人工智能 PyTorch 大数据毕设

《Tensorflow交通标志识别检测》开题报告 一、研究背景及意义 随着智能交通系统和无人驾驶技术的快速发展&#xff0c;交通标志识别系统成为智能驾驶系统的重要组成部分。传统的交通标志识别方法主要依赖于人工检查和识别&#xff0c;存在效率低下、易受主观因素影响等问题。…

聚观早报 | 苹果重磅更新;OpenAI推出ChatGPT Canvas

聚观早报每日整理最值得关注的行业重点事件&#xff0c;帮助大家及时了解最新行业动态&#xff0c;每日读报&#xff0c;就读聚观365资讯简报。 整理丨Cutie 10月1日消息 苹果重磅更新 OpenAI推出ChatGPT Canvas Meta发布Movie Gen iQOO 13影像规格曝光 华为HarmonyOS N…

高效微调理解(prompt-tuning,p-tuning v1,p-tuning v2,lora)

高效微调&#xff08;prompt-tuning&#xff0c;p-tuning v1&#xff0c;p-tuning v2&#xff0c;lora&#xff09; 1.prompt-tuning&#xff1a; 例子理解&#xff1b;保持原本模型参数不变&#xff0c;通过训练提示词的参数调整prompt&#xff0c;使其与下游任务匹配。 例子…

计算机毕业设计 基于Django的在线考试系统的设计与实现 Python+Django+Vue 前后端分离 附源码 讲解 文档

&#x1f34a;作者&#xff1a;计算机编程-吉哥 &#x1f34a;简介&#xff1a;专业从事JavaWeb程序开发&#xff0c;微信小程序开发&#xff0c;定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事&#xff0c;生活就是快乐的。 &#x1f34a;心愿&#xff1a;点…

职场上的人情世故,你知多少?这五点一定要了解

职场是一个由人组成的复杂社交网络&#xff0c;人情世故在其中起着至关重要的作用。良好的人际关系可以帮助我们更好地融入团队&#xff0c;提升工作效率&#xff0c;甚至影响职业发展。在职场中&#xff0c;我们需要了解一些关键要素&#xff0c;以更好地处理人际关系&#xf…

[C++]使用纯opencv部署yolov11-cls图像分类onnx模型

【算法介绍】 在C中使用纯OpenCV部署YOLOv11-cls图像分类ONNX模型是一项具有挑战性的任务&#xff0c;因为YOLOv11通常是用PyTorch等深度学习框架实现的&#xff0c;而OpenCV本身并不直接支持加载和运行PyTorch模型。然而&#xff0c;可以通过一些间接的方法来实现这一目标&am…

【MySQL】Ubuntu环境下MySQL的安装与卸载

目录 1.MYSQL的安装 2.MySQL的登录 3.MYSQL的卸载 4.设置配置文件 1.MYSQL的安装 首先我们要看看我们环境里面有没有已经安装好的MySQL 我们发现是默认是没有的。 我们还可以通过下面这个命令来确认有没有mysql的安装包 首先我们得知道我们当前的系统版本是什么 lsb_…

Pikachu-Cross-Site Scripting-DOM型xss

DOM型xss DOM型XSS漏洞是一种特殊类型的XSS,是基于文档对象模型 Document Object Model (DOM)的一种漏洞。是一个与平台、编程语言无关的接口&#xff0c;它允许程序或脚本动态地访问和更新文档内容、结构和样式&#xff0c;处理后的结果能够成为显示页面的一部分。 dom就是一…

云手机可以解决TikTok运营的哪些问题?

随着社交媒体的飞速发展&#xff0c;TikTok迅速崛起&#xff0c;成为个人和企业进行品牌宣传和内容创作的首选平台。然而&#xff0c;在运营TikTok账号的过程中&#xff0c;不少用户会遇到各种问题。本文将详细阐述云手机如何帮助解决这些问题。 1. 多账号管理的高效便捷 通过云…

[C++]使用纯opencv部署yolov11-pose姿态估计onnx模型

【算法介绍】 使用纯OpenCV部署YOLOv11-Pose姿态估计ONNX模型是一项具有挑战性的任务&#xff0c;因为YOLOv11通常是用PyTorch等深度学习框架实现的&#xff0c;而OpenCV本身并不直接支持加载和运行PyTorch模型。然而&#xff0c;可以通过一些间接的方法来实现这一目标&#x…

UE4 材质学习笔记05(凹凸偏移和视差映射/扭曲着色器)

一.凹凸偏移和视差映射 1.偏移映射 这需要一个高度图并且它的分辨率很低&#xff0c;只有256*256&#xff0c;事实上&#xff0c;如果高度图的分辨率比较低并且有点模糊&#xff0c;效果反而会更好 然后将高度图输出到BumpOffset节点的height插槽中&#xff0c; 之后利用得到…

关于PPT生成的开源大模型总结

目前需要开源的PPT生成模型&#xff0c;在这里对github上的一些模型进行筛选 搜索关键词&#xff1a;ppt generate&#xff08;more starts&#xff09; williamfzc/chat-gpt-ppt: 支持直接生成PPT支持中英文需要调用ChatGPT&#xff08;Add your token (official openai api k…

LabVIEW回转支承间隙自动化检测系统

开发了一种基于LabVIEW软件的回转支承间隙检测系统&#xff0c;通过高精度传感器和数据采集卡&#xff0c;自动化、高效地测量回转支承的轴向间隙和径向间隙&#xff0c;提高了检测精度和生产质量。以下是对系统的详细描述与应用案例分析&#xff0c;希望能为有类似需求的开发者…

如何通过视觉分析检测车辆逆行行为

随着交通网络的快速扩展和车辆数量的持续增加&#xff0c;城市交通管理面临着前所未有的挑战。交通事故的多发原因之一是车辆逆行&#xff0c;这种行为不仅严重威胁其他车辆和行人的安全&#xff0c;也加重了交通拥堵问题。因此&#xff0c;如何有效监控并预防车辆逆行成为城市…

Java基础(上)

Java的特性 简单易学&#xff08;语法简单&#xff0c;上手容易&#xff09;&#xff1b; 面向对象&#xff08;封装&#xff0c;继承&#xff0c;多态&#xff09;&#xff1b; 平台无关性&#xff08; Java 虚拟机实现平台无关性&#xff09;&#xff1b; 支持多线程&…

实施威胁暴露管理、降低网络风险暴露的最佳实践

随着传统漏洞管理的发展&#xff0c;TEM 解决了因攻击面扩大和安全工具分散而产生的巨大风险。 主动式 TEM 方法优先考虑风险并与现有安全工具无缝集成&#xff0c;使组织能够在威胁被有效利用之前缓解威胁。 为什么威胁暴露管理 (TEM) 在现代网络安全策略中变得至关重要&…