大家好,又见面了,我是你们的朋友全栈君。
搜索服务的父项目:supergo_search
1、建Module:supergo_search
2、删除src
搜索服务的提供者:supergo_search_service9003
1、建Module:supergo_search_service9003
2、改pom
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>supergo_search</artifactId>
<groupId>com.supergo</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>supergo_search_service9003</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--spring-es-->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-elasticsearch</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>com.supergo</groupId>
<artifactId>supergo-mapper</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.supergo</groupId>
<artifactId>supergo-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.14.8</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
3、启动类
package com.supergo.search;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import tk.mybatis.spring.annotation.MapperScan;
@SpringBootApplication
@EnableEurekaClient
@MapperScan("com.supergo.search.mapper")
public class SearchApplication9003 {
public static void main(String[] args) {
SpringApplication.run(SearchApplication9003.class, args);
}
}
4、建 yml
本次使用的ElasticSearch版本为5.6.8,需要在yml中配置连接
# 端口
server:
port: 9003
# 名字
spring:
application:
name: supergo-manager # 代表的就是我以什么样的名字入驻进的注册中心
datasource:
type: com.alibaba.druid.pool.DruidDataSource # 当前数据源操作类型
driver-class-name: com.mysql.jdbc.Driver # mysql驱动类
url: jdbc:mysql://127.0.0.1:3306/supergo?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true
username: root
password: 123456
# druid 专属配置
druid:
initial-size: 5
min-idle: 5
max-active: 20
max-wait: 1000
time-between-eviction-runs-millis: 60000
min-evictable-idle-time-millis: 300000
validation-query: select 'x'
test-while-idle: true
test-on-borrow: false
test-on-return: false
pool-prepared-statements: true
max-open-prepared-statements: 50
max-pool-prepared-statement-per-connection-size: 20
# stat是统计,wall是SQL防火墙,防SQL注入的,log4j是用来输出统计数据的
# filters: stat,wall,log4j,config
##是否启用StatFilter默认值true: 排除一些不必要的url
web-stat-filter:
enabled: true
exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*'
#是否启用StatViewServlet默认值true
stat-view-servlet:
allow: 127.0.0.1 #IP 白名单
url-pattern: /druid/* #监控地址,默认 /druid/*
login-username: admin
login-password: admin #
# deny:IP #黑名单
# 最大请求文件的大小
servlet:
multipart:
max-request-size: 5MB
# ElasticSearch连接配置
data:
elasticsearch:
cluster-name: cluster_es
cluster-nodes: 192.168.77.138:9300
eureka:
client:
register-with-eureka: true # 表示将自己注册到 eureka server ,默认为 true
fetch-registry: true # 表示是否从eureka server 抓取已有的注册信息,默认为true。单节点为所谓,集群必须为 true,才能配合ribbon使用负载均衡
service-url:
# 单机版:只用注册进一个服务中心【defaultZone: http://127.0.0.1:7001/eureka/】
defaultZone: http://eureka7001.com:7001/eureka/
# 集群版:需要同时注册进每个注册中心
# defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com/eureka/
# 显示的服务主机名称
instance:
prefer-ip-address: true # 访问路径显示 ip【统一:方便调试】
ip-address: 127.0.0.1
instance-id: ${
eureka.instance.ip-address}.${
server.port}
lease-renewal-interval-in-seconds: 3
lease-expiration-duration-in-seconds: 10
#网关设置了根路径,默认监控路径发生了变化
health-check-url-path: /api/actuator/health
#actuator服务监控与管理
management:
endpoint:
#开启端点
shutdown:
enabled: true
health:
show-details: always
# 加载所有的端点
endpoints:
web:
exposure:
include: "*"
5、实体类
接口对应的实体类:
package com.supergo.search.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
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;
/** * @Author: xj0927 * @Description: * @Date Created in 2021-01-04 12:26 * @Modified By: */
@Document(indexName = "supergo", type = "goods")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class GoodsEntity {
@Id
@Field(type = FieldType.Long, store = true)
private Long id;
@Field(type = FieldType.text, store = true, analyzer = "ik_max_word")
private String goods_name;
@Field(type = FieldType.keyword, store = true)
private String seller_id;
@Field(type = FieldType.keyword, store = true)
private String nick_name;
@Field(type = FieldType.Long, store = true)
private long brand_id;
@Field(type = FieldType.keyword, store = true)
private String brand_name;
@Field(type = FieldType.Long, store = true)
private long category1_id;
@Field(type = FieldType.keyword, store = true)
private String cname1;
@Field(type = FieldType.Long, store = true)
private long category2_id;
@Field(type = FieldType.keyword, store = true)
private String cname2;
@Field(type = FieldType.Long, store = true)
private long category3_id;
@Field(type = FieldType.keyword, store = true)
private String cname3;
@Field(type = FieldType.keyword, store = true, index = false)
private String small_pic;
@Field(type = FieldType.Float, store = true)
private double price;
}
搜索结果实体类 :
package com.supergo.search.entity;
import java.io.Serializable;
import java.util.List;
/** * @Author: xj0927 * @Description: * @Date Created in 2021-01-04 12:28 * @Modified By: */
public class SearchResult implements Serializable {
private List<GoodsEntity> goodsList;
private List<?> aggs;
public List<GoodsEntity> getGoodsList() {
return goodsList;
}
public void setGoodsList(List<GoodsEntity> goodsList) {
this.goodsList = goodsList;
}
public List<?> getAggs() {
return aggs;
}
public void setAggs(List<?> aggs) {
this.aggs = aggs;
}
}
6、接口
mysql操作相关的接口:
package com.supergo.search.mapper;
import com.supergo.search.entity.GoodsEntity;
import org.apache.ibatis.annotations.Select;
import java.util.List;
/** * @Author: xj0927 * @Description: * @Date Created in 2021-01-04 12:27 * @Modified By: */
public interface GoodsMapper {
@Select("SELECT\n" +
"\ta.id,\n" +
"\ta.goods_name,\n" +
"\ta.seller_id,\n" +
"\tb.nick_name,\n" +
"\ta.brand_id,\n" +
"\tc.name brand_name,\n" +
"\ta.category1_id,\n" +
"\td.NAME cname1,\n" +
"\ta.category2_id,\n" +
"\te.NAME cname2,\n" +
"\ta.category3_id,\n" +
"\tf.NAME cname3,\n" +
"\ta.small_pic,\n" +
"\ta.price\n" +
"FROM\n" +
"\ttb_goods a\n" +
"LEFT JOIN tb_seller b ON a.seller_id = b.seller_id\n" +
"LEFT JOIN tb_brand c ON a.brand_id = c.id\n" +
"LEFT JOIN tb_item_cat d ON a.category1_id = d.id\n" +
"LEFT JOIN tb_item_cat e ON a.category2_id = e.id\n" +
"LEFT JOIN tb_item_cat f ON a.category3_id = f.id\n" +
"WHERE\n" +
"\ta.is_delete = 0\n" +
"AND a.is_marketable = 1")
List<GoodsEntity> getGoodsList();
}
elasticsearch操作相关的接口:
package com.supergo.search.repository;
import com.supergo.search.entity.GoodsEntity;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
/** * @Author: xj0927 * @Description: * @Date Created in 2021-01-04 12:31 * @Modified By: */
public interface GoodsRepository extends ElasticsearchRepository<GoodsEntity, Long> {
}
7、导入索引库
service
从mysql数据库查询数据,然后再导入es
@Service
public class SearchService {
@Autowired
private GoodsMapper goodsMapper;
@Autowired
private GoodsRepository goodsRepository;
@Autowired
private ElasticsearchTemplate elasticsearchTemplate;
/*** * 查询数据库将所有商品数据导入到索引库 */
public HttpResult importGoods() {
//查询数据库
List<GoodsEntity> goodsList = goodsMapper.getGoodsList();
System.out.println(goodsList);
//把商品数据写入索引库
System.out.println("数据导入开始。。。");
goodsList.forEach(g -> goodsRepository.save(g));
System.out.println("数据导入完成!");
return HttpResult.ok();
}
}
controller
@RestController
public class SearchController {
@Autowired
private SearchService searchService;
@RequestMapping("/goods/import")
public HttpResult goodsImport() {
new Thread(() -> searchService.importGoods()).start();
return HttpResult.ok();
}
}
测试
浏览器输入:
http://localhost:9003/goods/import
查看索引:
数据成功导入ElasticSearch
8、搜索索引库
数据导入Es后,下面开始搜索服务的创建
先看京东的搜索方式:
在输入栏搜索“苹果”,会出现按不同方式的聚合结果
然后在分类栏,选择”苹果”,
对地址url进行转义解析:
本次也是使用类型方案:关键词使用查询,限制条件使用过滤
service
@Service
public class SearchService {
@Autowired
private GoodsMapper goodsMapper;
@Autowired
private GoodsRepository goodsRepository;
@Autowired
private ElasticsearchTemplate elasticsearchTemplate;
/** * @Description: 商品查询 * 原则:第一个参数使用查询方式,其他参数使用过滤 [提高效率] * @Author: xj0927 * @Date Created in 2021/1/4 19:55 */
public SearchResult search(String keyword, Map<String, String> filters, int page, int size) {
//设置查询条件
BoolQueryBuilder builder = QueryBuilders.boolQuery()
.should(QueryBuilders.multiMatchQuery(keyword, "goods_name"));
//判断是否有过滤条件
if (filters != null && !filters.isEmpty()) {
filters.keySet().forEach(key -> {
builder.filter(QueryBuilders.termQuery(key, filters.get(key)));
});
}
NativeSearchQuery query = new NativeSearchQueryBuilder()
.withQuery(builder) //查询条件
.withPageable(PageRequest.of(page, size))//设置分页信息
.addAggregation(AggregationBuilders.terms("category_aggs").field("cname3"))//聚合条件1
.addAggregation(AggregationBuilders.terms("brand_aggs").field("brand_name"))//聚合条件2
.withHighlightFields(new HighlightBuilder.Field("name").preTags("<span style='color:red>").postTags("</span>"))//设置高亮显示
.build(); //创建q
//执行查询
AggregatedPage<GoodsEntity> sResult = elasticsearchTemplate.queryForPage(query, GoodsEntity.class, new SearchResultMapper() {
@Override
public <T> AggregatedPage<T> mapResults(SearchResponse searchResponse, Class<T> aClass, Pageable pageable) {
List<GoodsEntity> goodsList = new ArrayList<>();
SearchHits searchHits = searchResponse.getHits();
searchHits.forEach(hits -> {
//原文档部分
GoodsEntity goodsEntity = new GoodsEntity();
goodsEntity.setId((Long) hits.getSource().get("id"));
goodsEntity.setGoods_name((String) hits.getSource().get("goods_name"));
goodsEntity.setSeller_id((String) hits.getSource().get("seller_id"));
goodsEntity.setNick_name((String) hits.getSource().get("nick_name"));
goodsEntity.setBrand_id((Integer) hits.getSource().get("brand_id"));
goodsEntity.setBrand_name((String) hits.getSource().get("brand_name"));
goodsEntity.setCategory1_id((Integer) hits.getSource().get("category1_id"));
goodsEntity.setCname1((String) hits.getSource().get("cname1"));
goodsEntity.setCategory2_id((Integer) hits.getSource().get("category2_id"));
goodsEntity.setCname2((String) hits.getSource().get("cname2"));
goodsEntity.setCategory3_id((Integer) hits.getSource().get("category3_id"));
goodsEntity.setCname3((String) hits.getSource().get("cname3"));
goodsEntity.setSmall_pic((String) hits.getSource().get("small_pic"));
goodsEntity.setPrice((Double) hits.getSource().get("price"));
//取高亮部分
HighlightField highlightField = hits.getHighlightFields().get("goods_name");
if (highlightField != null) {
String hl = highlightField.getFragments()[0].string();
goodsEntity.setGoods_name(hl);
}
goodsList.add(goodsEntity);
});
AggregatedPage<T> aggregatedPage = new AggregatedPageImpl<T>((List<T>) goodsList, pageable, searchHits.totalHits, searchResponse.getAggregations());
return aggregatedPage;
}
});
//取查询结果
List<GoodsEntity> content = sResult.getContent();
SearchResult searchResult = new SearchResult();
searchResult.setGoodsList(content);
//取分类聚合结果
Terms termsCat = (Terms) sResult.getAggregation("category_aggs");
List<String> catAggsList = termsCat.getBuckets().stream().map(e -> e.getKeyAsString()).collect(Collectors.toList());
Map catAggsMap = new HashMap();
catAggsMap.put("name", "分类");
catAggsMap.put("field", "cname3");
catAggsMap.put("content", catAggsList);
//取品牌聚合结果
Terms termsBrand = (Terms) sResult.getAggregation("brand_aggs");
List<String> brandAggsList = termsBrand.getBuckets().stream().map(e -> e.getKeyAsString()).collect(Collectors.toList());
Map brandAggsMap = new HashMap();
brandAggsMap.put("name", "品牌");
brandAggsMap.put("field", "brand_name");
brandAggsMap.put("content", brandAggsList);
List aggsList = new ArrayList();
aggsList.add(brandAggsMap);
aggsList.add(catAggsMap);
//设置过滤条件
searchResult.setAggs(aggsList);
return searchResult;
}
}
controller
![5](images/5.png)RestController
public class SearchController {
@Autowired
private SearchService searchService;
@RequestMapping("/goods/search")
public SearchResult search(@RequestParam(required = true) String keyword,
@RequestParam(required = false, value = "ev") String filter,
@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "5") int size) {
//filter的参数形式:ev=brand_name-小米|category3_id-255、
Map<String, String> filterMap = null;
try {
if (StringUtils.isNotBlank(filter)) {
String[] filters = filter.split("\\|");
filterMap = Stream.of(filters).collect(Collectors.toMap(e -> e.split("-")[0].trim(), e -> e.split("-")[1].trim()));
}
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(filter);
System.out.println(filterMap);
SearchResult searchResult = searchService.search(keyword, filterMap, page, size);
return searchResult;
}
}
测试
浏览器输入:
http://localhost:9003/goods/search?keyword=手机&ev=brand_name-联想
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/152834.html原文链接:https://javaforall.cn
【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...