背景:
一周前接到个紧急需求,项目PC端商品搜索要搞智能搜索,一个输入框,随便输入SPU码或者SKU码或者商品名称都能智能识别用户搜索的是啥,返回搜索结果。问了组长,这个要加入搜索引擎咯。这。。。我没搞过啊,产品还说要紧急上的,我想了下,先搞个临时方案吧,前端用正则识别用户输入的是中文或者英文时,传商品名称这个字段给我搜索,是数字的就传spu、sku给我。这个临时方案就是sql模糊搜索咯。然后上了这个,组长又说,搜索引擎还是要搞的,后面商品多了,搜索速度肯定有问题。那行吧,反正最近没需求,我研究下ES,预算时间是两周的,后来边看API边看网上大神们的实例,一周搞完了,现在勉强用着,不知道后面会不会有问题。下面开搞
项目里是在Linux下装的es,我这里在win10下装的,作为demo示例,额外加一下windows下安装ES:
1.下载并解压ES压缩包,我用的是6.4.3版本(7.0的版本需要设置密码): https://www.elastic.co/cn/downloads/past-releases
(官网下载很慢,有个老哥整理了下,不全仅供参考:https://blog.csdn.net/weixin_37281289/article/details/101483434 )
2.下载中文分词器及安装,参考一下人家的: https://blog.csdn.net/liuzhenfeng/article/details/39404435
在ik的github:https://codeload.github.com/medcl/elasticsearch-analysis-ik/zip/master 里也有说明怎么操作:
解释一下,第一步下载或者编辑ik插件。第一步有两个选择
1是到 https://github.com/medcl/elasticsearch-analysis-ik/releases 下载ik插件,注意ik的版本一定要和ES的版本相同,我使用的是6.4.3,下载的ik也要6.4.3。下载后在es解压目录下的plugins创建文件夹,重命名为ik,然后将下载的ik插件解压到刚才的ik文件夹里。
2是直接使用elasticsearch-plugin去安装,这个没尝试,应该是Linux下的操作。
ES和ik都下载解压后,进入ES的bin文件夹,点击elasticsearch.bat启动ES,启动成功界面,且ik也加载了:
浏览器输入:http://localhost:9200,
ok,ES启动成功。
创建test索引,type为person,索引和type都是自定义:
{
"settings": {
"analysis": {
"analyzer": {
"ik": {
"tokenizer": "ik_smart"
}
}
}
},
"mappings": {
"person": {
"dynamic": true,
"properties": {
"name": {
"type": "text",
"analyzer": "ik_smart",
"search_analyzer": "ik_smart"
},
"author": {
"type": "text",
"analyzer": "ik_smart",
"search_analyzer": "ik_smart"
}
}
}
}
}
这里指定中文分词器和搜索字段分词方式为ik_smart,关于分词方式的了解,参考:https://www.jianshu.com/p/914f102bc174
删除索引 DELETE请求 http://localhost:9200/test
下面配置项目:
ES版本用的是6.4.3,客户端用的是第三方的Jest 6.3
pom.xml引入:
org.elasticsearch
elasticsearch
6.4.3
io.searchbox
jest
6.3.1
yml配置文件:
spring:
elasticsearch:
jest:
uris: http://localhost:9200
read-timeout: 5000
connection-timeout: 5000
demo实体类:
public class Person {
private String name;
private String age;
private String desc;
//getter and setter...
}
增加文档内容,post restful,json内容和实体类字段对应,可以指定ID去添加,如果没指定ID,由ES自动分配:
查询所有插入的内容:
短语查询,根据字段匹配过滤:
查看索引的映射以及字段分词器 ,可以看到name和desc字段是按智能分词:
查看索引/类型/id的某字段的分词情况 http://localhost:9200/test/person/1/_termvectors?fields=desc:
ok,基础数据准备好了,下面结合java获取ES的数据,其实使用ES就像使用Redis这种no sql数据库一样:
@RestController
@RequestMapping("/test")
public class TestController {
@GetMapping("/person/query")
public List query() {
//组装查询参数
JSONObject json1=new JSONObject();
JSONObject json2=new JSONObject();
JSONObject json3=new JSONObject();
json3.put("desc", "是");//查询的字段和值
json2.put("match_phrase", json3);//匹配方式:短语匹配
json1.put("query", json2);
List personlList=new ArrayList();
Search search = new Search.Builder(json1.toJSONString()).addIndex("test").addType("person").build();
try {
SearchResult result = jestClient.execute(search);
List> hits = result.getHits(Person.class);
for (Hit hit : hits) {
Person person = hit.source;
personlList.add(person);
}
return personlList;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
我使用的这个版本的ES的XContentBuilder的string()方法被废弃了,没研究怎么构造json,我直接用几个json对象去拼接查询参数了,有点尴尬。这里就是拼接好查询参数,指定index和type去查询,将查询结果拿出来转成对应的bean。对于匹配方式的介绍,可以在官网API,《Elasticsearch: 权威指南》了解下:https://www.elastic.co/guide/cn/elasticsearch/guide/current/search-in-depth.html
总体大概是上面这些内容了,总的来说,就是新建索引并设置mapping分词方式,然后将数据初始化到ES,然后再根据要查询的字段组装查询的json,用ES的客户端连接ES进行查询,最后将结果组装返回就可以啦。ES的客户端有好几种, Java High Level Rest Client、 Java Low Level Rest Client、Jest。至于要使用哪种,看安装的ES版本以及自己操作喜欢...我是只找到Jest的看着简单点就用了。
我实际项目的思路是这样的,将商品的名称、标题、spu码、sku码、规格值等用户可能会搜索的信息用空格拼接到一个String字段,然后商品的ID作为文档的ID,商品详细信息的bean作为文档的主体。每天凌晨1点定时任务将上架的商品循环插入ES,在搜索的接口根据查询的字段拼接查询的json进行查询。在商品上架时就插入ES,下架商品就删除对应ID的文档,额,感觉有点像维护Reids缓存一样。
后面来点干货吧,一些工具类:
/**
* 索引中数据的操作(增删改查)
**/
public interface JestDataBaseService {
/**
* 单条删除
*/
boolean deleteItem(String index, String type, String id);
/**
* 批量创建索引
*/
void batchIndex(String index, String type, List list);
/**
* 单条索引(新增/更新)
*/
void singleIndex(String index, String type, T t);
/**
* 指定索引ID
*/
void singleIndexWithId(String index, String type, String id, T t);
/**
* 根据id查询
*/
T queryById(String index, String type, String id, Class clazz);
}
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.rondaful.cloud.commodity.service.JestDataBaseService;
import java.io.IOException;
import java.util.List;
import javax.annotation.Resource;
import io.searchbox.client.JestClient;
import io.searchbox.client.JestResult;
import io.searchbox.core.Bulk;
import io.searchbox.core.Delete;
import io.searchbox.core.Get;
import io.searchbox.core.Index;
/**
* elasticsearch通用操作
**/
public class JestDataBaseServiceImpl implements JestDataBaseService {
private final static Logger log = LoggerFactory.getLogger(JestDataBaseServiceImpl.class);
@Resource
private JestClient jestClient;
@Override
public boolean deleteItem(String index, String type, String id) {
try {
JestResult jestResult = jestClient.execute(new Delete.Builder(id).index(index).type(type).refresh(true).build());
if (!jestResult.isSucceeded()) {
log.error("deleteItem error:{}", jestResult.getErrorMessage());
}
return jestResult.isSucceeded();
} catch (IOException e) {
log.error("deleteItem error", e);
}
return false;
}
@Override
public void batchIndex(String index, String type, List list) {
try {
Bulk.Builder builder = new Bulk.Builder();
for (T t : list) {
builder.addAction(new Index.Builder(t).index(index).type(type).build());
}
JestResult jestResult = jestClient.execute(builder.build());
if (!jestResult.isSucceeded()) {
log.error("batchIndex error:{}", jestResult.getErrorMessage());
}
} catch (IOException e) {
log.error("batchIndex error", e);
}
}
@Override
public void singleIndex(String index, String type, T t) {
try {
JestResult jestResult = jestClient.execute(new Index.Builder(t).index(index).type(type).build());
if (!jestResult.isSucceeded()) {
log.error("singleIndex error:{}", jestResult.getErrorMessage());
}
} catch (IOException e) {
log.error("singleIndex error", e);
}
}
@Override
public void singleIndexWithId(String index, String type, String id, T t) {
try {
JestResult jestResult = jestClient.execute(new Index.Builder(t).index(index).type(type).id(id).build());
if (!jestResult.isSucceeded()) {
log.error("singleIndexWithId error:{}", jestResult.getErrorMessage());
}
} catch (IOException e) {
log.error("singleIndexWithId error", e);
}
}
@Override
public T queryById(String index, String type, String id, Class clazz) {
T result = null;
try {
Get get = new Get.Builder(index, id).type(type).build();
JestResult jestResult = jestClient.execute(get);
result = jestResult.getSourceAsObject(clazz);
} catch (IOException e) {
log.error("queryById error", e);
}
return result;
}
}
参考资料:
ES安装:http://www.mamicode.com/info-detail-2190390.html
分词器:https://www.jianshu.com/p/914f102bc174
Jest操作:https://blog.csdn.net/u010466329/article/details/75020956
Elasticsearch: 权威指南:https://www.elastic.co/guide/cn/elasticsearch/guide/current/index.html
Elasticsearch参考手册:https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html
小鱼的博客:http://www.gaowm.com/categories/Elasticsearch/
huitoudou的博客:https://blog.csdn.net/u014315200/article/details/78630284
docker上安装ES镜像:https://blog.csdn.net/weixin_38229356/article/details/84574416