赞
踩
docker pull kibana:7.9.3
docker run \
-d \
--name kibana \
--net es-net \
-p 5601:5601 \
-e ELASTICSEARCH_HOSTS='["http://node1:9200","http://node2:9200","http://node3:9200"]' \
--restart=always \
kibana:7.9.3
启动后,浏览器访问 Kibana,进入 Dev Tools:
Elasticsearch索引用来存储我们要搜索的数据,以倒排索引结构进行存储。
例如,要搜索商品数据,可以创建一个商品数据的索引,其中存储着所有商品的数据,供我们进行搜索:
当索引中存储了大量数据时,大量的磁盘io操作会降低整体搜索新能,这时需要对数据进行分片存储。
在一个索引中存储大量数据会造成性能下降,这时可以对数据进行分片存储。
每个节点上都创建一个索引分片,把数据分散存放到多个节点的索引分片上,减少每个分片的数据量来提高io性能:
每个分片都是一个独立的索引,数据分散存放在多个分片中,也就是说,每个分片中存储的都是不同的数据。搜索时会同时搜索多个分片,并将搜索结果进行汇总。
如果一个节点宕机分片不可用,则会造成部分数据无法搜索:
为了解决这一问题,可以对分片创建多个副本来解决。
对分片创建多个副本,那么即使一个节点宕机,其他节点中的副本分片还可以继续工作,不会造成数据不可用:
分片的工作机制:
1.主分片的数据会复制到副本分片
2.搜索时,以负载均衡的方式工作,提高处理能力
3.主分片宕机时,其中一个副本分片会自动提升为主分片
下面我们就以上图的结构来创建 products 索引
创建一个名为 products 的索引,用来存储商品数据。
分片和副本参数说明:
number_of_shards:分片数量,默认值是 5
number_of_replicas:副本数量,默认值是 1
我们有三个节点,在每个节点上都创建一个分片。每个分片在另两个节点上各创建一个副本。
# 创建索引,命名为 products
PUT /products
{
"settings": {
"number_of_shards": 3,
"number_of_replicas": 2
}
}
用索引名称过滤,查看 products 索引:
粗框为主分片,细框为副本分片
类似于数据库表结构,索引数据也被分为多个数据字段,并且需要设置数据类型和其他属性。
映射,是对索引中字段结构的定义和描述。
常用类型:
数字类型:
byte、short、integer、long
float、double
unsigned_long
字符串类型:
text : 会进行分词
keyword : 不会进行分词,适用于email、主机地址、邮编等
日期和时间类型:
date
在 products 索引中创建映射。
分词器设置:
# 定义mapping,数据结构 PUT /products/_mapping { "properties": { "id": { "type": "long" }, "title": { "type": "text", "analyzer": "ik_max_word", "search_analyzer": "ik_smart" }, "category": { "type": "text", "analyzer": "ik_smart", "search_analyzer": "ik_smart" }, "price": { "type": "float" }, "city": { "type": "text", "analyzer": "ik_smart", "search_analyzer": "ik_smart" }, "barcode": { "type": "keyword" } } }
映射参考:
GET /products/_mapping
添加的文档会有一个名为_id的文档id,这个文档id可以自动生成,也可以手动指定,通常可以使用数据的id作为文档id。
# 添加文档 PUT /products/_doc/10033 { "id":"10033", "title":"SONOS PLAY:5(gen2) 新一代PLAY:5无线智能音响系统 WiFi音箱家庭,潮酷数码会场", "category":"潮酷数码会场", "price":"3980.01", "city":"上海", "barcode":"527848718459" } PUT /products/_doc/10034 { "id":"10034", "title":"天猫魔盒 M13网络电视机顶盒 高清电视盒子wifi 64位硬盘播放器", "category":"潮酷数码会场", "price":"398.00", "city":"浙江杭州", "barcode":"522994634119" } PUT /products/_doc/10035 { "id":"10035", "title":"BOSE SoundSport耳塞式运动耳机 重低音入耳式防脱降噪音乐耳机", "category":"潮酷数码会场", "price":"860.00", "city":"浙江杭州", "barcode":"526558749068" } PUT /products/_doc/10036 { "id":"10036", "title":"【送支架】Beats studio Wireless 2.0无线蓝牙录音师头戴式耳机", "category":"潮酷数码会场", "price":"2889.00", "city":"上海", "barcode":"37147009748" } PUT /products/_doc/10037 { "id":"10037", "title":"SONOS PLAY:1无线智能音响系统 美国原创WiFi连接 家庭桌面音箱", "category":"潮酷数码会场", "price":"1580.01", "city":"上海", "barcode":"527783392239" }
也可以自动生成 _id 值:
POST /products/_doc
{
"id":"10027",
"title":"vivo X9前置双摄全网通4G美颜自拍超薄智能手机大屏vivox9",
"category":"手机会场",
"price":"2798.00",
"city":"广东东莞",
"barcode":"541396973568"
}
查看文档:
GET /products/_doc/10037
查看指定文档title字段的分词结果:
GET /products/_doc/10037/_termvectors?fields=title
底层索引数据无法修改,修改数据实际上是先删除再重新添加。
两种修改方式:
PUT:对文档进行完整的替换
POST:可以修改一部分字段
# 修改文档 - 替换
PUT /products/_doc/10037
{
"id":"10037",
"title":"SONOS PLAY:1无线智能音响系统 美国原创WiFi连接 家庭桌面音箱",
"category":"潮酷数码会场",
"price":"9999.99",
"city":"上海",
"barcode":"527783392239"
}
查看文档:
GET /products/_doc/10037
# 修改文档 - 更新部分字段
POST /products/_update/10037
{
"doc": {
"price":"8888.88",
"city":"深圳"
}
}
查看文档:
GET /products/_doc/10037
DELETE /products/_doc/10037
清空
POST /products/_delete_by_query
{
"query": {
"match_all": {}
}
}
# 删除 products 索引
DELETE /products
可以尝试用不同的分片和副本值来重新创建 products 索引
为了测试搜索功能,我们首先导入测试数据,3160条商品数据,数据样例如下
{ "index": {"_index": "pditems", "_id": "536563"}}
{ "id":"536563","brand":"联想","title":"联想(Lenovo)小新Air13 Pro 13.3英寸14.8mm超轻薄笔记本电脑","sell_point":"清仓!仅北京,武汉仓有货!","price":"6688.0","barcode":"","image":"/images/server/images/portal/air13/little4.jpg","cid":"163","status":"1","created":"2015-03-08 21:33:18","updated":"2015-04-11 20:38:38"}
将压缩文件中的 pditems.json 上传到服务器
PUT /pditems { "settings": { "number_of_shards": 3, "number_of_replicas": 2 }, "mappings": { "properties": { "id": { "type": "long" }, "brand": { "type": "text", "analyzer": "ik_smart" }, "title": { "type": "text", "analyzer": "ik_max_word" }, "sell_point": { "type": "text", "analyzer": "ik_max_word", "search_analyzer": "ik_smart" }, "price": { "type": "float" }, "image": { "type": "keyword" }, "cid": { "type": "long" }, "status": { "type": "byte" }, "created": { "type": "date", "format": "yyyy-MM-dd HH:mm:ss" }, "updated": { "type": "date", "format": "yyyy-MM-dd HH:mm:ss" } } } }
用 head 查看索引:
在服务器上,进入 pditems.json 所在的文件夹,执行批量数据导入:
curl -XPOST 'localhost:9200/pditems/_bulk' \
-H 'Content-Type:application/json' \
--data-binary @pditems.json
搜索 pditems 索引中全部 3160 条数据:
GET /pditems/_search
{
"query": {
"match_all": {}
},
"size": 3160
}
# 搜索 pditems 索引中全部数据
POST /pditems/_search
{
"query": {
"match_all": {}
}
}
# 查询 pditems 索引中title中包含"电脑"的商品
POST /pditems/_search
{
"query": {
"match": {
"title": "电脑"
}
}
}
# 价格大于2000,并且title中包含"电脑"的商品 POST /pditems/_search { "query": { "bool": { "must": [ { "match": { "title": "电脑" } } ], "filter": [ { "range": { "price": { "gte": "2000" } } } ] } } }
em标签高亮
highlight高亮设置
multi_match多字段匹配
POST /pditems/_search { "query": { "multi_match":{ "query": "手机", "fields": ["title", "sell_point"] } }, "highlight" : { "pre_tags" : ["<i class=\"highlight\">"], "post_tags" : ["</i>"], "fields" : { "title" : {}, "sell_point" : { "pre_tags": "<em>", "post_tags": "</em>" } } } }
https://docs.spring.io/spring-data/elasticsearch/docs/current/reference/html/#reference
Spring Data Elasticsearch 是 Elasticsearch 搜索引擎开发的解决方案。它提供:
模板对象,用于存储、搜索、排序文档和构建聚合的高级API。
例如,Repository 使开发者能够通过定义具有自定义方法名称的接口来表达查询。
在 Elasticsearch 中存储学生数据,并对学生数据进行搜索测试。
案例测试以下数据操作:
1.创建 students 索引和映射
2.C - 创建学生数据
3.R - 访问学生数据
4.U - 修改学生数据
5.D - 删除学生数据
6.使用 Repository 和 Criteria 搜索学生数据
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.3.6.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>cn.tedu</groupId> <artifactId>es-springboot</artifactId> <version>0.0.1-SNAPSHOT</version> <name>es-springboot</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-elasticsearch</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
logging.level.tracer=TRACE 作用是在控制台中显示底层的查询日志
spring:
elasticsearch:
rest:
uris: http://192.168.64.181:9200
logging:
level:
tracer: TRACE
package cn.tedu.es.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; // spring data es API 可以根据这里的设置 // 在服务器新建索引 // 一般情况下,索引应该自己在服务器上手动创建 @Document(indexName = "students",shards = 3,replicas = 2) @Data @NoArgsConstructor @AllArgsConstructor public class Student { @Id // 使用学号作为索引id private Long id; private String name; private Character gender; @Field("birthDate") // es索引中的字段名,与变量名相同可以省略 private String birthDate; }
@Documnet注解对索引的参数进行设置。
上面代码中,把 students 索引的分片数设置为3,副本数设置为2。
在 Elasticsearch 中创建文档时,使用 @Id 注解的字段作为文档的 _id 值
通过 @Field 注解设置字段的数据类型和其他属性。
text 类型会进行分词。
keyword 不会分词。
通过 analyzer 设置可以指定分词器,例如 ik_smart、ik_max_word 等。
我们这个例子中,对学生姓名字段使用的分词器是 ngram 分词器,其分词效果如下面例子所示:
Spring Data 的 Repository 接口提供了一种声明式的数据操作规范,无序编写任何代码,只需遵循 Spring Data 的方法定义规范即可完成数据的 CRUD 操作。
ElasticsearchRepository 继承自 Repository,其中已经预定义了基本的 CURD 方法,我们可以通过继承 ElasticsearchRepository,添加自定义的数据操作方法。
自定义数据操作方法需要遵循 Repository 规范,示例如下:
关键词 | 方法名 | es查询 |
---|---|---|
And | findByNameAndPrice | { “query” : { “bool” : { “must” : [ { “query_string” : { “query” : “?”, “fields” : [ “name” ] } }, { “query_string” : { “query” : “?”, “fields” : [ “price” ] } } ] } }} |
Or | findByNameOrPrice | { “query” : { “bool” : { “should” : [ { “query_string” : { “query” : “?”, “fields” : [ “name” ] } }, { “query_string” : { “query” : “?”, “fields” : [ “price” ] } } ] } }} |
Is | findByName | { “query” : { “bool” : { “must” : [ { “query_string” : { “query” : “?”, “fields” : [ “name” ] } } ] } }} |
Not | findByNameNot | { “query” : { “bool” : { “must_not” : [ { “query_string” : { “query” : “?”, “fields” : [ “name” ] } } ] } }} |
Between | findByPriceBetween | { “query” : { “bool” : { “must” : [ {“range” : {“price” : {“from” : ?, “to” : ?, “include_lower” : true, “include_upper” : true } } } ] } }} |
LessThan | findByPriceLessThan | { “query” : { “bool” : { “must” : [ {“range” : {“price” : {“from” : null, “to” : ?, “include_lower” : true, “include_upper” : false } } } ] } }} |
LessThanEqual | findByPriceLessThanEqual | { “query” : { “bool” : { “must” : [ {“range” : {“price” : {“from” : null, “to” : ?, “include_lower” : true, “include_upper” : true } } } ] } }} |
GreaterThan | findByPriceGreaterThan | { “query” : { “bool” : { “must” : [ {“range” : {“price” : {“from” : ?, “to” : null, “include_lower” : false, “include_upper” : true } } } ] } }} |
GreaterThanEqual | findByPriceGreaterThan | { “query” : { “bool” : { “must” : [ {“range” : {“price” : {“from” : ?, “to” : null, “include_lower” : true, “include_upper” : true } } } ] } }} |
Before | findByPriceBefore | { “query” : { “bool” : { “must” : [ {“range” : {“price” : {“from” : null, “to” : ?, “include_lower” : true, “include_upper” : true } } } ] } }} |
After | findByPriceAfter | { “query” : { “bool” : { “must” : [ {“range” : {“price” : {“from” : ?, “to” : null, “include_lower” : true, “include_upper” : true } } } ] } }} |
Like | findByNameLike | { “query” : { “bool” : { “must” : [ { “query_string” : { “query” : “?*”, “fields” : [ “name” ] }, “analyze_wildcard”: true } ] } }} |
StartingWith | findByNameStartingWith | { “query” : { “bool” : { “must” : [ { “query_string” : { “query” : “?*”, “fields” : [ “name” ] }, “analyze_wildcard”: true } ] } }} |
EndingWith | findByNameEndingWith | { “query” : { “bool” : { “must” : [ { “query_string” : { “query” : “*?”, “fields” : [ “name” ] }, “analyze_wildcard”: true } ] } }} |
Contains/Containing | findByNameContaining | { “query” : { “bool” : { “must” : [ { “query_string” : { “query” : “?”, “fields” : [ “name” ] }, “analyze_wildcard”: true } ] } }} |
In (when annotated as FieldType.Keyword) | findByNameIn(Collectionnames) | { “query” : { “bool” : { “must” : [ {“bool” : {“must” : [ {“terms” : {“name” : ["?","?"]}} ] } } ] } }} |
In | findByNameIn(Collectionnames) | { “query”: {“bool”: {“must”: [{“query_string”:{“query”: “”?" “?”", “fields”: [“name”]}}]}}} |
NotIn (when annotated as FieldType.Keyword) | findByNameNotIn(Collectionnames) | { “query” : { “bool” : { “must” : [ {“bool” : {“must_not” : [ {“terms” : {“name” : ["?","?"]}} ] } } ] } }} |
NotIn | findByNameNotIn(Collectionnames) | {“query”: {“bool”: {“must”: [{“query_string”: {“query”: “NOT(”?" “?”)", “fields”: [“name”]}}]}}} |
Near | findByStoreNear | Not Supported Yet ! |
True | findByAvailableTrue | { “query” : { “bool” : { “must” : [ { “query_string” : { “query” : “true”, “fields” : [ “available” ] } } ] } }} |
False | findByAvailableFalse | { “query” : { “bool” : { “must” : [ { “query_string” : { “query” : “false”, “fields” : [ “available” ] } } ] } }} |
OrderBy | findByAvailableTrueOrderByNameDesc | { “query” : { “bool” : { “must” : [ { “query_string” : { “query” : “true”, “fields” : [ “available” ] } } ] } }, “sort”:[{“name”:{“order”:“desc”}}] } |
package cn.tedu.esspringboot.es;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import java.util.List;
public interface StudentRepository extends ElasticsearchRepository<Student, Long> {
List<Student> findByName(String name);
List<Student> findByNameOrBirthDate(String name, String birthDate);
}
package cn.tedu.esspringboot.es; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; @Service public class StudentService { @Autowired private StudentRepository studentRepo; public void save(Student student) { studentRepo.save(student); } public void delete(Long id) { studentRepo.deleteById(id); } public void update(Student student) { save(student); } public List<Student> findByName(String name) { return studentRepo.findByName(name); } public List<Student> findByNameOrBirthDate(String name, String birthDate) { return studentRepo.findByNameOrBirthDate(name, birthDate); } }
在开始运行测试之前,在 Elasticsearch 中先创建 students 索引:
PUT /students { "settings": { "number_of_shards": 3, "number_of_replicas": 2, "index.max_ngram_diff":30, "analysis": { "analyzer": { "ngram_analyzer": { "tokenizer": "ngram_tokenizer" } }, "tokenizer": { "ngram_tokenizer": { "type": "ngram", "min_gram": 1, "max_gram": 30, "token_chars": [ "letter", "digit" ] } } } }, "mappings": { "properties": { "id": { "type": "long" }, "name": { "type": "text", "analyzer": "ngram_analyzer" }, "gender": { "type": "keyword" }, "birthDate": { "type": "date", "format": "yyyy-MM-dd" } } } }
添加测试类,对学生数据进行 CRUD 测试
package cn.tedu.esspringboot; import cn.tedu.esspringboot.es.Student; import cn.tedu.esspringboot.es.StudentService; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import java.util.List; @SpringBootTest public class Test1 { @Autowired private StudentService studentService; @Test public void test1() { studentService.save(new Student(998L,"张三",'男',"2020-12-04")); } @Test public void test2() { studentService.update(new Student(1L,"李四",'女',"2020-12-04")); } @Test public void test3() { List<Student> stu = studentService.findByName("四"); System.out.println(stu); } @Test public void test4() throws Exception { List<Student> stu; stu = studentService.findByNameOrBirthDate("四", "1999-09-09"); System.out.println(stu); stu = studentService.findByNameOrBirthDate("SFSDFS", "2020-12-04"); System.out.println(stu); } }
依次运行每个测试方法,并使用 head 观察测试结果
Spring Data Elasticsearch 中,可以使用 SearchOperations 工具执行一些更复杂的查询,这些查询操作接收一个 Query 对象封装的查询操作。
Spring Data Elasticsearch 中的 Query 有三种:
package cn.tedu.esspringboot.es; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.elasticsearch.core.ElasticsearchOperations; import org.springframework.data.elasticsearch.core.SearchHit; import org.springframework.data.elasticsearch.core.SearchHits; import org.springframework.data.elasticsearch.core.query.Criteria; import org.springframework.data.elasticsearch.core.query.CriteriaQuery; import org.springframework.stereotype.Component; import java.util.List; import java.util.stream.Collectors; @Component public class StudentSearcher { @Autowired private ElasticsearchOperations searchOperations; public List<Student> searchByBirthDate(String birthDate) { Criteria c = new Criteria("birthDate").is(birthDate); return criteriaSearch(c); } public List<Student> searchByBirthDate(String ge, String le) { Criteria c = new Criteria("birthDate").between(ge, le); return criteriaSearch(c); } private List<Student> criteriaSearch(Criteria c) { CriteriaQuery q = new CriteriaQuery(c); SearchHits<Student> hits = searchOperations.search(q, Student.class); List<Student> list = hits.stream().map(SearchHit::getContent).collect(Collectors.toList()); return list; } }
在 StudentService 中,调用 StudentSearcher,执行查询:
package cn.tedu.esspringboot.es; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; @Service public class StudentService { @Autowired private StudentRepository studentRepo; @Autowired private StudentSearcher studentSearcher; public void save(Student student) { studentRepo.save(student); } public void delete(Long id) { studentRepo.deleteById(id); } public void update(Student student) { save(student); } public List<Student> findByName(String name) { return studentRepo.findByName(name); } public List<Student> findByNameOrBirthDate(String name, String birthDate) { return studentRepo.findByNameOrBirthDate(name, birthDate); } public List<Student> findByBirthDate(String birthDate) { return studentSearcher.searchByBirthDate(birthDate); } public List<Student> findByBirthDate(String ge, String le) { return studentSearcher.searchByBirthDate(ge, le); } }
package cn.tedu.esspringboot; import cn.tedu.esspringboot.es.Student; import cn.tedu.esspringboot.es.StudentService; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import java.util.List; @SpringBootTest public class Test1 { @Autowired private StudentService studentService; @Test public void test1() { studentService.save(new Student(998L,"张三",'男',"2020-12-04")); } @Test public void test2() { studentService.update(new Student(1L,"李四",'女',"2020-12-04")); } @Test public void test3() { List<Student> stu = studentService.findByName("四"); System.out.println(stu); } @Test public void test4() throws Exception { List<Student> stu; stu = studentService.findByNameOrBirthDate("四", "1999-09-09"); System.out.println(stu); stu = studentService.findByNameOrBirthDate("SFSDFS", "2020-12-04"); System.out.println(stu); } @Test public void test5() throws Exception { List<Student> stu; stu = studentService.findByBirthDate("2020-12-04"); System.out.println(stu); } @Test public void test6() throws Exception { List<Student> stu; stu = studentService.findByBirthDate("2020-12-05", "2020-12-09"); System.out.println(stu); } }
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
spring:
elasticsearch:
rest:
uris:
- http://192.168.64.181:9200
- http://192.168.64.181:9201
- http://192.168.64.181:9202
package com.pd.pojo; 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; @Document(indexName = "pditems") @Data @NoArgsConstructor @AllArgsConstructor public class Item { @Id private Long id; private String brand; private String title; @Field("sell_point") private String sellPoint; private String price; private String image; }
package com.pd.es; import com.pd.pojo.Item; import org.springframework.data.domain.Pageable; import org.springframework.data.elasticsearch.annotations.Highlight; import org.springframework.data.elasticsearch.annotations.HighlightField; import org.springframework.data.elasticsearch.annotations.HighlightParameters; import org.springframework.data.elasticsearch.core.SearchHit; import org.springframework.data.elasticsearch.repository.ElasticsearchRepository; import java.util.List; /** * 做高亮显示 */ public interface ItemRepository extends ElasticsearchRepository<Item,Long> { /** * 如果要做高亮显示,高亮结果会封装到SearchHit对象 * @param kye1 * @param key2 * @param pageable * @return */ @Highlight(parameters = @HighlightParameters( preTags = "<em>", postTags = "</em>" ), fields = { @HighlightField(name="title"), @HighlightField(name = "sellPoint") }) List<SearchHit<Item>> findByTitleOrSellPoint(String kye1, String key2, Pageable pageable); }
findByTitleOrSellPoint()
package com.pd.service;
import com.pd.pojo.Item;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.SearchHit;
import java.util.List;
public interface SearchService {
List<SearchHit<Item>> search(String key, Pageable pageable);
}
package com.pd.controller; import com.pd.pojo.Item; import com.pd.service.SearchService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.elasticsearch.core.SearchHit; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import java.util.ArrayList; import java.util.List; @Controller public class SearchController { @Autowired private SearchService searchService; @GetMapping("/search/toSearch.html") // ?key=手机&page=0&size=20 public String search(Model model, String key, Pageable pageable) { List<SearchHit<Item>> r = searchService.search(key, pageable); // 把所有 SearchHit 中的 Item 对象拿出来,放入一个新的 List<Item> 集合 List<Item> list = new ArrayList<>(); for (SearchHit<Item> sh : r) { Item item = sh.getContent();//从 SearchHit 取出上商品对象 // SearchHit 对象中的高亮数据 // ["xxx", "<em>", "手机", "</em>", "xxxxx"] List<String> titleHighlight = sh.getHighlightField("title"); // 把高亮的 title 放入 item,替换原始的 title item.setTitle(highlightTiele(titleHighlight)); list.add(item); } // 集合放入model对象,传递到 jsp 界面进行显示 model.addAttribute("list", list); model.addAttribute("p", pageable); return "/search.jsp"; } private String highlightTiele(List<String> titleHighlight) { StringBuilder sb = new StringBuilder(); for (String s : titleHighlight) { sb.append(s); } return sb.toString(); } }
<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ page isELIgnored="false" %> <!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title>商品搜索页面</title> <link rel="stylesheet" href="../css/header.css" /> <link rel="stylesheet" href="../css/search.css" /> <link rel="stylesheet" href="../css/footer.css" /> <style> div.describe p em { color: #f00; } </style> </head> <jsp:include page="commons/header.jsp"></jsp:include> <body> <div class="big"> <form name="" action="" method="post"> <section id="section"> <p class="header"> 搜索结果 > ${param.key} </p> <div id="content_box"> <%-- ${list} 从Model获取list属性:List<Item> --%> <c:forEach items="${list}" var="solrItem"> <div class="lf" id="d1"> <div class="img"> <!-- ../images/search/product_img.png --> <img src="${solrItem.image}" alt="" onclick="toItemInfo(${solrItem.id})" /> </div> <div class="describe"> <p onclick="toItemInfo(${solrItem.id})"> ${solrItem.title} </p> <span class="price"><b>¥</b> <span class="priceContent"> ${solrItem.price}</span></span> <span class="addCart"><img id="collect" src="../images/search/care.png" alt="" /><a href="javascript:void(0);" class="add_cart">加入购物车</a></span> <!--<span class="succee" style="display: none"> <img src="/images/search/product_true.png" alt="" /> <span>已移入购物车</span> </span>--> </div> </div> </c:forEach> </div> <c:if test="${list.size() == 0}"> 没有更多商品了! </c:if> <c:if test="${p.pageNumber > 0}"> <a href="?key=${param.key}&page=${p.pageNumber-1}&size=${p.pageSize}">上一页</a> </c:if> <c:if test="${list.size() != 0}"> <a href="?key=${param.key}&page=${p.pageNumber+1}&size=${p.pageSize}">下一页</a> </c:if> </section> </form> </div> <!-- 尾部--> <!-- 页面底部--> <div class="foot_bj"> <div id="foot"> <div class="lf"> <p class="footer1"><img src="../images/footer/logo.png" alt="" class=" footLogo"/></p> <p class="footer2"><img src="../images/footer/footerFont.png"alt=""/></p> </div> <div class="foot_left lf" > <ul> <li><a href="#"><h3>买家帮助</h3></a></li> <li><a href="#">新手指南</a></li> <li><a href="#">服务保障</a></li> <li><a href="#">常见问题</a></li> </ul> <ul> <li><a href="#"><h3>商家帮助</h3></a></li> <li><a href="#">商家入驻</a></li> <li><a href="#">商家后台</a></li> </ul> <ul> <li><a href="#"><h3>关于我们</h3></a></li> <li><a href="#">关于拼多</a></li> <li><a href="#">联系我们</a></li> <li> <img src="../images/footer/wechat.png" alt=""/> <img src="../images/footer/sinablog.png" alt=""/> </li> </ul> </div> <div class="service"> <p>拼多商城客户端</p> <img src="../images/footer/ios.png" class="lf"> <img src="../images/footer/android.png" alt="" class="lf"/> </div> <div class="download"> <img src="../images/footer/erweima.png"> </div> <!-- 页面底部-备案号 #footer --> <div class="record"> ©2017 拼多集团有限公司 版权所有 京ICP证xxxxxxxxxxx </div> </div> </div> <div class="modal" style="display:none"> <div class="modal_dialog"> <div class="modal_header"> 操作提醒 </div> <div class="modal_information"> <img src="../images/model/model_img2.png" alt=""/> <span>将您的宝贝加入购物车?</span> </div> <div class="yes"><span>确定</span></div> <div class="no"><span>取消</span></div> </div> </div> <script src="../js/jquery-3.1.1.min.js"></script> <script src="../js/index.js"></script> <script src="../js/jquery.page.js"></script> <script> $(".add_cart").click(function(){ $(".modal").show(); $(".modal .modal_information span").html("将您的宝贝加入购物车?"); }) $(".yes").click(function(){ $(".modal").hide(); }) $('.no').click(function(){ $('.modal').hide(); }) </script> <!--<script type="text/javascript"> // var status = ${status}; var pages = ${pageBean.totalPages}; var index = ${pageBean.pageIndex}; $(".tcdPageCode").createPage({ // 总页数 pageCount:pages, // 起始页 current:index, backFn:function(p){ // 执行代码 window.location.href="http://localhost:18888/search.html?q=${q}&page="+p; } }); </script>--> <!--<script type="text/javascript"> /* 商品详情页 */ function toItemInfo(id) { if (id) { window.location.href="/toItemInfo/"+id+".html"; }else { alert("商品id不存在"); } } </script>--> <script type="text/javascript"> /**添加到收藏**/ $("#collect").click(function(e){ $(".modal").show(); $(".modal .modal_information span").html("将您的宝贝加入收藏夹"); }) $(".yes").click(function(){ $(".modal").hide(); $('#collect').attr("src","../images/search/care1.png"); }) </script> </body> </html>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> <!-- 页面顶部--> <header id="top"> <div id="logo" class="lf"> <a href="/"> <img src="/images/server/images/portal/header/logo.png" alt="logo" /> </a> </div> <div id="top_input" class="lf"> <c:choose> <c:when test="${not empty param.key}"> <input id="input" type="text" value="${param.key}" /> </c:when> <c:otherwise> <input id="input" type="text" placeholder="请输入您要搜索的内容" /> </c:otherwise> </c:choose> <div class="seek" tabindex="-1"> <div class="actived" ><span>分类搜索</span> <img src="/images/server/images/portal/header/header_normal.png" alt=""/></div> <div class="seek_content" > <div id="shcy" >生活餐饮</div> <div id="xxyp" >学习用品</div> <div id="srdz" >私人订制</div> </div> </div> <a href="javascript:void(0);" class="rt" onclick="search1()"><img id="search" src="/images/server/images/portal/header/search.png" alt="搜索"/></a> </div> <div class="rt"> <ul class="lf" id="iul"> <li><a href="/collect/toMyCollect.html" title="我的收藏"> <img class="care" src="/images/server/images/portal/header/care.png" alt="" /> </a><b>|</b></li> <li><a href="/order/toMyOrder.html" title="我的订单"> <img class="order" src="/images/server/images/portal/header/order.png" alt="" /> </a><b>|</b></li> <li><a href="/cart/toCart.html" title="我的购物车"> <img class="shopcar" src="/images/server/images/portal/header/shop_car.png" alt="" /> </a><b>|</b></li> <li></li> </ul> </div> <br /> </header> <nav id="nav"> <ul> <li><a href="/">首页</a></li> <li><a href="/food/toItemFood.html">生活餐饮</a></li> <li><a href="/toCate.html">学习用品</a></li> <li><a href="/lookforward.html">私人定制</a></li> </ul> </nav> <script src="/js/jquery-3.1.1.min.js"></script> <script src="/js/slide.js"></script> <script type="text/javascript"> function logout() { $.ajax({ url : '/user/logout.html', type : 'post', dataType:'json', success:function(result) { if (result != null && result != "" && result != undefined) { if (result.status == 200) { //alert(result.msg); window.location.href = "/user/toLogin.html"; }else { alert(result.msg); } } }, error:function() { alert('退出失败!'); } }); } </script> <script> $('#nav>ul>li').click(function(){ $(this).children().addClass('active'); $(this).siblings().children().removeClass('active'); }) </script> <script src="/js/jquery.cookie.js"></script> <script type="text/javascript"> $(function () { //请求本网站checkLogin.html,checkLogin()用httpClient做代理去访问sso $.ajax({ type:"POST", url:"/user/checkLogin.html", xhrFields:{withCredentials:true}, dataType:"json", success:function(result){ var user = result.data; console.log(result); if (result.status === 200) { $("#iul").append('<li><a href="/lookforward.html">'+user.username+'</a><b>|</b></li><li><a href="/address/list.html">地址管理</a> | <a href="javascript:;" οnclick="logout()">退出</a></li>'); }else if(result.status === 500){ $("#iul").append('<li><a href="/user/toLogin.html">登录</a></li>'); } }, error:function(textStatus,XMLHttpRequest){ //alert("系统异常!"); } }); //$.cookie出异常 //var ticket = $.cookie("DN_TICKET"); //服务器返回的是js,这种处理跨域的方式叫jsonp /* $.ajax({ type:"post", url:"http://sso.ajstore.com:90/user/checkLoginForJsonp.html", dataType:"jsonp", jsonp:"jsonpCallback",//jsonpCallback是服务器端接收参数的参数名 xhrFields:{withCredentials:true},//ajax默认不发送cookie //浏览器收到的是jquery(json字符串) //函数名jquery //执行函数jquery,得到的是json字符串,再调用success,把json字符串传过来了 success:function(result){ var user = result.data; console.log(result); if (result.status === 200) { $("#iul").append('<li><a href="/lookforward.html">'+user.username+'</a><b>|</b></li><li><a href="javascript:;" οnclick="logout()">退出</a></li>'); }else if(result.status === 500){ $("#iul").append('<li><a href="http://sso.ajstore.com:90/user/toLogin.html?callback=http://www.ajstore.com">登录</a></li>'); } }, error:function(textStatus,XMLHttpRequest){ alert("系统异常!"+JSON.stringify(textStatus)+" ------ "+XMLHttpRequest); } }); */ //服务器返回的是json /* $.ajax({ type:"post", url:"http://sso.ajstore.com:90/user/checkLogin.html", dataType:"json",//原先是jsonp要改成json xhrFields:{withCredentials:true},//ajax默认不发送cookie success:function(result){ var user = result.data; console.log(result); if (result.status === 200) { $("#iul").append('<li><a href="/lookforward.html">'+user.username+'</a><b>|</b></li><li><a href="javascript:;" οnclick="logout()">退出</a></li>'); }else if(result.status === 500){ $("#iul").append('<li><a href="http://sso.ajstore.com:90/user/toLogin.html?callback=http://www.ajstore.com">登录</a></li>'); } }, error:function(textStatus,XMLHttpRequest){ alert("系统异常!"+JSON.stringify(textStatus)+" ------ "+XMLHttpRequest); } }); */ }) </script> <script> function search1(){ var q=$("#input").val(); console.log(q); window.location.href = "/search/toSearch.html?key="+q; } </script> <script type="text/javascript"> document.onkeydown=keyDownSearch; function keyDownSearch(e) { var theEvent = e || window.event; var code = theEvent.keyCode || theEvent.which || theEvent.charCode; if (code == 13) { search1(); return false; } return true; } </script>
搜索出来高亮
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。