六、集成Spring Boot
6.1 环境的搭建
-
新建一个空项目
-
新建一个spring boot模块
-
发现导入的默认es依赖和我们本地的es版本不一样
-
自定义es 的版本
<properties> <java.version>1.8</java.version> <!--自定义es版本依赖,保证和本地一样--> <elasticsearch.version>7.6.1</elasticsearch.version> </properties>
-
编写配置类ElasticSearchClientConfig
@Bean
public RestHighLevelClient restHighLevelClient() {
RestHighLevelClient client = new RestHighLevelClient(
RestClient.builder(
new HttpHost("localhost", 9200, "http")));
return client;
}
6.2 API测试
6.2.1 关于索引的API测试
创建索引
代码实现
@Autowired
@Qualifier("restHighLevelClient")
private RestHighLevelClient client;
//测试索引的创建
@Test
public void testCreateIndex() throws IOException {
// 1、创建索引请求
CreateIndexRequest request = new CreateIndexRequest("cvzhanshi_index");
// 2、客户端执行请求 IndicesClient, 请求后获得响应
CreateIndexResponse createIndexResponse = client.indices().create(request, RequestOptions.DEFAULT);
System.out.println(createIndexResponse);
}
判断索引是否存在
代码实现
//测试获取索引
@Test
void testExistIndex() throws IOException{
GetIndexRequest request = new GetIndexRequest("cvzhanshi_index");
boolean exists = client.indices().exists(request, RequestOptions.DEFAULT);
System.out.println(exists);
}
换一个不存在的索引名字
删除索引
代码实现
// 测试删除索引
@Test
void testDeleteIndex() throws IOException {
DeleteIndexRequest request = new DeleteIndexRequest("cvzhanshi_index");
//删除
AcknowledgedResponse delete = client.indices().delete(request, RequestOptions.DEFAULT);
System.out.println(delete.isAcknowledged());
}
完整代码
@SpringBootTest
class EsApiApplicationTests {
@Autowired
@Qualifier("restHighLevelClient")
private RestHighLevelClient client;
//测试索引的创建
@Test
public void testCreateIndex() throws IOException {
// 1、创建索引请求
CreateIndexRequest request = new CreateIndexRequest("cvzhanshi_index");
// 2、客户端执行请求 IndicesClient, 请求后获得响应
CreateIndexResponse createIndexResponse = client.indices().create(request, RequestOptions.DEFAULT);
System.out.println(createIndexResponse);
}
//测试获取索引
@Test
void testExistIndex() throws IOException{
GetIndexRequest request = new GetIndexRequest("cvzhanshi_index1");
boolean exists = client.indices().exists(request, RequestOptions.DEFAULT);
System.out.println(exists);
}
// 测试删除索引
@Test
void testDeleteIndex() throws IOException {
DeleteIndexRequest request = new DeleteIndexRequest("cvzhanshi_index");
//删除
AcknowledgedResponse delete = client.indices().delete(request, RequestOptions.DEFAULT);
System.out.println(delete.isAcknowledged());
}
}
6.2.2 关于文档的API测试
创建实体类
/**
* @author cVzhanshi
* @create 2021-08-14 14:19
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Component
public class User {
private String name;
private int age;
}
导入json依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.74</version>
</dependency>
添加文档(插入)
代码实现
//测试文档的添加
@Test
public void testAddDocument() throws IOException {
//创建对象
User user = new User("cvzhanshi", 20);
//创建请求
IndexRequest request = new IndexRequest("cvzhanshi_index");
//规则 put /kuang_index/_doc/1
request.id("1");
//设置请求超时时间 1s
request.timeout(TimeValue.timeValueSeconds(1));
// 将我们的数据放入请求 json
request.source(JSON.toJSONString(user), XContentType.JSON);
// 客户端发送请求 , 获取响应的结果
IndexResponse indexResponse = client.index(request, RequestOptions.DEFAULT);
System.out.println(indexResponse.toString());
System.out.println(indexResponse.status());
}
判断文档是否存在
代码实现
//判断文档是否存在
@Test
void testIsExists() throws IOException {
GetRequest getRequest = new GetRequest("cvzhanshi_index", "1");
// 不获取返回的 _source 的上下文了
getRequest.fetchSourceContext(new FetchSourceContext(false));
getRequest.storedFields("_none_");
boolean exists = client.exists(getRequest, RequestOptions.DEFAULT);
System.out.println(exists);
}
获取文档信息
代码实现
//获取文档信息
@Test
void testGetDocument() throws IOException {
GetRequest getRequest = new GetRequest("cvzhanshi_index", "1");
// 不获取返回的 _source 的上下文了
// getRequest.fetchSourceContext(new FetchSourceContext(false));
// getRequest.storedFields("_none_");
GetResponse getResponse = client.get(getRequest, RequestOptions.DEFAULT);
System.out.println(getResponse);
System.out.println(getResponse.getSourceAsString());
}
更新文档信息
代码实现
//获取文档信息
@Test
void testUpdateDocument() throws IOException {
UpdateRequest request = new UpdateRequest("cvzhanshi_index","1");
request.timeout("1s");
User user = new User("cv战士", 21);
request.doc(JSON.toJSONString(user),XContentType.JSON);
UpdateResponse update = client.update(request, RequestOptions.DEFAULT);
System.out.println(update.status());
}
删除文档信息
代码实现
// 删除文档记录
@Test
void testDeleteRequest() throws IOException {
DeleteRequest deleteRequest = new DeleteRequest("cvzhanshi_index", "1");
deleteRequest.timeout("1s");
DeleteResponse deleteResponse = client.delete(deleteRequest, RequestOptions.DEFAULT);
System.out.println(deleteResponse.status());
}
批量插入数据
代码实现
// 特殊的,真的项目一般都会批量插入数据!
@Test
void testBulkRequest() throws IOException {
BulkRequest bulkRequest = new BulkRequest();
bulkRequest.timeout("10s");
List<User> userList = new ArrayList<>();
userList.add(new User("cvzhanshi",20));
userList.add(new User("user",21));
userList.add(new User("ursula",22));
userList.add(new User("wile",23));
userList.add(new User("CVZHANSHI",24));
for (int i = 0;i< userList.size();i++){
bulkRequest.add(
new IndexRequest("cvzhanshi_index")
.id("" + (i+1))
.source(JSON.toJSONString(userList.get(i)),XContentType.JSON)
);
}
BulkResponse bulk = client.bulk(bulkRequest, RequestOptions.DEFAULT);
System.out.println(bulk.hasFailures());//false 返回是否失败,false表示成功
}
当然批量更新,删除等操作类似
查询
语法实现
// 查询
// SearchRequest 搜索请求
// SearchSourceBuilder 条件构造
// HighlightBuilder 构建高亮
// TermQueryBuilder 精确查询
// MatchAllQueryBuilder
// xxx QueryBuilder 对应我们刚才看到的命令!
@Test
void testSearch() throws IOException {
SearchRequest searchRequest = new SearchRequest("cvzhanshi_index");
//构建搜索条件
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
//查询条件,我们可以使用SearchSourceBuilder工具来实现
//精确匹配QueryBuilders.termQuery()
TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("name", "cvzhanshi");
sourceBuilder.query(termQueryBuilder);
sourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));
searchRequest.source(sourceBuilder);
SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
System.out.println(JSON.toJSONString(searchResponse));
System.out.println("+++++++++++++++++++++++++++++++++++++++++");
for (SearchHit hit : searchResponse.getHits()) {
System.out.println(hit.getSourceAsMap());
}
}
完整代码
@SpringBootTest
class EsApiApplicationTests {
@Autowired
@Qualifier("restHighLevelClient")
private RestHighLevelClient client;
//测试文档的添加
@Test
void testAddDocument() throws IOException {
//创建对象
User user = new User("cvzhanshi", 20);
//创建请求
IndexRequest request = new IndexRequest("cvzhanshi_index");
//规则 put /kuang_index/_doc/1
request.id("1");
//设置请求超时时间 1s
request.timeout(TimeValue.timeValueSeconds(1));
// 将我们的数据放入请求 json
request.source(JSON.toJSONString(user), XContentType.JSON);
// 客户端发送请求 , 获取响应的结果
IndexResponse indexResponse = client.index(request, RequestOptions.DEFAULT);
System.out.println(indexResponse.toString());
System.out.println(indexResponse.status());
}
//判断文档是否存在
@Test
void testIsExists() throws IOException {
GetRequest getRequest = new GetRequest("cvzhanshi_index", "1");
// 不获取返回的 _source 的上下文了
getRequest.fetchSourceContext(new FetchSourceContext(false));
getRequest.storedFields("_none_");
boolean exists = client.exists(getRequest, RequestOptions.DEFAULT);
System.out.println(exists);
}
//获取文档信息
@Test
void testGetDocument() throws IOException {
GetRequest getRequest = new GetRequest("cvzhanshi_index", "1");
// 不获取返回的 _source 的上下文了
// getRequest.fetchSourceContext(new FetchSourceContext(false));
// getRequest.storedFields("_none_");
GetResponse getResponse = client.get(getRequest, RequestOptions.DEFAULT);
System.out.println(getResponse);
System.out.println(getResponse.getSourceAsString());
}
//更新文档信息
@Test
void testUpdateDocument() throws IOException {
UpdateRequest request = new UpdateRequest("cvzhanshi_index","1");
request.timeout("1s");
User user = new User("cv战士", 21);
request.doc(JSON.toJSONString(user),XContentType.JSON);
UpdateResponse update = client.update(request, RequestOptions.DEFAULT);
System.out.println(update.status());
}
// 删除文档记录
@Test
void testDeleteRequest() throws IOException {
DeleteRequest deleteRequest = new DeleteRequest("cvzhanshi_index", "1");
deleteRequest.timeout("1s");
DeleteResponse deleteResponse = client.delete(deleteRequest, RequestOptions.DEFAULT);
System.out.println(deleteResponse.status());
}
// 特殊的,真的项目一般都会批量插入数据!
@Test
void testBulkRequest() throws IOException {
BulkRequest bulkRequest = new BulkRequest();
bulkRequest.timeout("10s");
List<User> userList = new ArrayList<>();
userList.add(new User("cvzhanshi",20));
userList.add(new User("user",21));
userList.add(new User("ursula",22));
userList.add(new User("wile",23));
userList.add(new User("CVZHANSHI",24));
for (int i = 0;i< userList.size();i++){
bulkRequest.add(
new IndexRequest("cvzhanshi_index")
.id("" + (i+1))
.source(JSON.toJSONString(userList.get(i)),XContentType.JSON)
);
}
BulkResponse bulk = client.bulk(bulkRequest, RequestOptions.DEFAULT);
System.out.println(bulk.hasFailures());//false 返回是否失败,false表示成功
}
// 查询
// SearchRequest 搜索请求
// SearchSourceBuilder 条件构造
// HighlightBuilder 构建高亮
// TermQueryBuilder 精确查询
// MatchAllQueryBuilder
// xxx QueryBuilder 对应我们刚才看到的命令!
@Test
void testSearch() throws IOException {
SearchRequest searchRequest = new SearchRequest("cvzhanshi_index");
//构建搜索条件
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
//查询条件,我们可以使用SearchSourceBuilder工具来实现
//精确匹配QueryBuilders.termQuery()
TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("name", "cvzhanshi");
sourceBuilder.query(termQueryBuilder);
sourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));
searchRequest.source(sourceBuilder);
SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
System.out.println(JSON.toJSONString(searchResponse));
System.out.println("+++++++++++++++++++++++++++++++++++++++++");
for (SearchHit hit : searchResponse.getHits()) {
System.out.println(hit.getSourceAsMap());
}
}
}
七、小实战-效仿京东搜索
7.1 环境搭建
-
新建spring boot项目
-
修改es的版本,与本地es版本对应,导入json依赖(肯定能用到)
<properties> <java.version>1.8</java.version> <!--自定义es版本依赖,保证和本地一样--> <elasticsearch.version>7.6.1</elasticsearch.version> </properties> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.74</version> </dependency>
-
配置文件的编写
server: port: 9090 # 关闭thymeleaf缓存 spring: thymeleaf: cache: false
-
前端页面
前端页面可以关注公众号“狂神说”分享的网盘链接自行下载
ES资料地址:链接:https://pan.baidu.com/s/1PT3jLvCksOhq7kgAKzQm7g 提取码:s824
解压后
粘贴进项目中
-
测试,创建IndexController
/** * @author cVzhanshi * @create 2021-08-14 17:05 */ @Controller public class IndexController { @GetMapping({"/","/index"}) public String index(){ return "index"; } }
7.2 爬取数据
获取请求返回的数据,筛选出我们想要的数据
-
导入爬虫所需要的依赖
<!--jsoup 解析网页--> <dependency> <groupId>org.jsoup</groupId> <artifactId>jsoup</artifactId> <version>1.13.1</version> </dependency>
-
爬取数据的工具类的编写
/** * @author cVzhanshi * @create 2021-08-15 10:36 */ @Component public class HtmlParseUtil { public List<Content> parseJD(String keywords) throws IOException { //获取请求: https://search.jd.com/Search?keyword=java&enc=utf-8&wq=java&pvid=f807c58b66dc4baab4c7ed71834c36be //前提,联网,ajax不能获得 String url = "https://search.jd.com/Search?keyword=" + keywords + "&enc=utf-8"; //解析网页。(Jsoup返回Document就是浏览器Document对象) // Document document = Jsoup.parse(new URL(url), 30000); //设置绕过登录 Document document = Jsoup.connect(url).userAgent("Mozilla/5.0 (Windows NT 5.1; zh-CN) AppleWebKit/535.12 (KHTML, like Gecko) Chrome/22.0.1229.79 Safari/535.12").timeout(30000).get(); System.out.println(document.html()); //所有你在js中可以使用的方法,这里都能用! Element element = document.getElementById("J_goodsList"); //获取所有的Li元素 Elements elements = element.getElementsByTag("li"); System.out.println(elements); List<Content> contents = new ArrayList<>(); //获取元素中的内容,这el就是每一 个Li标签了! for (Element el : elements) { //由于图片是延迟加载 //data-lazy-img String img = el.getElementsByTag("img").eq(0).attr("data-lazy-img"); String price = el.getElementsByClass("p-price").eq(0).text(); String title = el.getElementsByClass("p-name").eq(0).text(); contents.add(new Content(img,price,title)); } return contents; } }
-
测试工具类
7.3 业务的编写
-
编写es的配置类
/** * @author cVzhanshi * @create 2021-08-14 11:43 */ @Configuration public class ElasticSearchClientConfig { @Bean public RestHighLevelClient restHighLevelClient() { RestHighLevelClient client = new RestHighLevelClient( RestClient.builder( new HttpHost("localhost", 9200, "http"))); return client; } }
-
编写service层的ContentService
/** * @author cVzhanshi * @create 2021-08-15 11:23 */ //业务的编写 @Service public class ContentService { @Autowired private RestHighLevelClient restHighLevelClient; public Boolean parseContent(String keywords) throws IOException { List<Content> contents = new HtmlParseUtil().parseJD(keywords); BulkRequest bulkRequest = new BulkRequest(); bulkRequest.timeout("2m"); for (int i = 0; i < contents.size(); i++) { bulkRequest.add(new IndexRequest("jd_goods") .source(JSON.toJSONString(contents.get(i)), XContentType.JSON) ); } BulkResponse bulkResponse = restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT); return !bulkResponse.hasFailures(); } public List<Map<String, Object>> searchPage(String keyword, int pageNo, int pageSize) throws IOException { if (pageNo <= 1) { pageNo = 1; } //条件搜索 SearchRequest searchRequest = new SearchRequest("jd_goods"); SearchSourceBuilder sourceBuilder = new SearchSourceBuilder(); //分页 sourceBuilder.from(pageNo); sourceBuilder.size(pageSize); //精准匹配 TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("title", keyword); sourceBuilder.query(termQueryBuilder); sourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS)); //执行搜索 searchRequest.source(sourceBuilder); SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT); //解析结果 ArrayList<Map<String, Object>> list = new ArrayList<>(); for (SearchHit documentFields : searchResponse.getHits().getHits()) { list.add(documentFields.getSourceAsMap()); } System.out.println(list); return list; } // 2.获取这些数据实现搜索高亮功能 public List<Map<String, Object>> searchPageHighlightBuilder(String keyword, int pageNo, int pageSize) throws IOException { if (pageNo <= 1) { pageNo = 1; } //条件搜索 SearchRequest searchRequest = new SearchRequest("jd_goods"); SearchSourceBuilder sourceBuilder = new SearchSourceBuilder(); //分页 sourceBuilder.from(pageNo); sourceBuilder.size(pageSize); //精准匹配 TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("title", keyword); sourceBuilder.query(termQueryBuilder); sourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS)); //高亮 HighlightBuilder highlightBuilder = new HighlightBuilder(); highlightBuilder.field("title"); highlightBuilder.requireFieldMatch(false); //多个高亮显示 highlightBuilder.preTags("<span style='color:red'>"); highlightBuilder.postTags("</span>"); sourceBuilder.highlighter(highlightBuilder); //执行搜索 searchRequest.source(sourceBuilder); SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT); //解析结果 ArrayList<Map<String, Object>> list = new ArrayList<>(); for (SearchHit hit : searchResponse.getHits().getHits()) { Map<String, HighlightField> highlightFields = hit.getHighlightFields(); HighlightField title = highlightFields.get("title"); Map<String, Object> sourceAsMap = hit.getSourceAsMap(); //原来的结果! //解析高亮的字段,将原来的字段换为我们高亮的字段即可! if (title != null) { Text[] fragments = title.fragments(); String n_title = ""; for (Text text : fragments) { n_title += text; sourceAsMap.put("title", n_title); //高亮字段替换掉原来的内容即可! } } list.add(sourceAsMap); } return list; } }
-
编写controller层的ContentController
/** * @author cVzhanshi * @create 2021-08-15 11:25 */ @RestController public class ContentController { @Autowired private ContentService contentService; //爬取数据 @GetMapping("/parse/{keywords}") public Boolean parse(@PathVariable("keywords") String keywords) throws Exception { return contentService.parseContent(keywords); } //搜索 @GetMapping("/search/{keyword}/{pageNo}/{pageSize}") public List<Map<String, Object>> search(@PathVariable("keyword") String keyword, @PathVariable("pageNo") int pageNo, @PathVariable("pageSize") int pageSize) throws IOException { //return contentService.searchPage(keyword, pageNo, pageSize); return contentService.searchPageHighlightBuilder(keyword, pageNo, pageSize); } }
-
先进行数据的爬取,存入到es中,浏览器访问localhost:9090/parse/java
-
因为要使用vue在前端接收数据,所以我们学要vue的js
-
找个空的文件夹运行命令
npm install vue
npm install axios
-
在生成的文件中找到两个js,复制到项目中
-
改写index.html,使用vue接收数据
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="utf-8"/> <title>cv战士Java-ES仿京东实战</title> <link rel="stylesheet" th:href="@{/css/style.css}"/> <!--<script th:src="@{/js/jquery.min.js}"></script>--> </head> <body class="pg"> <div class="page" id="app"> <div id="mallPage" class=" mallist tmall- page-not-market "> <!-- 头部搜索 --> <div id="header" class=" header-list-app"> <div class="headerLayout"> <div class="headerCon "> <!-- Logo--> <h1 id="mallLogo"> <img th:src="@{/images/jdlogo.png}" alt=""> </h1> <div class="header-extra"> <!--搜索--> <div id="mallSearch" class="mall-search"> <form name="searchTop" class="mallSearch-form clearfix"> <fieldset> <legend>天猫搜索</legend> <div class="mallSearch-input clearfix"> <div class="s-combobox" id="s-combobox-685"> <div class="s-combobox-input-wrap"> <input v-model="keyword" type="text" autocomplete="off" value="dd" id="mq" class="s-combobox-input" aria-haspopup="true"> </div> </div> <button type="submit" @click.prevent="searchKey" id="searchbtn">搜索</button> </div> </fieldset> </form> <ul class="relKeyTop"> <li><a>cv战士Java</a></li> <li><a>cv战士前端</a></li> <li><a>cv战士Linux</a></li> <li><a>cv战士大数据</a></li> <li><a>cv战士理财</a></li> </ul> </div> </div> </div> </div> </div> <!-- 商品详情页面 --> <div id="content"> <div class="main"> <!-- 品牌分类 --> <form class="navAttrsForm"> <div class="attrs j_NavAttrs" style="display:block"> <div class="brandAttr j_nav_brand"> <div class="j_Brand attr"> <div class="attrKey"> 品牌 </div> <div class="attrValues"> <ul class="av-collapse row-2"> <li><a href="#"> cv战士 </a></li> <li><a href="#"> Java </a></li> </ul> </div> </div> </div> </div> </form> <!-- 排序规则 --> <div class="filter clearfix"> <a class="fSort fSort-cur">综合<i class="f-ico-arrow-d"></i></a> <a class="fSort">人气<i class="f-ico-arrow-d"></i></a> <a class="fSort">新品<i class="f-ico-arrow-d"></i></a> <a class="fSort">销量<i class="f-ico-arrow-d"></i></a> <a class="fSort">价格<i class="f-ico-triangle-mt"></i><i class="f-ico-triangle-mb"></i></a> </div> <!-- 商品详情 --> <div class="view grid-nosku"> <div class="product" v-for="result in results"> <div class="product-iWrap"> <!--商品封面--> <div class="productImg-wrap"> <a class="productImg"> <img :src="result.img"> </a> </div> <!--价格--> <p class="productPrice"> <em>{{result.price}}</em> </p> <!--标题--> <p class="productTitle"> <a v-html="result.title"></a> </p> <!-- 店铺名 --> <div class="productShop"> <span>店铺: cv战士Java </span> </div> <!-- 成交信息 --> <p class="productStatus"> <span>月成交<em>999笔</em></span> <span>评价 <a>3</a></span> </p> </div> </div> </div> </div> </div> </div> </div> <!--前端使用Vue,实现前后端分离--> <script th:src="@{/js/axios.min.js}"></script> <script th:src="@{/js/vue.min.js}"></script> <script> new Vue({ el: '#app', data: { keyword: "", //搜索的关键字 results: [] //搜索的结果 }, methods: { searchKey() { let keyword = this.keyword; console.log(keyword); //对接后端的接口 axios.get('search/' + keyword + "/1/20").then(response => { console.log(response); this.results = response.data;//绑定数据 }) } } }) </script> </body> </html>
7.5 测试
启动项目,直接访问localhost:9090/
进行搜索,搜索的前提是,之前有把关键的数据爬取下来放在es中
分享代码地址
Gitee:https://gitee.com/cvzhanshi-ursula/es-api.git
GitHub:https://github.com/cvzhanshi-ursula/es-api