微服务SpringCloudAlibaba springboot整合ElasticSearch实现商品搜索功能
微服务SpringCloudAlibaba springboot整合ElasticSearch实现商品搜索功能
·
话不多说直接开始
首先在模块中添加search模块(搜索)
service-goods(商品)
service-search(搜索)
service-wms(仓储)
加入依赖
<!--elasticsearch版本 7.4.2 -->
<properties>
<elasticsearch.version>7.4.2</elasticsearch.version>
</properties>
<!--引入依赖: elasticsearch-rest-high-level-client -->
<dependencies>
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>7.4.2</version>
</dependency>
创建几个包
创建ES的配置类ElasticSearchConfig
@Configuration
@ConfigurationProperties(prefix = "es")
@Setter
public class ElasticSearchConfig {
private String hostname;
private int port;
private String protocol;
//请求的一些选项
public static final RequestOptions COMMON_OPTIONS;
static{
RequestOptions.Builder builder=RequestOptions.DEFAULT.toBuilder();
COMMON_OPTIONS=builder.build();
}
@Bean
public RestHighLevelClient restHighLevelClient(){
RestClientBuilder restClientBuilder=
RestClient.builder(new HttpHost(hostname,port,protocol));
RestHighLevelClient restHighLevelClient=
new RestHighLevelClient(restClientBuilder);
return restHighLevelClient;
}
}
在创建ES的工具类 可以直接调用其中的我封装好的方法
- 索引ES保存数据
- 主键新增的数据
- 根据ID删除单条记录
- 更新数据
- 根据ID从ES中查询数据
- 查选条件构造器
- 使用form+size的方式实现ES分页查询
- 使用scroll实现ES分页查询
@Component
@Slf4j
public class ESUtil {
@Resource
private RestHighLevelClient restHighLevelClient;
/**
* @param index 索引
* @param bachList 保存的数据
* @param <T>
* @return
*/
public <T extends Object> boolean saveBatch(String index,List<T> bachList){
BulkRequest bulkRequest=new BulkRequest();
//封装保存的数据
for(int i=0;i<bachList.size();i++){
T t= bachList.get(i);
String jsonString= JSON.toJSONString(t);
IndexRequest indexRequest = new IndexRequest();
indexRequest.index(index);
indexRequest.source(jsonString,XContentType.JSON);
bulkRequest.add(indexRequest);
}
try {
restHighLevelClient.bulk(bulkRequest,ElasticSearchConfig.COMMON_OPTIONS);
return true;
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
/**
* ES保存数据
*
* @param index 索引
* @param id 主键
* @param jsonString 新增的数据
* @return
*/
public boolean saveOne(String index, String id, String jsonString) {
//封装保存的数据
IndexRequest indexRequest = new IndexRequest();
indexRequest.index(index);
indexRequest.id(id);
indexRequest.source(jsonString, XContentType.JSON);
try {
//执行新增操作
IndexResponse indexResponse =
restHighLevelClient.index(indexRequest, ElasticSearchConfig.COMMON_OPTIONS);
log.info("新增的结果:" + indexResponse);
return true;
} catch (IOException e) {
e.printStackTrace();
return false;
}
}
/**
* 根据ID删除单条记录
*
* @param index
* @param id
* @return
*/
public boolean deleteById(String index, String id) {
DeleteRequest deleteRequest = new DeleteRequest();
deleteRequest.index(index);
deleteRequest.id(id);
try {
DeleteResponse deleteResponse =
restHighLevelClient.delete(deleteRequest,
ElasticSearchConfig.COMMON_OPTIONS);
log.info("删除的结果:" + deleteResponse);
return true;
} catch (IOException e) {
e.printStackTrace();
return false;
}
}
/**
* 更新数据
*
* @param index
* @param id
* @param jsonString
* @return
*/
public boolean updateById(String index, String id, String jsonString) {
UpdateRequest updateRequest = new UpdateRequest();
updateRequest.index(index);
updateRequest.id(id);
updateRequest.doc(jsonString, XContentType.JSON);
try {
UpdateResponse response =
restHighLevelClient.update(updateRequest, ElasticSearchConfig.COMMON_OPTIONS);
log.info("更新的结果:" + response);
return true;
} catch (IOException e) {
e.printStackTrace();
return false;
}
}
/**
* 根据ID从ES中查询数据
* @param index
* @param id
* @param targetClass
* @return
*
*/
public <T extends Object> T select(String index,String id, Class<T> targetClass){
SearchRequest searchRequest=new SearchRequest();
searchRequest.indices(index);
//构建查询条件
SearchSourceBuilder searchSourceBuilder=new SearchSourceBuilder();
searchSourceBuilder.query(QueryBuilders.termQuery("_id",id));
searchRequest.source(searchSourceBuilder);
try {
SearchResponse searchResponse= restHighLevelClient.search(searchRequest,ElasticSearchConfig.COMMON_OPTIONS);
SearchHits hits= searchResponse.getHits();
SearchHit [] searchHits= hits.getHits(); //查询结果
if(searchHits==null||searchHits.length==0){ //如果数组为空,表示没有查询结果
return null;
}
String jsonString= searchHits[0].getSourceAsString();
T t= JSON.parseObject(jsonString,targetClass);
return t;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
/**
*
* @param index
* @param targetClass
* @param searchSourceBuilder 查选条件的构建器
* @param <T>
* @return
*/
public <T extends Object> List<T> select(String index,Class<T> targetClass,
SearchSourceBuilder searchSourceBuilder){
SearchRequest searchRequest=new SearchRequest(); //查询请求
searchRequest.indices(index);
searchRequest.source(searchSourceBuilder); //请求中添加中查询条件
List<T> list=new ArrayList<>();
try {
//执行查询,获得查询结果
SearchResponse searchResponse=
restHighLevelClient.search(searchRequest,ElasticSearchConfig.COMMON_OPTIONS);
SearchHits hits= searchResponse.getHits(); //封装结果
SearchHit[] searchHits= hits.getHits();
for(SearchHit searchHit:searchHits){
String jsonString= searchHit.getSourceAsString();
T t=JSON.parseObject(jsonString,targetClass);
list.add(t);
}
} catch (IOException e) {
e.printStackTrace();
}
return list;
}
/**
* 使用from+size的方式实现 ES的分页查询
* @param index 索引
* @param searchSourceBuilder 查询条件
* @param targetClass 目标类
* @param from 从第几条开始
* @param size 显示多少条记录
* @param <T>
* @return Map: 当前页的数据 总页数
*/
public <T extends Object> Map<String,Object> page(String index,
SearchSourceBuilder searchSourceBuilder,
Class<T> targetClass,int from,int size){
SearchRequest searchRequest=new SearchRequest();
searchRequest.indices(index);
searchSourceBuilder.from(from);
searchSourceBuilder.size(size);
searchRequest.source(searchSourceBuilder);
Map<String,Object> result=new HashMap<>();
List<T> resultList=new ArrayList<>();
int page=0;
try {
SearchResponse searchResponse=
restHighLevelClient.search(searchRequest,ElasticSearchConfig.COMMON_OPTIONS);
SearchHits hits= searchResponse.getHits();
long totalValue=hits.getTotalHits().value; //获得总记录数 3.0/2=1.5 Math.ceil(1.5) 2.0;
page = (int) Math.ceil((double)totalValue/size); //总页数
SearchHit [] searchHits= hits.getHits();
for(SearchHit searchHit:searchHits){
String jsonString= searchHit.getSourceAsString();
T t= JSON.parseObject(jsonString,targetClass);
resultList.add(t);
}
} catch (IOException e) {
e.printStackTrace();
}
result.put("page",page);
result.put("list",resultList);
return result;
}
/**
* 使用scroll分页
* @param index
* @param searchSourceBuilder
* @param targetClass
* @param size
* @param scrollId
* @param <T>
* @return Map 当前页的数据 scrollId page
*/
public <T extends Object> Map<String,Object> page(String index,
SearchSourceBuilder searchSourceBuilder,
Class<T> targetClass,int size,String scrollId){
SearchRequest searchRequest=new SearchRequest();
searchRequest.indices(index);
Scroll scroll=new Scroll(TimeValue.timeValueMinutes(1)); //指定scroll镜像的时间为1分钟
searchSourceBuilder.size(size); //每页显示多少条记录
Map<String,Object> map=new HashMap<>();
SearchResponse searchResponse=null;
try {
if(StringUtils.isBlank(scrollId)){ //scroll方式的第一次查询
searchRequest.scroll(scroll); //查询是scroll查询 镜像的时间为1分钟
searchRequest.source(searchSourceBuilder); //查询请求中添加查询条件
searchResponse=restHighLevelClient.search(searchRequest,ElasticSearchConfig.COMMON_OPTIONS);
}else{ //scroll方式的后面查询 请求:GET /_search/scroll
SearchScrollRequest searchScrollRequest=new SearchScrollRequest();
searchScrollRequest.scroll(scroll);
searchScrollRequest.scrollId(scrollId);
searchResponse=restHighLevelClient.scroll(searchScrollRequest,ElasticSearchConfig.COMMON_OPTIONS);
}
//封装查询结果
map= searchResponseToMap(searchResponse,size,targetClass);
} catch (IOException e) {
e.printStackTrace();
}
return map;
}
//当前页的数据 scrollId 总页数
private <T extends Object> Map<String,Object>
searchResponseToMap(SearchResponse searchResponse,int size,Class<T> targetClass){
SearchHits hits= searchResponse.getHits(); //查询的结果
double count=hits.getTotalHits().value; //获得总记录数
int page= (int)Math.ceil(count/size); //算出总页数
Map<String,Object> map=new HashMap<>(); //返回的结果
List<T> list=new ArrayList<>(); //当前页的数据
SearchHit [] searchHits= hits.getHits(); //获得hits中的数据
for(SearchHit temp:searchHits){
String jsonString= temp.getSourceAsString();
T t=JSON.parseObject(jsonString,targetClass);
list.add(t);
}
map.put("page",page);
map.put("scrollId",searchResponse.getScrollId());
map.put("list",list);
return map;
}
}
实体类
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class Company {
private String name;
private String job;
private String logo;
private Double payment;
@Override
public String toString() {
return "name:"+name+",job:"+job+",logo:"+logo+",payment:"+payment;
}
}
yml文件
es:
hostname: 外网IP/本地IP
port: 9200
protocol: http
启动类
写完之后我们可以先用测试类测试
添加
@Test
public void testSave(){
Company company=
Company.builder().name("广坤").job("东北F4")
.logo("DBF4").payment(20000.0).build();
List<Company> companyList=new ArrayList<>();
companyList.add(company);
companyList.add(company);
companyList.add(company);
esUtil.saveBatch("company-index",companyList);
}
删除
@Test
public void testDelete(){
esUtil.deleteById("company-index","5");
}
修改
@Test
public void testUpdate(){
Company company= Company.builder().name("广坤").job("东北F4")
.logo("DBF4").payment(20000.0).build();
String jsonString=JSONObject.toJSONString(company);
esUtil.updateById("company-index","4",jsonString);
}
按ID查询
@Test
public void testQueryId(){
Company company= esUtil.select("company-index","1000",Company.class);
System.out.println(company+"!!!!!!!!!!!");
}
查询(from+size)
@Test
public void testQuery(){
SearchSourceBuilder searchSourceBuilder=new SearchSourceBuilder();
// searchSourceBuilder.query(QueryBuilders.matchQuery("name","scott"));
List<Company> companyList=
esUtil.select("company-index",Company.class,searchSourceBuilder);
for(Company temp:companyList){
System.out.println(temp.getName());
}
}
查询(scrollID)
@Test
public void testPage2(){
SearchSourceBuilder searchSourceBuilder=new SearchSourceBuilder();
Map<String,Object> map= esUtil.page("company-index",
searchSourceBuilder,Company.class,2,"DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAFSu4WMlRKYnQzcW1RMDJBeVhnODF6ekxTQQ==");
int page=(int)map.get("page");
List<Company> companyList=(List<Company>)map.get("list");
String scrollId=(String)map.get("scrollId");
System.out.println("总页数:"+page);
System.out.println("每页的数据:"+companyList.size());
System.out.println(scrollId);
}
全部测试完 没有问题 下面可以去kibanna写DSL语句
创建索引 修改映射
PUT goods-index
{
"mappings" : {
"properties" : {
"brandId" : {
"type" : "long"
},
"brandImg" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"brandName" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"catalogId" : {
"type" : "long"
},
"categoryName" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"hasStock" : {
"type" : "boolean"
},
"hotScore" : {
"type" : "long"
},
"list" : {
"type" : "nested",
"properties" : {
"attrName" : {
"type" : "keyword",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"attrValue" : {
"type" : "keyword",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"skuId" : {
"type" : "long"
}
}
},
"saleCount" : {
"type" : "long"
},
"skuId" : {
"type" : "long"
},
"skuImg" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"skuPrice" : {
"type" : "long"
},
"skuTitle" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"spuId" : {
"type" : "long"
}
}
}
}
}
有些类型需要改成keyword
查看映射
GET /goods-index/_mapping
查数据 写出DSL语句才好在java中实现
GET /goods-index/_search
{
"query": {
"bool": {
"must": [
{
"multi_match": {
"query": "华为",
"fields": [
"brandName",
"skuTitle"
]
}
}
],
"filter": [
{
"term": {
"catalogId": "225"
}
},
{
"term": {
"brandId": "5"
}
},
{
"nested": {
"path": "list",
"query": {
"bool": {
"must": [
{
"match": {
"list.attrName": "颜色"
}
},
{
"match": {
"list.attrValue": "星河银"
}
}
]
}
}
}
}
]
}
},
"from": 0,
"size": 2,
"sort": [
{
"hotScore": {
"order": "desc"
}
}
]
}
解释:
- 使用ESmulti_match查询 query: "华为" 查询的字段是fields:brandName,skuTitle根据这两个字段查询出带有华为的数据
- filter term过滤出 catalogId = 225 term brandId=5
- nested 是类型是对象数据类型的专用版本,它允许对象数组以可以彼此独立查询的方式进行索引。path:集合字段名。一个match相当于list集合中一个字段名list.attrName ="颜色"list.attrValue="星河银"
- from 从第几条开始 size一页展示多少条
- sort排序 根据什么字段进行排序,order=desc 倒排
ES中拿到数据 我们现在去java开始编写代码
@PostMapping("/search")
public QueryResult queryResultSearch(@RequestBody SearchQueryParam searchQueryParam) {
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
//转类型
String sortName = searchQueryParam.getSortName();
String sortValue = searchQueryParam.getSortValue();
String keyWord = searchQueryParam.getKeyWord();
Integer brandId = searchQueryParam.getBrandId();
Integer categoryId = searchQueryParam.getCategoryId();
List<SearchQueryParam.Attr> attrList = searchQueryParam.getAttrList();
//排序sort
if (!StringUtils.isNotEmpty(sortName) && (!StringUtils.isNotEmpty(sortValue) && searchQueryParam.getSortValue().equals("asc"))) {
searchSourceBuilder.sort(searchQueryParam.getSortName(), SortOrder.ASC);
}
if (!StringUtils.isNotEmpty(sortName) && (!StringUtils.isNotEmpty(sortValue) && searchQueryParam.getSortValue().equals("desc"))) {
searchSourceBuilder.sort(searchQueryParam.getSortName(), SortOrder.DESC);
}
int size = 2;
int from = (searchQueryParam.getCurrentPage() - 1) * size;
//搜索框
if (!StringUtils.isNotEmpty(keyWord)) {
boolQueryBuilder.must(QueryBuilders.multiMatchQuery(keyWord, "brandName", "skuTitle"));
}
//filter 属性
if (brandId != 0 || brandId != null) {
boolQueryBuilder.filter(QueryBuilders.termQuery("brandId", brandId));
}
if (categoryId != 0 || categoryId != null) {
boolQueryBuilder.filter(QueryBuilders.termQuery("catalogId", categoryId));
}
//list类型 nested
for (SearchQueryParam.Attr attr : attrList) {
BoolQueryBuilder boolQueryBuilder1 = QueryBuilders.boolQuery();
List<QueryBuilder> must = boolQueryBuilder1.must();
must.add(QueryBuilders.matchQuery("list.attrName", attr.getAttrName()));
must.add(QueryBuilders.matchQuery("list.attrValue", attr.getAttrValue()));
boolQueryBuilder.filter(QueryBuilders.nestedQuery("list", boolQueryBuilder1, ScoreMode.None));
}
searchSourceBuilder.query(boolQueryBuilder);
Map<String, Object> page = esUtil.page("goods-index", searchSourceBuilder, SkuEsDTO.class, from, size);
List<SkuEsDTO> list = (List<SkuEsDTO>) page.get("list");
System.out.println("元素个数:" + list.size());
//这里开始方法 封装 ES代码到这里结束!!!!
SearchSourceBuilder searchSourceBuilder1 = new SearchSourceBuilder();
List<SkuEsDTO> select = esUtil.select("goods-index", SkuEsDTO.class, searchSourceBuilder1);
System.out.println("select :" + select.size());
QueryResult queryResult = new QueryResult();
HashSet<Attr> attrHashSet = new HashSet<>();
//BrandVo添加数据
for (SkuEsDTO skuEsDTO : select) {
Attr attr1 = new Attr();
queryResult = new QueryResult();
BrandVo brandVo = new BrandVo();
String brandName = skuEsDTO.getBrandName();
Long brandIdOne = skuEsDTO.getBrandId();
//brandVo添加数据
brandVo.setId(brandIdOne.intValue());
brandVo.setName(brandName);
queryResult.getBrandVo().add(brandVo);
//Catalog添加数据
CategoryVo categoryVo = new CategoryVo();
Long catalogId = skuEsDTO.getCatalogId();
String categoryName = skuEsDTO.getCategoryName();
categoryVo.setCatalogId(catalogId.intValue());
categoryVo.setCategoryName(categoryName);
queryResult.getCategoryVoList().add(categoryVo);
//list 颜色
for (SkuEsDTO.Attr attr : skuEsDTO.getList()) {
String attrName = attr.getAttrName();
String attrValue = attr.getAttrValue();
attr1.setAttrValue(attrValue);
attrHashSet.add(attr1);
queryResult.getAttrsMap().put(attrName, attrHashSet);
}
}
queryResult.setTotalPage(select.size());
queryResult.setSkuEsDTOList(list);
return queryResult;
}
swagger 测试
{
"attrList": [
{
"attrName": "颜色",
"attrValue": "星河银",
"skuId": 1
}
],
"brandId": 5,
"categoryId": 225,
"currentPage": 1,
"keyWord": "华为",
"sortName": "hotScore",
"sortValue": "desc"
}
展示成功!
更多推荐
已为社区贡献2条内容
所有评论(0)