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 删除:
注意:
- 通过id删除只能作用于id域,id域必须存值。
- 通过哪个域作为条件删除,哪个域就必须有该属性 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查询
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 搜索电脑手机
- 第二次搜索时品牌和属性出现的比sku快 --->品牌和属性一定是在缓存里
- 电脑和手机都有屏幕尺寸,为什么只有手机的值呢? 很有可能是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.collate=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来实现拼音分词