• 电商项目实战(架构六)——Elasticsearch实现商品搜索


    一、前言

      Elasticsearch是一个分布式、可扩展、实时的搜索与数据分析引擎,它能从一开始就赋予你的数据以搜索、分析和探索的能力,可用于全文搜索和数据实时统计。

    二、框架

      Elasticsearch的安装和使用

      1、下载Elasticsearch6.2.2压缩包,下载地址:https://www.elastic.co/cn/downloads/past-releases/elasticsearch-6-2-2

      

       2、安装中文分词插件,解压后,在cmd命令框中进入到bin目录下,执行命令:elasticsearch-plugin install  https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v6.2.2/elasticsearch-analysis-ik-6.2.2.zip

      

       

       3、运行bin目录下的elasticsearch.bat,启动elasticsearch

      

       

       4、下载kibana,作为访问elasticsearch的客户端,下载地址:https://artifacts.elastic.co/downloads/kibana/kibana-6.2.2-windows-x86_64.zip,解压后进入bin目录,打开文件kibana.bat,启动Kibana用户界面

      

      

       5、访问http://localhost:5601打开用户界面

      

      Spring Data Elasticsearch

      1、常用注解

      @Document(表示映射到Elasticsearch文档上的领域对象)

    public @interface Document{
        //索引库名次,mysql中数据库的概念
        String indexName(); 
        //文档类型,mysql中表的概念
        String type() default "";   
        //默认分片数
        short shards() default 5;
        //默认副本数量
        short replicas default 1;
      
    }

      @Id(表示是文档的id,文档可以认为是mysql中表字段的概念)

    public @interface Id{
    }

      @Field

    public @interface Field{
        //文档中字段的类型
        FieldType type() default FieldType.Auto;
        //是否建立倒排索引
        boolean index() default true;
        //是否进行存储
        boolean store() deafult false;
        //分词器名次
        String analyzer() default "";
    }
    
    //为文档自动指定元数据类型
    public enum FieldType{
        Text,    //会进行分词并建了索引的字符类型
        Integer,
        Long,
        Date,
        Float,
        Double,
        Boolean,
        Object,
        Auto,    //自动判断字段类型
        Nested,    //嵌套对象类型
        Ip,
        Attachment,
        Keyword    //不会进行分词建立索引的类型
        
    }

    三、建表

      商品信息表:pms_product

      

      字段解释:id(商品信息表id),brand_id(品牌id),product_category_id(商品分类id),feight_template_id(运费模板id),product_attribute_category_id(规格属性类别id),name(商品名称),pic(商品主图),product_sn(货号),publish_status(是否上架 0->下架  1->上架),recommand_status(是否推荐  0->否  1->是),verify_status(审核状态  0->待审核  1->审核通过  2->审核拒绝),sale_count(销量),unit(单位),min_price(最低价),max_price(最高价),market_price(市场价),description(商品描述),stock_total(库存总数),weight(重量(单位默认为克)),album_pics(画册图片,限制为5张,以逗号分割,主图在第一位),detail_title(商品详情标题),detail_sub_title(商品详情副标题),detail_html(商品详情富文本)

      商品规格属性分类表:pms_product_attribute_category

      

       字段解释:id(商品规格属性类别表id),name(类别名称),attribute_count(类别下属性数量)

      商品规格属性表:pms_product_attribute

      

       字段解释:id(商品规格属性表id),product_attribute_category_id(规格属性类别id),name(规格属性名称),type(类型 0->规格属性  1->参数)

      商品规格属性值表:pms_product_attribute_value

      

       字段解释:id(商品规格属性值id),product_attribute_id(商品规格属性id),value(属性值)

    四、整合Elasticsearch,实现商品搜索

      1、在pom.xml文件中添加相关依赖

    <!--Elasticsearch相关依赖-->
        <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
        </dependency>

      2、修改application.yml文件

    spring:
      #连接elasticsearch
      data:
        elasticsearch:
          repositories:
            enabled: true
          cluster-nodes: 127.0.0.1:9300    #es的连接地址及端口号
          cluster-name:   elasticsearch    #es集群的名称

      3、新建elasticsearch.document和elasticsearch.repository包

      

       4、在document包下新建商品文档对象EsProduct

    package com.zzb.test.admin.elasticsearch.document;
    
    import org.springframework.data.annotation.Id;
    import org.springframework.data.elasticsearch.annotations.Document;
    import org.springframework.data.elasticsearch.annotations.Field;
    import org.springframework.data.elasticsearch.annotations.FieldType;
    
    import java.io.Serializable;
    import java.math.BigDecimal;
    import java.util.List;
    
    /**
     * 商品信息
     * Created by zzb on 2019/12/3 17:09
     */
    @Document(indexName = "pms", type = "product", shards = 1, replicas = 0)
    public class EsProduct implements Serializable {
        private static final long serialVersionUID = -1L;
        @Id
        private Long id;
        private Long brandId;
        private Long productCategoryId;
        private Long productAttributeCategoryId;
        private String unit;
        private BigDecimal minPrice;
        private BigDecimal maxPrice;
        private BigDecimal marketPrice;
        private String description;
        private BigDecimal stockTotal;
        private BigDecimal weight;
        @Field(type = FieldType.Keyword)
        private String productSn;
        @Field(analyzer = "ik_max_word", type = FieldType.Text)
        private String name;
        @Field(analyzer = "ik_max_word", type = FieldType.Text)
        private String detailTitle;
        @Field(analyzer = "ik_max_word", type = FieldType.Text)
        private String detailSubTitle;
        @Field(analyzer = "ik_max_word", type = FieldType.Text)
        private String keyword;
        @Field(type = FieldType.Nested)     //嵌套对象类型
        private List<EsProductAttributeValue> attrValueList;
    
        public String getKeyword() {
            return keyword;
        }
    
        public void setKeyword(String keyword) {
            this.keyword = keyword;
        }
    
        public Long getBrandId() {
            return brandId;
        }
    
        public void setBrandId(Long brandId) {
            this.brandId = brandId;
        }
    
        public Long getProductCategoryId() {
            return productCategoryId;
        }
    
        public void setProductCategoryId(Long productCategoryId) {
            this.productCategoryId = productCategoryId;
        }
    
        public Long getProductAttributeCategoryId() {
            return productAttributeCategoryId;
        }
    
        public void setProductAttributeCategoryId(Long productAttributeCategoryId) {
            this.productAttributeCategoryId = productAttributeCategoryId;
        }
    
        public String getUnit() {
            return unit;
        }
    
        public void setUnit(String unit) {
            this.unit = unit;
        }
    
        public BigDecimal getMinPrice() {
            return minPrice;
        }
    
        public void setMinPrice(BigDecimal minPrice) {
            this.minPrice = minPrice;
        }
    
        public BigDecimal getMaxPrice() {
            return maxPrice;
        }
    
        public void setMaxPrice(BigDecimal maxPrice) {
            this.maxPrice = maxPrice;
        }
    
        public BigDecimal getMarketPrice() {
            return marketPrice;
        }
    
        public void setMarketPrice(BigDecimal marketPrice) {
            this.marketPrice = marketPrice;
        }
    
        public String getDescription() {
            return description;
        }
    
        public void setDescription(String description) {
            this.description = description;
        }
    
        public BigDecimal getStockTotal() {
            return stockTotal;
        }
    
        public void setStockTotal(BigDecimal stockTotal) {
            this.stockTotal = stockTotal;
        }
    
        public BigDecimal getWeight() {
            return weight;
        }
    
        public void setWeight(BigDecimal weight) {
            this.weight = weight;
        }
    
        public Long getId() {
            return id;
        }
    
        public void setId(Long id) {
            this.id = id;
        }
    
        public String getProductSn() {
            return productSn;
        }
    
        public void setProductSn(String productSn) {
            this.productSn = productSn;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public String getDetailTitle() {
            return detailTitle;
        }
    
        public void setDetailTitle(String detailTitle) {
            this.detailTitle = detailTitle;
        }
    
        public String getDetailSubTitle() {
            return detailSubTitle;
        }
    
        public void setDetailSubTitle(String detailSubTitle) {
            this.detailSubTitle = detailSubTitle;
        }
    
        public List<EsProductAttributeValue> getAttrValueList() {
            return attrValueList;
        }
    
        public void setAttrValueList(List<EsProductAttributeValue> attrValueList) {
            this.attrValueList = attrValueList;
        }
    }

      5、在document包下新建商品文档对象内的嵌套对象EsProductAttributeValue

    package com.zzb.test.admin.elasticsearch.document;
    
    import org.springframework.data.elasticsearch.annotations.Field;
    import org.springframework.data.elasticsearch.annotations.FieldType;
    
    import java.io.Serializable;
    
    /**
     * 商品属性
     * Created by zzb on 2019/12/3 17:36
     */
    public class EsProductAttributeValue implements Serializable {
        private static final long serialVersionUID = 1L;
        //属性值id
        private Long id;
        //属性id
        private Long productAttributeId;
        //属性值
        @Field(type = FieldType.Keyword)
        private String value;
        @Field(type = FieldType.Keyword)
        private String name;
    
        public Long getId() {
            return id;
        }
    
        public void setId(Long id) {
            this.id = id;
        }
    
        public Long getProductAttributeId() {
            return productAttributeId;
        }
    
        public void setProductAttributeId(Long productAttributeId) {
            this.productAttributeId = productAttributeId;
        }
    
        public String getValue() {
            return value;
        }
    
        public void setValue(String value) {
            this.value = value;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    }

      6、在repository包下新建操作Elasticsearch的接口继承ElasticsearchRepository

    package com.zzb.test.admin.elasticsearch.repository;
    
    import com.zzb.test.admin.elasticsearch.document.EsProduct;
    import org.springframework.data.domain.Page;
    import org.springframework.data.domain.Pageable;
    import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
    
    /**
     * 操作Elasticsearch的接口
     * Created by zzb on 2019/12/4 10:54
     */
    public interface EsProductRepository extends ElasticsearchRepository<EsProduct,Long> {
        /**
         * 搜索查询
         * @param name
         * @param detailTitle
         * @param keyword
         * @param page
         * @return
         */
        Page<EsProduct> findByKeyword(String name, String detailTitle, String keyword,Pageable page);
    }

      7、在service包下新建Elasticsearch商品搜索Service类EsProductService

    package com.zzb.test.admin.service;
    
    import com.zzb.test.admin.elasticsearch.document.EsProduct;
    import org.springframework.data.domain.Page;
    import org.springframework.data.domain.Pageable;
    
    import java.util.List;
    
    /**
     * Elasticsearch商品搜索的Service
     * Created by zzb on 2019/12/4 11:00
     */
    public interface EsProductService {
        /**
         * 从数据库中导入商品到ES
         * @return
         */
        int importAll();
    
        /**
         * 根据id删除商品
         * @param id
         */
        void delete(Long id);
    
        /**
         * 根据id创建商品
         * @param id
         * @return
         */
        EsProduct create(Long id);
    
        /**
         * 批量删除
         * @param ids
         */
        void deletes(List<Long> ids);
    
        /**
         * 根据关键字搜索
         * @param keyword
         * @param pageNum
         * @param pageSize
         * @return
         */
        Page<EsProduct> searchPage(String keyword, Integer pageNum,Integer pageSize);
    }

      8、在impl包下创建其实现类EsProductServiceImpl

    package com.zzb.test.admin.service.impl;
    
    import com.zzb.test.admin.dao.EsProductDao;
    import com.zzb.test.admin.elasticsearch.document.EsProduct;
    import com.zzb.test.admin.elasticsearch.repository.EsProductRepository;
    import com.zzb.test.admin.service.EsProductService;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.data.domain.Page;
    import org.springframework.data.domain.PageRequest;
    import org.springframework.data.domain.Pageable;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;
    import org.springframework.util.CollectionUtils;
    
    import java.util.ArrayList;
    import java.util.Iterator;
    import java.util.List;
    
    /**
     * EsProductService接口的实现类
     * Created by zzb on 2019/12/4 11:06
     */
    @Service
    @Transactional
    public class EsProductServiceImpl implements EsProductService {
        private static final Logger logger = LoggerFactory.getLogger(EsProductServiceImpl.class);
        @Autowired
        private EsProductDao esProductDao;
        @Autowired
        private EsProductRepository esProductRepository;
        @Override
        public int importAll() {
            List<EsProduct> esProductList = esProductDao.getProductEs(null);
            Iterable<EsProduct> iterable = esProductRepository.saveAll(esProductList);
            Iterator<EsProduct> iterator = iterable.iterator();
            logger.info("导入ES数据{}:",iterator);
            int count = 0;
            while (iterator.hasNext()) {
                count++;
                iterator.next();
            }
            return count;
        }
    
        @Override
        public void delete(Long id) {
            logger.info("删除ES中的商品{}:",id);
            esProductRepository.deleteById(id);
        }
    
        @Override
        public EsProduct create(Long id) {
            List<EsProduct> esProducts = esProductDao.getProductEs(id);
            if (CollectionUtils.isEmpty(esProducts)) {
                return null;
            }
            EsProduct esProduct = esProducts.get(0);
            logger.info("导入ES单条商品{}:",esProduct);
            return esProductRepository.save(esProduct);
        }
    
        @Override
        public void deletes(List<Long> ids) {
            if (!CollectionUtils.isEmpty(ids)) {
                List<EsProduct> esProductList = new ArrayList<>();
                ids.forEach(id->{
                    EsProduct esProduct = new EsProduct();
                    esProduct.setId(id);
                    esProductList.add(esProduct);
                });
                logger.info("批量删除ES中的商品{}:",esProductList);
                esProductRepository.deleteAll(esProductList);
            }
        }
    
        @Override
        public Page<EsProduct> searchPage(String keyword, Integer pageNum, Integer pageSize) {
            Pageable pageable = PageRequest.of(pageNum,pageSize);
            return esProductRepository.findByKeyword(keyword,keyword,keyword,pageable);
        }
    }

      9、在dao包下新建操作数据库接口EsProductDao和映射xml文件EsProductDao.xml

    package com.zzb.test.admin.dao;
    
    import com.zzb.test.admin.elasticsearch.document.EsProduct;
    
    import java.util.List;
    
    /**
     * Elasticsearch商品搜索dao
     * Created by zzb on 2019/12/4 11:20
     */
    public interface EsProductDao {
        List<EsProduct> getProductEs(Long id);
    }
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.zzb.test.admin.dao.EsProductDao">
      <select id="getProductEs" resultType="com.zzb.test.admin.elasticsearch.document.EsProduct" parameterType="java.lang.Long">
        SELECT DISTINCT
            p.id id,
            p.product_sn productSn,
            p.brand_id brandId,
            pb.brand_name brandName,
            p.product_category_id productCategoryId,
            p.pic pic,
            p. NAME NAME,
            p.detail_title detailTitle,
            p.min_price minPrice,
            p.recommand_status recommandStatus,
            p.stock_total stockTotal,
            p.sort sort
        FROM
            pms_product p
        LEFT JOIN pms_brand pb ON pb.id = p.brand_id
        LEFT JOIN pms_product_attribute_category ppac ON ppac.id = p.product_attribute_category_id
        LEFT JOIN pms_product_attribute pa ON pa.product_attribute_category_id = ppac.id
        LEFT JOIN pms_product_attribute_value pav ON pa.id = pav.product_attribute_id
        WHERE
            p.del_status = 0
        AND p.publish_status = 1
        <if test="id!=null">
            AND p.id=#{id}
        </if>
      </select>
    </mapper>

      10、在controller包下新建控制器EsProductController

    package com.zzb.test.admin.controller;
    
    import com.zzb.test.admin.common.CommonPage;
    import com.zzb.test.admin.common.CommonResult;
    import com.zzb.test.admin.elasticsearch.document.EsProduct;
    import com.zzb.test.admin.service.EsProductService;
    import io.swagger.annotations.Api;
    import io.swagger.annotations.ApiOperation;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.data.domain.Page;
    import org.springframework.stereotype.Controller;
    import org.springframework.util.StringUtils;
    import org.springframework.web.bind.annotation.*;
    
    import java.util.List;
    
    /**
     * ES搜索商品Controller
     * Created by zzb on 2019/12/4 14:06
     */
    @Controller
    @Api(tags = "EsProductController",description = "ES商品搜索")
    public class EsProductController {
        @Autowired
        private EsProductService esProductService;
    
        @ApiOperation("从数据库导入ES商品数据")
        @RequestMapping(value = "/esProduct/importAll",method = RequestMethod.POST)
        @ResponseBody
        public CommonResult<Integer> importAll(){
            int count = esProductService.importAll();
            return CommonResult.success(count);
        }
    
        @ApiOperation("根据id删除商品")
        @RequestMapping(value = "/esProduct/delete/{id}",method = RequestMethod.POST)
        @ResponseBody
        public CommonResult deleteById(@PathVariable Long id){
            esProductService.delete(id);
            return CommonResult.success("删除成功");
        }
    
        @ApiOperation("批量删除商品")
        @RequestMapping(value = "/esProduct/deletes",method = RequestMethod.POST)
        @ResponseBody
        public CommonResult deleteById(List<Long> ids){
            esProductService.deletes(ids);
            return CommonResult.success("删除成功");
        }
    
        @ApiOperation("根据id创建商品")
        @RequestMapping(value = "/esProduct/create",method = RequestMethod.POST)
        @ResponseBody
        public CommonResult create(Long id){
            EsProduct esProduct = esProductService.create(id);
            if (StringUtils.isEmpty(esProduct)) {
                return CommonResult.failed("创建失败");
            }
            return CommonResult.success("创建成功");
        }
    
        @ApiOperation("搜索商品")
        @RequestMapping(value = "/esProduct/search",method = RequestMethod.GET)
        @ResponseBody
        public CommonResult<CommonPage<EsProduct>> search(@RequestParam(required = false) String keyword,
                                                          @RequestParam(required = false, defaultValue = "0") Integer pageNum,
                                                          @RequestParam(required = false, defaultValue = "5") Integer pageSize){
            Page<EsProduct> esProductPage = esProductService.searchPage(keyword,pageNum,pageSize);
            return CommonResult.success(CommonPage.restPage(esProductPage));
        }
    }

      11、修改common包下分页结果解析类CommonPage

    package com.zzb.test.admin.common;
    
    import com.github.pagehelper.PageInfo;
    import org.springframework.data.domain.Page;
    
    import java.util.List;
    
    /**
     * mybatis分页封装
     * Created by zzb on 2019/11/15 12:27
     */
    public class CommonPage<T> {
        private Integer pageNum;
        private Integer pageSize;
        private Integer totalPage;
        private Long total;
        private List<T> list;
    
        /**
         * 将PageHelper分页后的list转为分页信息
         * @param list
         * @param <T>
         * @return
         */
        public static <T> CommonPage<T> restPage(List<T> list){
            CommonPage<T> result = new CommonPage<>();
            PageInfo<T> pageInfo = new PageInfo<>(list);
            result.setPageNum(pageInfo.getPageNum());
            result.setPageSize(pageInfo.getPageSize());
            result.setTotal(pageInfo.getTotal());
            result.setList(pageInfo.getList());
            return result;
        }
    
        /**
         * 将SpringData分页后的list转为分页信息
         * @param pageInfo
         * @param <T>
         * @return
         */
        public static <T> CommonPage<T> restPage(Page pageInfo){
            CommonPage<T> result = new CommonPage<>();
            result.setPageNum(pageInfo.getNumber());
            result.setPageSize(pageInfo.getSize());
            result.setTotalPage(pageInfo.getTotalPages());
            result.setList(pageInfo.getContent());
            return result;
        }
    
        public Integer getPageNum() {
            return pageNum;
        }
    
        public void setPageNum(Integer pageNum) {
            this.pageNum = pageNum;
        }
    
        public Integer getPageSize() {
            return pageSize;
        }
    
        public void setPageSize(Integer pageSize) {
            this.pageSize = pageSize;
        }
    
        public Integer getTotalPage() {
            return totalPage;
        }
    
        public void setTotalPage(Integer totalPage) {
            this.totalPage = totalPage;
        }
    
        public Long getTotal() {
            return total;
        }
    
        public void setTotal(Long total) {
            this.total = total;
        }
    
        public List<T> getList() {
            return list;
        }
    
        public void setList(List<T> list) {
            this.list = list;
        }
    }

    五、添加数据

      在数据库中给商品相关表添加数据

    六、测试

      1、访问http://localhost:10077/swagger-ui.html

      2、访问登录接口,获取访问权限

      3、访问接口/esProduct/importAll,将数据库中数据导入到elasticsearch

      

       4、访问接口/esProduct/search,查询导入的数据

      

       5、访问接口/esProduct/delete/{id},将id为1的商品从elasticsearch中移除

      

       再次访问接口/esProduct/search,进行查看是否移除成功

      

      项目github地址:https://github.com/18372561381/shoptest

  • 相关阅读:
    C++面向对象高级编程(下)第二周-Geekband
    C++面向对象高级编程(下)第一周-Geekband
    C++面向对象高级编程(下)-Geekband
    堆,栈,内存管理, 拓展补充-Geekband
    C++面向对象高级编程(上)-Geekband
    MFC 多屏显示
    Open CASCADE Technology: IGES Support
    JAVA反射
    HashMap
    Linux 系统编程
  • 原文地址:https://www.cnblogs.com/zzb-yp/p/11959236.html
Copyright © 2020-2023  润新知