谷粒商城项目(学习笔记九)

本文档详细记录了谷粒商城项目中集成Elasticsearch进行全文检索的过程,包括Elasticsearch的安装、ik分词器的配置、自定义分词库的建立以及商品信息的上架到Elasticsearch的步骤。通过测试验证了Elasticsearch与项目的整合功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

第六章:全文检索——elasticsearch

一、安装elasticsearch

1.拉取镜像

docker pull elasticsearch:7.4.2

2创建容器

注意加权限,不然外部无法访问

mkdir -p /mydata/elasticsearch/config
mkdir -p /mydata/elasticsearch/data

echo "http.host: 0.0.0.0" >> /mydata/elasticsearch/config/elasticsearch.yml

chmod -R 777 /mydata/elasticsearch/

docker run --name elasticsearch -p 9200:9200 -p 9300:9300 \
-e "discovery.type=single-node" \
-e ES_JAVA_OPTS="-Xms64m -Xmx128m" \
-v /mydata/elasticsearch/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml \
-v /mydata/elasticsearch/data:/usr/share/elasticsearch/data \
-v /mydata/elasticsearch/plugins:/usr/share/elasticsearch/plugins \
-d elasticsearch:7.4.2

3.访问测试

http://localhost:9200/

成功页面

 4.Kibana(可以不用安,可视化工具)

docker pull kibana:7.4.2(版本要一致)

docker run --name kibana -e ELASTICSEARCH_HOSTS=http://(虚拟机地址):9200 -p 5601:5601 \
-d kibana:7.4.2










二、安装 ik 分词器

https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.4.2/elasticsearch-analysis-ik-7.4.2.zip

1安装对应版本的ik

解压压缩包,放入Plugins中

unzip elasticsearch-analysis-ik-7.4.2.zip

改变权限

chmod -R 777 ik/

测试

POST _analyze
{ "analyzer": "ik_max_word", "text": "我是中国人"
}

自定义词库

2安装nginx

 随便启动一个 nginx 实例,只是为了复制出配置
 docker run -p 80:80 --name nginx -d nginx:1.10
 将容器内的配置文件拷贝到当前目录:docker container cp nginx:/etc/nginx .  别忘了后面的点
 修改文件名称:mv nginx conf 把这个 conf 移动到/mydata/nginx 下
 终止原容器:docker stop nginx
 执行命令删除原容器:docker rm $ContainerId
 创建新的 nginx;执行以下命令
docker run -p 80:80 --name nginx \
-v /mydata/nginx/html:/usr/share/nginx/html \
-v /mydata/nginx/logs:/var/log/nginx \
-v /mydata/nginx/conf:/etc/nginx \
-d nginx:1.10

在nginx的html文件夹下新建es文件夹,创建fenci.txt来放自定义的分词

访问 http://地址/es/fenci.txt 来查看是否创建成功

 3.为ik配置自定义的分词

来到ik的config目录下,打开IKAnalyzer.cfg.xml
在文件中修改配置为nginx中的地址

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
	<comment>IK Analyzer 扩展配置</comment>
	<!--用户可以在这里配置自己的扩展字典 -->
	<entry key="ext_dict"></entry>
	 <!--用户可以在这里配置自己的扩展停止词字典-->
	<entry key="ext_stopwords"></entry>
	<!--用户可以在这里配置远程扩展字典 -->
	<entry key="remote_ext_dict">http://自己的地址/es/fenci.txt</entry>
	<!--用户可以在这里配置远程扩展停止词字典-->
	<!-- <entry key="remote_ext_stopwords">words_location</entry> -->
</properties>

4.测试

POST _analyze
{
    "analyzer": "ik_max_word",
    "text": "乔碧萝殿下"
}

三、测试elasticsearch的整合

1.导入api和common

        <dependency>
            <groupId>com.yangyan.gulimall</groupId>
            <artifactId>gulimall-common</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

        <!--elasticsearch的api-->
        <dependency>
            <groupId>org.elasticsearch.client</groupId>
            <artifactId>elasticsearch-rest-high-level-client</artifactId>
            <version>7.4.2</version>
        </dependency>

2.检查Dependencies版本中与springboot版本一致

修改springboot中的elasticsearch版本

3.新建配置文件GulimallElasticSearchConfig和注册中心

@Configuration
public class GulimallElasticSearchConfig {

    @Bean
    RestHighLevelClient client() {
        RestClientBuilder builder = RestClient.builder(new HttpHost("47.113.199.234", 9200, "http"));
        return new RestHighLevelClient(builder);
    }
}

二、测试

1.插入测试

    @Resource
    private RestHighLevelClient client;   

    @Test
    void index() throws IOException {
        IndexRequest request = new IndexRequest("product").id("20")
                .source("spuName","华为","id",20L);

        try {
            IndexResponse response = client.index(request, GulimallElasticSearchConfig.COMMON_OPTIONS);
            System.out.println(request);
        } catch (ElasticsearchException e) {
            if (e.status() == RestStatus.CONFLICT) {
            }
        }
    }

    @Data
    class Product{
        private Long id;
        private String SpuName;
    }

2.search查询测试

    @Test
    public void searchData() throws IOException{
        //1.构建
        //创建检索请求
        SearchRequest searchRequest = new SearchRequest();

        //指定DSL,检索条件
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.query(QueryBuilders.matchQuery("spuName", "华为"));

        System.out.println(searchSourceBuilder.toString());

        searchRequest.source(searchSourceBuilder);


        //2.执行
        SearchResponse search = client.search(searchRequest, GulimallElasticSearchConfig.COMMON_OPTIONS);

        System.out.println(search);
    }

四、elasticsearch的整合

1.商品上架

1控制器

    /**
     *商品上架
     */
    @PostMapping("/{spuId}/up")
    public R spuUp(@PathVariable("spuId") Long spuId){
        spuInfoService.up(spuId);

        return R.ok();
    }

2.在common中封装es的SkuEsModel

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

    }
}

3.业务的实现

    @Resource
    BrandService brandService;

    @Resource
    CategoryService categoryService;

    @Resource
    WareFeignService wareFeignService;

    @Resource
    SearchFeignService searchFeignService;    

    //商品上架
    @Override
    public void up(Long spuId) {

        //1.查出当前spuid对应的所有sku信息,品牌的名字。
        List<SkuInfoEntity> skus = skuInfoService.getSkusBySpuId(spuId);
        List<Long> skuIdList = skus.stream().map(SkuInfoEntity::getSkuId).collect(Collectors.toList());

        //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.selectSearchAttrIds(attrIds);

        Set<Long> idSet = new HashSet<>(searchAttrIds);

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

        //TODO 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, item -> item.getHasStock()));
        }catch (Exception e){
            log.error("库存服务查询异常:原因{}",e);
        }

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

            //组装skuPrice,skuImg
            esModel.setSkuPrice(sku.getPrice());
            esModel.setSkuImg(sku.getSkuDefaultImg());

            //TODO 1.是否有库存
            if (finalStockMap == null){
                esModel.setHasStock(true);
            }else {
                esModel.setHasStock(finalStockMap.get(sku.getSkuId()));
            }

            //TODO 2.热度评分
            esModel.setHotScore(0L);

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

            //TODO 4.查询当前sku的所有可被检索的规格属性;
            CategoryEntity category = categoryService.getById(esModel.getCatalogId());
            esModel.setCatalogName(category.getName());

            //设置检索属性
            esModel.setAttrs(attrsList);

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

        //TODO 5.发送给检索服务es进行执行
        R r = searchFeignService.productStatusUp(upProducts);
        if(r.getCode()==0){
            //远程调用成功
            //TODO 6.修改sku的状态
            baseMapper.updateSpuStatus(spuId, ProductConstant.StatusEnum.SPU_UP.getCode());
        }else {
            //远程调用失败
            //TODO 7.重复调用接口幂等性
        }
    }

4.选出指定集合里的属性

    @Override
    public List<Long> selectSearchAttrIds(List<Long> attrIds) {
       return baseMapper.selectSearchAttrIds(attrIds);
    }


    @Mapper
    public interface AttrDao extends BaseMapper<AttrEntity> {
        List<Long> selectSearchAttrIds(@Param("attrIds") List<Long> attrIds);
    }


    <select id="selectSearchAttrIds" 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>

5.远程调用是否有库存

    /**
     * 查询sku是否有库存
     */
    @PostMapping("/hasstock")
    public R getSkusHasStock(@RequestBody List<Long> skuIds) {
        List<SkuHasStockVo> vos = wareSkuService.getSkusHasStock(skuIds);

        return R.ok().setData(vos);
    }


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


    <select id="getSkuStock" resultType="java.lang.Long">
        SELECT SUM(stock-stock_locked) FROM wms_ware_sku WHERE sku_id=#{skuId};
    </select>

远程调用接口

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

    /**
     * 查询sku是否有库存
     */
    @PostMapping("/ware/waresku/hasstock")
    R getSkusHasStock(@RequestBody List<Long> skuIds);

}

修改R类


	//利用fastjson进行逆转
	public <T> T getData(String key,TypeReference<T> typeReference){
		Object data = get(key);//默认是map
		String s = JSON.toJSONString(data);
		T t = JSON.parseObject(s, typeReference);
		return t;
	}

	//利用fastjson进行逆转
	public <T> T getData(TypeReference<T> typeReference){
		Object data = get("data");//默认是map
		String s = JSON.toJSONString(data);
		T t = JSON.parseObject(s, typeReference);
		return t;
	}
	public R setData(Object data){
		put("data",data);
		return this;
	}

6.发送给es执行

1)定制常量

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

2)控制器

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

    @Resource
    ProductSaveService productSaveService;

    /**
     * 上架商品
     */
    @PostMapping("/product")
    public R productStatusUp(@RequestBody List<SkuEsModel> skuEsModels) {
        boolean b =false;
        try {
            b = productSaveService.productStatusUp(skuEsModels);
        } catch (Exception e) {
            log.error("ElasticSearchController商品上架错误:{}",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());
        }
    }
    
}

3)实现类

    @Resource
    RestHighLevelClient restHighLevelClient;

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

        //保存到es
        //1.建立索引和映射关系
        //2.各es中保存这些数据
        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();
        List<String> errors = Arrays.stream(bulk.getItems()).map(item -> {
            return item.getId();
        }).collect(Collectors.toList());
        log.error("商品上架错误:{},返回的数据:{}",errors,bulk.toString());

        return b;
    }

4)调用接口

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

    @PostMapping("/search/save/product")
    R productStatusUp(@RequestBody List<SkuEsModel> skuEsModels);
}

7.修改sku状态

@Mapper
public interface SpuInfoDao extends BaseMapper<SpuInfoEntity> {

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


<update id="updateSpuStatus">
    UPDATE pms_spu_info SET publish_status=#{code},update_time=NOW() WHERE id=#{spuId};
</update>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值