solr实操(3)

1总结

 1.1:BigDecimal类型是否支持

  1.1  高版本solr金额不支持 BigDecimal类型 ,低版本是支持的.

   鉴定:pdouble只支持 Double 不支持 BigDecimal

             低版本double支持BigDecimal

1.2:拼写检查(鸡肋)

  2.1只支持英文,不支持中文

  案例:我想搜索 solr 我写的是 soll 可以告诉我你要搜索的是不是 solr

1.3:自动建议

    3.1 只支持前缀匹配,不管是中文,英文,字母, 只要前缀匹配上即可

         例如 商品名称叫 : diannao笔记本电脑小新

         搜索:dian,diannao, diannao笔记本   都可以被搜索出来

         搜索 笔记本是不行的,

        总结: 必须是所在域必须前缀匹配的上,错字谐音都不行。前缀匹配差一个字都不行。

                配置分词还不能是拼音分词器。

1.4:主键默认是id

添加元素必须要有主键,否则添加失败。删除根据主键删除会很快。其他删除很慢

主键必须具备下面的2个配置才算是主键

  <field name="id" type="string" indexed="true" stored="true" required="true" multiValued="false" /> 
  <uniqueKey>id</uniqueKey>

这俩配置才确定的id是唯一主键:required="true",<uniqueKey>id</uniqueKey>

1.5:拼音分词+ik分词

  这样的配置可以将搜索达到最大化(拼音搜索,错别字搜索)

1将pinyinAnalyzer4.3.1.jar和pinyin4j-2.5.0.jar 这俩jar放进tomcat下的solr下的lib里

2下面有 拼音+ik分词具体配置 --->配置文件(managed-schema或者schema.xml)

1.6修改配置文件带来的问题

 当你修改配置文件了,需要重启tomcat, 但是有的时候重启也不会管用,

  例如:明明修改了配置,但是搜索还是没有。 索引没有动态更新。

   目前解决办法:删除原来数据,重新添加数据。 以后看看能不能动态改变索引。

  方式二:试试可以

q查询条件
fq筛选条件
sort排序规则
start,rows分页参数
hl是否高亮显示
hl.fl高亮显示的字段,索引
hl.simple.pre高亮显示的前缀
hl.simple.post高亮显示的后缀
dismax用来修改权重打分:只能使用bf作用效果是相加,多维度调整麻烦
edismax用来修改权重打分:支持boost函数与score相乘作为,在处理多个维度排序时,score其实也应该是其中一个维度

2 域和分词的配置

配置文件(managed-schema或者schema.xml)

<!-- ik分词器/字典域 -->
  <fieldType name="text_ik" class="solr.TextField">
    <!-- 创建索引用ik分词器 -->
    <analyzer type="index">
    <!--   设置分词力度,useSmart=true粗力度,useSmart=false细力度 -->
        <tokenizer class="org.wltea.analyzer.lucene.IKTokenizerFactory" useSmart="false" conf="ik.conf"/>
        <filter class="solr.LowerCaseFilterFactory"/>
    </analyzer>
     <!--  查询时用ik分词器 ,匹配数可能会是多个 -->
    <analyzer type="query">
        <tokenizer class="org.wltea.analyzer.lucene.IKTokenizerFactory" useSmart="true" conf="ik.conf"/>
        <filter class="solr.LowerCaseFilterFactory"/>
    </analyzer>

      <!-- <analyzer type="query">
        <tokenizer class="solr.WhitespaceTokenizerFactory" />
    </analyzer> -->

  </fieldType>
    <!-- 拼音分词器/字典域 -->
    <!--   positionIncrementGap:多值搜索,指定多个值之间的距离,这可以防止虚假词组匹配 
       例子:搜索(电脑 手机) 实际这是两个词,positionIncrementGap=0会认为这是一个词
    -->
  <fieldType name="text_pinyin" class="solr.TextField" positionIncrementGap="100">
    <!-- 创建索引用拼音分词器 -->
    <analyzer type="index">
       <tokenizer class="org.wltea.analyzer.lucene.IKTokenizerFactory"/>  
         <!--拼音分词器下配置:minTermLenght:最小中文词长度,意思是小于这个值的中文词不会做拼音转换。 -->
        <filter class="com.shentong.search.analyzers.PinyinTransformTokenFilterFactory" minTermLenght="2" />
        <!--拼音分词器下配置:minGram:最小拼音切分长度 maxGram: -->
        <filter class="com.shentong.search.analyzers.PinyinNGramTokenFilterFactory" minGram="1" maxGram="20" />
    </analyzer>
     <!--  查询时用pinyin分词器 ,匹配数可能会是多个 -->
    <analyzer type="query">
          <tokenizer class="org.wltea.analyzer.lucene.IKTokenizerFactory"/>       
         <filter class="com.shentong.search.analyzers.PinyinTransformTokenFilterFactory" minTermLenght="2" />
        <filter class="com.shentong.search.analyzers.PinyinNGramTokenFilterFactory" minGram="1" maxGram="20" />
    </analyzer>
  </fieldType>


      <!-- 普通域 -->    
    <!-- 京东为例 sku也是能搜索到的 如果下架还要通过主键删除,必须 indexed="true" -->
          <!-- type="plong" 要看版本的 --> 
 <field name="item_sku" type="plong" indexed="true"  stored="true"/>
   <!-- 商品名称 品牌+商品名称+属性  荣耀 HONOR 荣耀X30全网通5G手机12GB+256GB 晨曦金 -->
   <field name="item_title" type="text_pinyin" indexed="true" stored="true"/>
    <!-- 商品名称 荣耀 HONOR -->
   <field name="item_good_name" type="text_ik" indexed="true" stored="true"/>
   <!-- 商品价格 -->
   <field name="item_price" type="pdouble" indexed="true" stored="true"/>
    <!-- 商品图片url -->
   <field name="item_image_url" type="string" indexed="false" stored="true"/>
        <!-- 商品分类(1级) -->
   <field name="item_category1" type="string" indexed="true" stored="true"/>
     <!-- 商品分类(1级id) -->
   <field name="item_category1Id" type="string" indexed="true" stored="true"/>
     <!-- 商品分类(2级) -->
   <field name="item_category2" type="string" indexed="true" stored="true"/>
     <!-- 商品分类(2级id) -->
   <field name="item_category2Id" type="string" indexed="true" stored="true"/>
    <!-- 商品分类(3级) -->
   <field name="item_category3" type="string" indexed="true" stored="true"/>
     <!-- 商品分类(3级id) -->
   <field name="item_category3Id" type="string" indexed="true" stored="true"/>
    <!-- 店铺名称 -->
   <field name="item_seller" type="text_ik" indexed="true" stored="true" />
    <!-- 品牌 -->
   <field name="item_brand" type="string" indexed="true" stored="true"/>
    <!-- indexed="true" 有可能通过spu删除数据 -->
   <field name="item_spu"  type="plong" indexed="true" stored="true"/>
    <!-- 上架时间 -->
     <field name="item_uptime" type="pdate" indexed="true" stored="true" />
   <field name="item_delete" type="boolean" indexed="true" stored="true" />
   <field name="item_show" type="boolean" indexed="true" stored="true" />
    <!-- 动态域 -->
   <dynamicField name="item_spec_*" type="string" indexed="true" stored="true" />  

  <!-- 复制域:搜索框只有一个,但是搜索的东西可以是 品牌,价格,商品名称等等  说白了以后我们只搜索复制域-->   
  <field name="item_keyword" type="text_ik" indexed="true" stored="false" multiValued="true"/>
  <copyField source="item_title" dest="item_keyword"/>
  <copyField source="item_category1" dest="item_keyword"/>
  <copyField source="item_seller" dest="item_keyword"/>
  <copyField source="item_brand" dest="item_keyword"/>
   <copyField source="item_spec_*" dest="item_keyword"/>

3 SolrJ简单api

Solrj - Solr - Apache Software Foundation

总结:

  solr动态域添加数据(@Dynamic):注意

 3.1api使用前准备

       maven坐标      
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-solr</artifactId>
            <version>2.2.11.RELEASE</version>
        </dependency>
   配置文件配置

  data:
    solr:
      host: http://127.0.0.1:8080/solr/collection1
直接使用

@Autowired
private SolrClient solrClient;

实体类:

import lombok.Data;
import org.apache.solr.client.solrj.beans.Field;
import org.springframework.data.solr.core.mapping.Dynamic;

import java.io.Serializable;
import java.util.Date;
import java.util.Map;

@Data
public class ItemSolrJ implements Serializable {

    @Field("id")
    private String id;
    @Field("item_sku")
    private Long sku;
    //商品展示名称
    @Field("item_title")
    private String title;
    //商品名
    @Field("item_good_name")
    private String goodName;
    //价格
    @Field("item_price")
    private Double price;
    //图片地址
    @Field("item_image_url")
    private String imageUrl;
    //一级分类
    @Field("item_category1")
    private String category1;
    //一级分类id
    @Field("item_category1Id")
    private String category1Id;
    //二级分类
    @Field("item_category2")
    private String category2;
    //二级分类id
    @Field("item_category2Id")
    private String category2Id;
    //三级分类
    @Field("item_category3")
    private String category3;
    //三级分类id
    @Field("item_category3Id")
    private String category3Id;
    //三级分类拼音
    @Field("item_category3_pinyin")
    private String category3Pinyin;
    //商家名称
    @Field("item_seller")
    private String seller;
    //品牌
    @Field("item_brand")
    private String brand;
    //品牌拼音 不能用拼音分词器
    @Field("item_brand_pinyin")
    private String brandPinyin;
    @Field("item_spu")
    private Long spu;
    @Field("item_delete")
    private Boolean delete;
    @Field("item_show")
    private Boolean show;
    /**
     * 动态域 solrJ @Dynamic 这个有没有都一样,
     */
    @Dynamic
    @Field(value = "item_spec_*")
    private Map<String, String> specMap;
    @Field("item_uptime")
    private Date uptime;
}

3.2增加/更新 / 批量添加

  Item item = new Item();
        item.setId(1L);
        item.setSku(2L);
        item.setTitle("华为手机");
        item.setPrice(Double.valueOf("600.00"));
        Map<String, String> hashMap = new HashMap<>();
        hashMap.put("item_spec_颜色","红色");
        hashMap.put("item_spec_机身内存","16g");
        item.setSpecMap(hashMap );
        SolrInputDocument document = new SolrInputDocument();
        document.setField("id", 500L);
        document.setField("item_sku", 500L);
        document.setField("item_title", "小米手机Client");
        document.setField("item_price", Double.valueOf("600.00"));
        document.setField("item_spec_颜色", "红色");
        document.setField("item_spec_机身内存", "16g");
        try {
            //主键存在将会变成更新,添加方式一
            solrClient.addBean(item);
            //solrClient.addBeans();
            //添加方式二
            solrClient.add(document);
            solrClient.commit();
        } catch (SolrServerException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

3.2 删除:

注意:

  1. 通过id删除只能作用于id域,id域必须存值。
  2. 通过哪个域作为条件删除,哪个域就必须有该属性 indexed="true"
         //  条件删除: deleteByQuery
        // 1 任何删除通过的域都必须有该属性 indexed="true"
        // 2 deleteById 是最靠谱的
        // 3 deleteByQuery("item_title:手机");
        //  item_title 配置了分词(text_ik):将会导致 华为手机,小米手机都会删除
        //  item_title 配置不变(string):将会导致 华为手机,小米手机都不会删除
        // solrClient.deleteByQuery("item_title:手机");//条件删除
        //全部删除
        // solrClient.deleteByQuery("*:*");
        //id域(默认就有的),而且只能作用于id域
        solrClient.deleteById("2");
        solrClient.commit();

3.3查询

SOLR8.0 query查询用法详解 | ZPY博客

 3.3.1 高亮+过滤+排序

String keywords = "笔记本";
        String field = "item_keyword";
        Integer pageSize=20;//每页个数
        Integer pageNum=1;//当前页
        //2.创建查询条件
        SolrQuery query = new SolrQuery();
         //查询全部
        // query.setQuery( "*:*" );  =query.add("q", "*:*");
        query.setQuery(field + ":" + keywords);// q 查询条件 复制域查询
        // 复制域的话 :item_keyword:旗舰店
        // 也可以灵活查询 item_title:手机 OR item_price:700   OR/AND/NOT 必须大写
        // query.setQuery("item_title:手机 AND item_price:700");
        query.addSort("item_price", SolrQuery.ORDER.desc);
        query.addFilterQuery("item_show:true");
        query.addFilterQuery("item_delete:false");
        query.addFilterQuery("item_title:联想");
        query.addFilterQuery("item_price:[* TO 8000]");
        query.setStart((pageNum-1)*pageSize);
        query.setRows(pageSize);
        query.addSort("item_price", SolrQuery.ORDER.desc);
        query.addSort("id", SolrQuery.ORDER.asc);
        //打开高亮
        query.setHighlight(true);
        query.setParam("hl.fl", "item_title");//高亮域 哪个域加标签
        query.setHighlightSimplePre("<span style='color:red;'>");//设置前缀
        query.setHighlightSimplePost("</span>");//设置后缀
  /* 结果展示的域 只想返回个别的域即可这样写
        query.addField("item_show:true");
        query.addField("item_delete:false");*/
        //3.执行
        QueryResponse queryResponse = solrClient.query(query);
        //将下面带高亮的内容替换掉上面的
        //4.得到结果集
        SolrDocumentList results = queryResponse.getResults();
        //直接得到全部结果,如果不是高亮查询即可使用
        List<ItemSolrJ> beans = queryResponse.getBeans(ItemSolrJ.class);
        //5.得到高亮
        Map<String, Map<String, List<String>>> highlighting = queryResponse.getHighlighting();
        //6.如果有结果,并且有数据,就装
        List<ItemSolrJ> products = new ArrayList<>();
        if (results != null && results.getNumFound() > 0) {
            for (SolrDocument doc : results) {
                //把doc中的内容拿出来放到Product中----可用工具类中的方法代替-----
                ItemSolrJ item = new ItemSolrJ();
                item.setSku(Long.valueOf(doc.get("item_sku").toString()));
                item.setTitle(doc.get("item_title") + "");
                item.setPrice(Double.valueOf(doc.get("item_price") + ""));
                //---------------------------------------------------------------
                //拿高亮的内容
                String key = doc.getFieldValue("id") + "";
                Map<String, List<String>> map = highlighting.get(key);
                //看你设置了哪几个高亮域
                List<String> value = map.get("item_title");
                String highlightingField = "";
                if (value != null && value.size() > 0) {
                    highlightingField = value.get(0);
                }
                 item.setTitle(highlightingField);
                products.add(item);

            }
        }
        System.out.println(products);

3.3.2分组查询

     /**
     * 分组查询
     */
    @Test
    public void queryGroup6() throws Exception {
        SolrQuery query = new SolrQuery();
        query.setQuery("item_title:手机");//搜索商品名带手机的
        query.setFacet(true);
        query.addFacetQuery("item_brand:华为");//品牌是华为的
        query.addFacetQuery("item_price:[* TO 800]"); //价格小于800的
        //根据品牌分组
        query.addFacetField("item_brand");
        query.setFacetMinCount(1);
        QueryResponse rsp = solrClient.query(query);
        Map<String, Integer> facetQuery = rsp.getFacetQuery();
        System.out.println(JSON.toJSONString(facetQuery));
        // 统计品牌是华为的个数  ,价格<=800的个数
        //{"item_brand:华为":1,"item_price:[* TO 800]":2}
    }

   /**
     * 分组查询 :分组查询价格在0-200,200-400,每个区间商品数
     */
    @Test
    public void queryGroup2() throws Exception {
        SolrQuery query = new SolrQuery();
        query.setQuery("*:*");
        query.setFacet(true);
        //将0-800 以每份200的差值分组
        query.addNumericRangeFacet("item_price",0,800,200);
        QueryResponse rsp = solrClient.query(query);
        List<RangeFacet> facetRanges = rsp.getFacetRanges();
        for (RangeFacet facetRange : facetRanges) {
            String name = facetRange.getName();
            System.out.println(JSON.toJSONString(name));
        }
    }
    /**
     * 统计一下2015年每个季度添加商品的数量
     */
    @Test
    public void queryGroup3() throws Exception {
        SolrQuery query = new SolrQuery();
        query.setQuery("*:*");
        query.setFacet(true);
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date start = sdf.parse("2015-01-01 00:00:00");
        Date end = sdf.parse("2016-01-01 00:00:00");
        query.addDateRangeFacet("item_createtime",start,end,"+3MONTH");
        QueryResponse rsp = solrClient.query(query);
        List<RangeFacet> facetRanges = rsp.getFacetRanges();
        RangeFacet rangeFacet = facetRanges.get(0);
    }
 /**
     * 统计一下item_price在0-1000 和0-100 商品数量  和 item_createtime 是2019年到现在添加的商品数量
     */
    @Test
    public void queryGroup4() throws Exception {
        SolrQuery query = new SolrQuery();
        query.setQuery("*:*");
        query.setFacet(true);
        query.addIntervalFacets("item_pric",new String[]{"[0,1000]","0,100"});
        query.addIntervalFacets("item_createtime",new String[]{"[2019-01-01T0:0:0z,NOW]"});
        QueryResponse rsp = solrClient.query(query);
        List<IntervalFacet> intervalFacets = rsp.getIntervalFacets();
    }
  /**
     * 维度查询: 统计每一个品牌和其不同分类商品对应的数量
     *   联想 手机 10
     *   联想 电脑 1
     *   华为 电脑 1
     */
    @Test
    public void queryGroup5() throws Exception {
        SolrQuery query = new SolrQuery();
        query.setQuery("*:*");
        query.setFacet(true);
        query.addFacetPivotField("item_brand,item_category");
        QueryResponse rsp = solrClient.query(query);
        NamedList<List<PivotField>> facetPivot = rsp.getFacetPivot();
    }

4 SolrJ拼写检查和建议词

 4.1拼写检查solrconfig.xml

注意:/select 是默认查询  还要加上这个    startup="lazy"

拼写检查:只支持英文搜索,不支持中文。中文没有建议的
 <!--拼写纠错 插件 -->
  <searchComponent name="spellcheck" class="solr.SpellCheckComponent">
      <!-- 复制域:配置的type-->
    <str name="queryAnalyzerFieldType">text_pinyin</str>
    <!-- 可以声明并使用多个“拼写检查器”组成部分-->
    <!-- 从主索引的字段构建的拼写检查器 ,默认的校正器,只对主索引库管用 进行拼写检查 -->
    <lst name="spellchecker">
      <!-- 拼音纠错插件中检查器的名称:别人可以引用 代码中也要引用才知道你用哪个检查器-->
      <str name="name">mySpellcheck</str>
        <!-- 复制域:配置的name-->
      <str name="field">item_title</str>
      <str name="classname">solr.DirectSolrSpellChecker</str>
      <!-- 使用的拼写检查距离度量,默认为内部levenshtein -->
      <str name="distanceMeasure">internal</str>
      <!-- 被视为有效拼写检查建议所需的最低准确性 -->
        <!-- 被认为是有效的 spellcheck suggestion的最小精度值,介于0和1之间的值。值越大,匹配的结果数越少。个人理解:假设对 cit进行拼写检查,
         如果此值设置够小的话,"fat","sit"都会被认为是"cit"的正确的拼写。如果此值设置够大,则"sit"会被认为是正确的拼写 -->
      <float name="accuracy">0.5</float>
      <!-- 枚举术语时考虑的最大编辑数:可以是1或2 -->
       <!-- 值为 1 或者2. 指示最多有几个字母变动。比如:对"manag"进行拼写检查,则会找到"manager"做为正确的拼写;如果对"mana"进行
           拼写检查,因为"mana"到"manager",需有3个字母的变动,所以"manager"会被遗弃  -->
      <int name="maxEdits">2</int>
      <!-- 枚举术语时的最小共享前缀 -->
        <!-- 最小的前辍数。如设置为1,意思是指第一个字母不能错。比如:输入"cinner",虽然和"dinner"只有一个字母的编辑距离,
           但是变动的是第一个字母,所以"dinner"不是"cinner"的正确拼写 -->
      <int name="minPrefix">1</int>
      <!-- 每个结果的最大检查次数 -->
      <int name="maxInspections">5</int>
      <!-- 要考虑更正的查询项的最小长度 -->
        <!-- 进行拼写检查所需要的最小的字母数。此处设置为4,表示如果只输入了3个字母,则不会进行拼写检查(3个字母的单词都会写错的话,我也无语了) -->
      <int name="minQueryLength">4</int>
      <!-- 可考虑更正的查询项的文档的最大阈值 -->
      <float name="maxQueryFrequency">0.01</float>
      <!-- 取消注释,要求建议出现在1%的文档中
        <float name="thresholdTokenFrequency">.01</float>
      -->
    </lst>

    <!-- 可以拆分或组合单词的拼写检查器. 有效解决了空格 看 "/spell" 下面的处理程序供使用 -->
    <lst name="spellchecker">
       <!-- 拼音纠错插件中检查器的名称:别人可以引用 -->
      <str name="name">wordbreak</str>
      <str name="classname">solr.WordBreakSolrSpellChecker</str>
        <!-- 复制域:配置的name-->
      <str name="field">item_title</str>
      <str name="combineWords">true</str>
      <str name="breakWords">true</str>
      <int name="maxChanges">10</int>
    </lst>
  </searchComponent>

WordBreakSolrSpellChecker(增强拼写检查器)

可以通过组合相邻的查询词,或者查询词分解成多个词,来提供建议.是对searchComponent的增强。通常和DirectSolrSpellChecker组合使用 -很有效的解决了搜索带空格的情况

如上面的代码已经写完了,直接复制即可使用----->DirectSolrSpellChecker

<requestHandler name="/select" class="solr.SearchHandler" startup="lazy">
     <lst name="defaults">
       <str name="echoParams">explicit</str>
       <int name="rows">10</int>
       <str name="df">text</str>
         <!-- 自己添加的 -->
        <str name="spellcheck">true</str>
        <str name="spellcheck.dictionary">mySpellcheck</str>
        <str name="spellcheck.count">10</str>
     </lst>
      <arr name="last-components">
             <!--   自动检查器添加 -->
        <str>spellcheck</str>
        <!--   自动建议组件添加 -->
        <str>suggest</str>
      </arr>
    </requestHandler>
  //配置在 /select方式
        SolrQuery querySelect = new SolrQuery();
        querySelect.setQuery("item_title:Lenova");
        querySelect.set("spellcheck",true);
        QueryResponse rsp = solrClient.query(querySelect);
        SpellCheckResponse spellCheckResponse = rsp.getSpellCheckResponse();
        Map<String, SpellCheckResponse.Suggestion> suggestionMap = spellCheckResponse.getSuggestionMap();
        for (String key : suggestionMap.keySet()) {
            SpellCheckResponse.Suggestion suggestion = suggestionMap.get(key);
            //建议词集合
            List<String> alternatives = suggestion.getAlternatives();
        }
//spell方式 有空研究一下

4.2 自动建议

solrconfig.xml  :别忘记 select/ 那也要配置自动建议器。拼音纠错那已经配置了,可以直接粘贴

<!-- 自动建议器:对用户输入的关键字进行预测和建议 -->
  <searchComponent name="suggest" class="solr.SuggestComponent">
    <lst name="suggester">
     <!-- 自动建议器名称,需要查询的时候指定 -->
      <str name="name">mySuggester</str>
       <!-- 查找器工厂类,确定如何从字典索引中查找 -->
      <str name="lookupImpl">FuzzyLookupFactory</str>      <!-- org.apache.solr.spelling.suggest.fst -->
       <!-- 决定如何将词存入到索引 -->
      <str name="dictionaryImpl">DocumentDictionaryFactory</str>   

       <!-- org.apache.solr.spelling.suggest.HighFrequencyDictionaryFactory -->
       <!-- 词典域,一般指定查询的域/复制域 item_keyword 范围太大了,一般就是商品名称+三级分类 这个是我自己新建的复制域   -->
      <str name="field">item_good_name</str>
        <!-- 权重域,这里是将价格作为权重 -->
      <str name="weightField">item_price</str>
       <!-- 不能配置拼音分词器,报错 -->
      <str name="suggestAnalyzerFieldType">text_ik</str>
        <!-- 
          1 下面两个配置可以配置文件配置也可以代码配置
          2 文件中配置:启动tomcat就会构建数据 ,代码中就不加了,优化速度。 缺点,item_good_name 数据变化 他感知不到,搜索还是旧数据
          3 代码中加上下面配置,和上面相反 
          -->
     <!--  <str name="buildOnStartup">true</str> -->
     <!--  <str name="buildOnCommit">true</str> -->
    </lst>
  </searchComponent>

   @Test
    public void querysuggest() throws Exception {
        SolrQuery query = new SolrQuery();
        query.setQuery("diannao56");
        query.set("suggest",true);
        query.set("qt", "/suggest");
        query.set("suggest.build", "true");
        query.set("suggest.dictionary","mySuggester");
        query.set("suggest.count",5);
        QueryResponse rsp = solrClient.query(query);
        SuggesterResponse suggesterResponse = rsp.getSuggesterResponse();
        //获取自动建议数据
        Map<String, List<Suggestion>> suggestions = suggesterResponse.getSuggestions();
        List<Suggestion> mySuggester = suggestions.get("mySuggester");
    }

4.3外部文件方式拼音纠错(部分资料,以后补充)

SpellCheckComponent - Solr - Apache Software Foundation

Spell Checking | Apache Solr Reference Guide 7.1

 solrHome/solrcore/conf/solrconfig.xml

 FileBasedSpellChecker:外部文件方式拼写检查

使用外部文件方式作为拼写检查字的词典,基于词典中的词进行拼写检查。

1 在solrconfig.xml中配置拼写检查的组件 (可以有多个searchComponent标签)
注意:

       <str name="accuracy">0.5</str> 有的时候要str 有的时候要float。只要报类型异常就换

       <float name="accuracy">0.5</float>

  <!--自己配置的从文件中读取单词列表的拼写检查器 拼写纠错 插件 -->
  <searchComponent name="filecheck" class="solr.SpellCheckComponent">
      <!-- 复制域:配置的type-->
    <str name="queryAnalyzerFieldType">text_ik</str>
    <!-- 可以声明并使用多个“拼写检查器”组成部分-->
    <!-- 从主索引的字段构建的拼写检查器 ,默认的校正器,只对主索引库管用 进行拼写检查 -->
    <lst name="spellchecker">
       <!-- 这个插件的名称-->
      <str name="name">file</str>
      <!-- solrhome/solrcore/conf/spellings.txt-->
      <str name="sourceLocation">spellings.txt</str>
      <str name="characterEncoding">UTF-8</str>
      <str name="spellcheckIndexDir">spellcheckerFile</str>
        <!-- 复制域:配置的name-->
      <str name="field">item_keyword</str>
      <str name="classname">solr.FileBasedSpellChecker</str>
      <!-- 使用的拼写检查距离度量,默认为内部levenshtein -->
      <str name="distanceMeasure">org.apache.lucene.search.spell.LevensteinDistance</str>
        <!-- 精度值,介于0和1之间的值。值越大,匹配的结果数越少。假设对 naike进行拼写检查,
         如果此值设置够小的话,"naik","naikek"都会被认为是"naike"的正确的拼写。
         如果此值设置够大,则"naike"才会被认为是正确的拼写 -->
 <!-- 注意这个标签 自己创建时就得写str,二自带的那个就是 float 。 -->
      <str name="accuracy">0.5</str>
      <!-- 枚举术语时考虑的最大编辑数:可以是1或2 -->
       <!-- 值为 1 或者2. 指示最多有几个字母变动。比如:对"manag"进行拼写检查,则会找到"manager"做为正确的拼写;如果对"mana"进行
           拼写检查,因为"mana"到"manager",需有3个字母的变动,所以"manager"会被遗弃  -->
      <int name="maxEdits">2</int>
      <!-- 枚举术语时的最小共享前缀 -->
        <!-- 最小的前辍数。如设置为1,意思是指第一个字母不能错。比如:输入"cinner",虽然和"dinner"只有一个字母的编辑距离,
           但是变动的是第一个字母,所以"dinner"不是"cinner"的正确拼写 -->
      <int name="minPrefix">1</int>
      <!-- 每个结果的最大检查次数 -->
      <int name="maxInspections">5</int>=
        <!-- 进行拼写检查所需要的最小的字母数。此处设置为4,表示如果只输入了3个字母,则不会进行拼写检查-->
      <int name="minQueryLength">4</int>
      <!-- 被推荐的词在文档中出现的最小频率,整数表示在文档中出现的次数,百分比数表示有百分之多少的文档出现了该推荐词 -->
      <float name="maxQueryFrequency">0.01</float>
      <!-- 取消注释,要求建议出现在1%的文档中
        <float name="thresholdTokenFrequency">.01</float>
      -->
    </lst>     
  </searchComponent>

2 在solrhome/solrcore/conf/spellings.txt 创建这个文件

 3 在执行时机(/select)添加引用即可:

 <arr name="last-components">
        <str>filecheck</str>
      </arr>

5 solrTemplate(重点)

5.1 京东为例:

5.1.1 搜索电脑手机

  1.   第二次搜索时品牌和属性出现的比sku快 --->品牌和属性一定是在缓存里
  2.   电脑和手机都有屏幕尺寸,为什么只有手机的值呢? 很有可能是map(规格的返回数据类型),而且还没有合并属性key一样的数据。

5.1.2 搜索建议,和纠错个人实现很难,目前只满足搜索

5.2 配置

  data:
    solr:
      host: http://127.0.0.1:8080/solr

    @Autowired
    private SolrTemplate solrTemplate;

5.3 新增/更新/删除

        Item item = new Item();
        item.setId(2L);
        item.setSku(2L);
        item.setTitle("飞利浦(PHILIPS) 手电筒强光手电 Type-C充电多功能家用便携小型户外骑行停电照明应急灯SFL1236黑色 Y");
        item.setPrice(Double.valueOf("9000.00"));
        item.setCategory("户外照明");
        item.setSeller("飞利浦(PHILIPS)京东自营店");
        item.setBrand("飞利浦(PHILIPS)");
        item.setDelete(false);
        item.setShow(true);
        Map<String, String> hashMap = new HashMap<>();
        hashMap.put("光源类型","LED");
        hashMap.put("防水强度","防水");
        //动态域
        item.setSpecMap(hashMap);
        item.setUptime(new Date());
        solrTemplate.saveBean("collection1",item);
        solrTemplate.commit("collection1"); //提交
        //根据条件删除(满足条件的都会删除) 很慢
        Query query = new SimpleQuery("*:*");
        solrTemplate.delete("collection1",query);
        // 根据id删除 快
        solrTemplate.deleteByIds("collection1","1");
        List<String> ids = new ArrayList<>();
        solrTemplate.deleteByIds("collection1",ids);
        solrTemplate.commit("collection1");

5.4 查询

import lombok.Data;
import java.util.Map;

/**
 * 接收前端传来的搜索数据
 */
@Data
public class QueryKeyWords {
    /**
     * 搜索框中的数据
     */
    private String keyWords;
    /**
     * 分类条件查询
     */
    private String category;
    /**
     * 品牌条件查询
     */
    private String brand;
    /**
     * 规格条件查询
     */
    private Map<String,String> specMap;
    /**
     * 价格区间条件查询 0-500
     */
    private String priceRange;
    /**
     * 价格升序降序 前端传值 ASC升序, DESC降序
     */
    private String priceSort;
    private int pageNo=1;//当前页
    private int pageSize=20;//每页个数
}
import lombok.Data;
import java.util.List;
import java.util.Map;

/**
 * 首页搜索展示数据
 */
@Data
public class ShowVo {
    /**
     * sku集合
     */
    private List<Item> goodsVoList;
    /**
     * 品牌集合
     */
    private List<String> brandVoList;
    /**
     * 品牌集合
     */
    private Map<String,List<String>> specVoList;
    //总页数
    private int totalPages;
    //总数
    private long totalElements;
}
 @Test
    public void queryShow() {
        //1 模仿前端动态传参
        QueryKeyWords queryKeyWords = new QueryKeyWords();
        queryKeyWords.setKeyWords("电脑");//搜索框的关键词
        queryKeyWords.setCategory("笔记本");//分类过滤
        Map<String, String> hashMap = new HashMap<>();
        hashMap.put("颜色", "红色");
        hashMap.put("机身内存", "16g");
        queryKeyWords.setSpecMap(hashMap);//属性过滤
        queryKeyWords.setPriceRange("1-9000");//价格区间过滤
        queryKeyWords.setPriceSort("DESC");//价格降序
        //2 响应结果封装
        ShowVo showVo = new ShowVo();
        //3 搜索框+条件过滤
        showVo.setGoodsVoList(searchHightlight(queryKeyWords));
        //4 分组查询三级分类,因为三级分类关联->(品牌,规格)
         // 假如说我搜索关键字是 (手机电脑)有两个三级分类呢?
        List<String> categoryList = searchGroupCategory(queryKeyWords);
        // 4 分组查询(品牌 +属性) 选择了一个条件,页面也会关闭一个选项
        // 4.1 品牌:["三星","小米"]
        showVo.setBrandVoList(searchBrandList(categoryList));
        showVo.setSpecVoList(searchSpecList(categoryList));
        System.out.println(showVo.getGoodsVoList());

    }

/**
     * 分页查询高亮sku数据(商品数据)
     * @param queryKeyWords
     * @return
     */
    private List<Item> searchHightlight(QueryKeyWords queryKeyWords){
        //2 高亮查询-> 哪个域要进行高亮显示
        SimpleHighlightQuery query = new SimpleHighlightQuery();
        HighlightOptions highlightOptions = new HighlightOptions().addField("item_title").addField("item_category3").addField("item_image_url").addField("item_seller");//设置高亮域
        highlightOptions.setSimplePrefix("<em style='color:red'>");//前缀
        highlightOptions.setSimplePostfix("</em>");//后置
        query.setHighlightOptions(highlightOptions);
        //3 关键字查询
        Criteria highlightCriteria = new Criteria("item_keyword").is(queryKeyWords.getKeyWords());
        query.addCriteria(highlightCriteria);

        FilterQuery filterDefault = new SimpleFilterQuery();
        Criteria CriteriaDefault = new Criteria("item_delete").is(true).and("item_show").is(true);
        filterDefault.addCriteria(CriteriaDefault);
        //添加过滤查询条件
        query.addFilterQuery(filterDefault);

        //4.1 按商品分类过滤查询
        if(StringUtils.isNotBlank(queryKeyWords.getCategory())){
            FilterQuery filterQuery = new SimpleFilterQuery();
            Criteria filterCriteria = new Criteria("item_category3").is(queryKeyWords.getCategory());
            filterQuery.addCriteria(filterCriteria);
            //添加过滤查询条件
            query.addFilterQuery(filterQuery);
        }
        //4.2品牌过滤
        if(StringUtils.isNotBlank(queryKeyWords.getBrand())){
            FilterQuery filterQuery = new SimpleFilterQuery();
            Criteria filterCriteria = new Criteria("item_brand").is(queryKeyWords.getBrand());
            filterQuery.addCriteria(filterCriteria);
            //添加过滤查询条件
            query.addFilterQuery(filterQuery);
        }
        //4.3属性过滤
        if(CollectionUtil.isNotEmpty(queryKeyWords.getSpecMap())){
            Map<String,String> specMap = queryKeyWords.getSpecMap();
            for (String key : specMap.keySet()) {
                FilterQuery filterQuery = new SimpleFilterQuery();
                //动态域 item_spec_*   "item_spec_颜色":"红色"
                Criteria filterCriteria = new Criteria("item_spec_"+key).is(specMap.get(key));
                filterQuery.addCriteria(filterCriteria);
                //添加过滤查询条件
                query.addFilterQuery(filterQuery);
            }
        }
        //4.4、按价格过滤 (京东的价格区间,前端用了正则了大于0的正整数)
        if(StringUtils.isNotBlank(queryKeyWords.getPriceRange())){
            String priceRange = queryKeyWords.getPriceRange();
            String[] price = priceRange.split("-");
            if(StringUtils.isNotBlank(price[0])){ //如果最低价格不等于0
                FilterQuery filterQuery = new SimpleQuery();
                Criteria filterCriteria = new Criteria("item_price").greaterThanEqual(price[0]);
                filterQuery.addCriteria(filterCriteria);
                query.addFilterQuery(filterQuery);
            }
            if(StringUtils.isNotBlank(price[1])){ //如果最高价格不等于*,则限制最高价格
                FilterQuery filterQuery = new SimpleQuery();
                Criteria filterCriteria = new Criteria("item_price").lessThanEqual(price[1]);
                filterQuery.addCriteria(filterCriteria);
                query.addFilterQuery(filterQuery);
            }
        }
        //5分页查询
        query.setOffset(Long.valueOf((queryKeyWords.getPageNo()-1)*queryKeyWords.getPageSize()));//索引位设置
        query.setRows(queryKeyWords.getPageSize());
        // 6价格排序 同理-(评论数,销量...)
        if(StringUtils.isNotBlank(queryKeyWords.getPriceSort())) {
            if (Sort.Direction.DESC.equals(queryKeyWords.getPriceSort())) {
                Sort sort = new Sort(Sort.Direction.DESC, "item_price");
                query.addSort(sort);
            } else {
                Sort sort = new Sort(Sort.Direction.ASC, "item_price");
                query.addSort(sort);
            }
        }

        //高亮显示页
        HighlightPage<Item> page = solrTemplate.queryForHighlightPage("collection1", query, Item.class);
        //高亮入口集合(每条记录的高亮入口)
        List<HighlightEntry<Item>> highlighted = page.getHighlighted();
        for (HighlightEntry<Item> highlightEntry : highlighted) {
            //获取原生对象
            Item entity = highlightEntry.getEntity();
            //高亮域的个数 可以设置多个域
            List<HighlightEntry.Highlight> highlights = highlightEntry.getHighlights();
            //方式一设置的高亮域是多个的情况(都适用)
            for (HighlightEntry.Highlight highlight : highlights) {
                //域的名称
                String fieldName = highlight.getField().getName();
                //如果该域配置  multiValued="true" 是可能多值, 没有配置就是一个值, 目前看好像除了复制域会配置这个属性,其他域好像没有
                List<String> snipplets = highlight.getSnipplets();
                //高亮域设置了几个,这就写几个
                switch (fieldName) {
                    case "item_title":
                        entity.setTitle(snipplets.get(0));
                        break;
                    case "item_category3":
                        entity.setCategory(snipplets.get(0));
                        break;
                    case "item_image":
                        entity.setImage(snipplets.get(0));
                        break;
                    default:
                        break;
                }
            }
            //方式二设置的高亮域是一个的情况(简写)
      /*     if(null !=highlights &&highlights.size()>0){
               Item entity = entry.getEntity();
               //覆盖原生的标题  <em style='color:red'>手机</em> 覆盖    手机
               entity.setTitle(highlights.get(0).getSnipplets().get(0));
           }*/
        }
        List<Item> content = page.getContent();//高亮后的结果
        return content;
    }

 /**
     * 分组查询所有数据得三级分类名称:根据查询出的三级分类可以缓存 品牌,规格  (三 级分类关联品牌,规格  )
     * @return
     */
    private List<String> searchGroupCategory(QueryKeyWords queryKeyWords){
        List<String> list = new ArrayList();
        Query query = new SimpleQuery("*:*");
        Criteria criteria = new Criteria("item_keyword").is(queryKeyWords.getKeyWords());
        query.addCriteria(criteria);
        // 请注意,分组查询,必须指定 offset/limit, 否则会抛异常,Pageable must not be null! (源码问题,必须有参数,但是设置多少无所谓,不影响结果)
   /*     query.setOffset(0L);
        query.setRows(2);*/
        //根据哪个域进行分组,相当于group by item_category3 所以item_category3域 必须是string;;
        GroupOptions groupOptions = new GroupOptions().addGroupByField("item_category3").setOffset(0).setLimit(1);
        query.setGroupOptions(groupOptions);
        //获取分组页
        GroupPage<Item> page = solrTemplate.queryForGroupPage("collection1",query,Item.class);
        //page.getGroupResult(String arg0);取其中一个addGroupByField结果
        GroupResult<Item> groupResult = page.getGroupResult("item_category3");
        //获取分组入口页
        Page<GroupEntry<Item>> groupEntries = groupResult.getGroupEntries();
        //获取分组入口集合
        List<GroupEntry<Item>> entryList = groupEntries.getContent();
        for(GroupEntry<Item> entry :entryList){
            list.add(entry.getGroupValue());
        }
        return list;

    }

    /**
     * 根据商品分类名称查询所有品牌
     * @param categoryList 商品分类名称集合
     * @return
     */
    private List<String> searchBrandList(List<String> categoryList) {
        //1.获取品牌 根据三级分类/或者模板id  获取品牌
      //  List brandList = (List) redisTemplate.boundHashOps("category").get("brandList");
      //  return brandList;
        return new ArrayList<>();
    }
    /**
     * 根据商品分类名称查询所有规格列表 (可能不同分类下,规格名重复问题)
     * @param categoryList 商品分类名称集合
     * @return
     */
    private Map<String,List<String>> searchSpecList(List<String> categoryList){
        Map<String,List<String>> map = new HashMap<>();
        // 屏幕尺寸:["7英寸以上","6.8-7.0英寸","6.0英寸一下"] (手机电脑都有屏幕尺寸这个规格)
        //获取规格列表  根据三级分类获取 规格和规格属性
     //   Map specList = (Map) redisTemplate.boundHashOps("category").get("specList");
        return map;
    }

5.5 拼音纠错

 /**
     * 模拟建议  
     *  注意如果item_title 配置了 拼音分词+ik分词 ,那基本都会查询到数据即使你查询的是错误,因为有拼音分词
     */
    @Test
    public void query2() {
        Query query = new SimpleQuery("*:*");
        Criteria criteria = new Criteria("item_title").is("Lenov44");
        query.addCriteria(criteria);
        //启用拼写检查SpellcheckOptions
        query.setSpellcheckOptions(SpellcheckOptions.spellcheck()
                .collate()//响应包含索引中未找到的单词以及替代词。spellcheck.collat​​e=true会导致返回原始查询的修改版本以及最可能的替代方案
                //拼写检查器名称
                .dictionaries("mySpellcheck")
                //设置要返回的最大建议数。
                .count(5)
                //启用扩展结果,包括词频等。
                .extendedResults());
        //设置请求处理程序,它必须能够处理建议
        query.setRequestHandler("/select");
        SpellcheckedPage<Item> page = solrTemplate.query("collection1", query, Item.class);
        //如果没有查询到数据 ,查看建议数据
        //注意如果item_title 配置了 拼音分词+ik分词 ,那基本都会查询到数据即使你查询的是错误,因为有拼音分词
        long totalElements = page.getTotalElements();
        if(totalElements==0){
            Collection<SpellcheckQueryResult.Alternative> alternatives = page.getAlternatives();
            for (SpellcheckQueryResult.Alternative alternative : alternatives) {
                //错误的词
                String term = alternative.getTerm();
                //建议的词
                String suggestion = alternative.getSuggestion();
            }
        }
    }

5.6 自动建议

 目前没有找到 替代方式  solrj

6solr高级api

 集群配置:单机也行

    @Bean("cloudSolrClient")
    public CloudSolrClient cloudSolrClient() {
        //zk地址
        List<String> zkHosts = new ArrayList<>();
        zkHosts.add("192.168.135.128:2181");
        zkHosts.add("192.168.135.128:2182");
        zkHosts.add("192.168.135.128:2183");
        CloudSolrClient.Builder builder = new                                                                            CloudSolrClient.Builder().withZkHost(zkHosts);
        CloudSolrClient cloudSolrClient = builder.build();
        cloudSolrClient.setDefaultCollection("collection1");
        return cloudSolrClient;
    }

用pinyin4j来实现拼音分词

Chinese to Pinyin download | SourceForge.net

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值