需求
我们大致能完成多条件组合查询功能, 有点类似jd商品的搜索功能。
需要完成的功能:
-
根据时间查询(范围)
-
精准查询
-
模糊查询
准备数据
将我们需要的数据存储到es中, 我们使用爬虫来完成这一步。
接口地址数据来源: https://api.66mz8.com/api/music.163.php?format=json
跑完该接口只有1百多条数据 尴尬了...
{
"code": 200,
"name": "城南花已开",
"artists_name": "三亩地",
"music_url": "http:\/\/music.163.com\/song\/media\/outer\/url?id=468176711.mp3",
"music_pic": "http:\/\/p3.music.126.net\/i-7ktILRPImJ0NwiH8DABg==\/109951162885959979.jpg",
"avatarurl": "https:\/\/p2.music.126.net\/uarVFKgUlrI9Z1nr-50cAw==\/109951162843608471.jpg",
"nickname": "城南花已开",
"comments": "谢谢私信给我朋友们,真心谢谢你们!我好坚持加油的!"
}
上面的数据我们使用 name,artists_name, nickname, comments我们再随机生成时间, 范围在5年以内。
代码见 CrawlerComments.java , 大概跑了1百多条就没有数据了...
简单页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://cdn.staticfile.org/vue-resource/1.5.1/vue-resource.min.js"></script>
</head>
<body>
<div id="content">
<div>
<form>
<span>歌曲名称:</span><input type="text" name="name" v-model="name">
<span>歌手:</span><input type="text" name="artists_name" v-model="artists_name">
<span>用户名:</span><input type="text" name="nickname" v-model="nickname">
<span>关键字:</span><input type="text" name="kw" v-model="kw">
<span>开始时间:</span><input type="text" name="start-time" v-model="starttime">
<span>结束时间:</span><input type="text" name="end-time" v-model="endtime">
<span>页码:</span><input style="width: 20px;" type="text" name="pagenum" v-model="pagenum">
<input type="button" @click="search" value="搜索">
<input type="reset" value="重置">
</form>
</div>
<div>
<table>
<tr>
<th>评论时间</th>
<th>歌手</th>
<th>歌曲名</th>
<th>评论人</th>
<th>评论内容</th>
</tr>
<tr v-for="(item, index) in resultData" :key="index">
<td style="width: 170px;">{{item.time}}</td>
<td>{{item.artists_name}}</td>
<td>{{item.name}}</td>
<td>{{item.nickname}}</td>
<td>{{item.comment}}</td>
</tr>
</table>
</div>
</div>
</body>
<script>
var vm = new Vue({
el:'#content',
data:{
name: "",
artists_name: "",
nickname: "",
kw: "",
starttime: "",
endtime : "",
pagenum: 1,
resultData:[]
},
methods:{
search: function() {
var data = {
"name":this.name,
"artists_name":this.artists_name,
"nickname":this.nickname,
"kw":this.kw,
"starttime":this.starttime,
"endtime":this.endtime
};
this.$http.post("http://127.0.0.1:8080/comments/search?pagenum="+this.pagenum, data).then(
function(result){
console.log(123);
this.resultData = result.body.content;
},
function(error){
this.resultData = error.content;
}
)
}
}
});
</script>
</html>
业务代码
- controller
2个接口, 一个多条件查询, 一个是供上面存储数据的接口
@Autowired
private CommentsService commentsService;
@RequestMapping("/search")
public Object search(@RequestBody CommentsSearchParam searchParam, @Param("pagenum") int pagenum){
Page<Comments> search = commentsService.search(searchParam, pagenum);
return search;
}
@RequestMapping("/save")
public String save(@RequestBody Comments comments){
boolean exist = commentsService.exist(comments.getCommentid());
if(!exist){
commentsService.save(comments);
}else{
System.out.println("已存在:" + JSON.toJSONString(comments));
}
return "ok";
}
- service
@Override
public Page<Comments> search(CommentsSearchParam param, int pagenum) {
if(pagenum < 1){
pagenum = 1;
}
pagenum = pagenum -1;
PageRequest pageable = PageRequest.of(pagenum, 20, Sort.by(Sort.Order.asc("time")));
// 使用不太正确,详情见下面查询详解
BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
if(StringUtils.isNotEmpty(param.getName())){
String name = param.getName();
boolQueryBuilder.must(new TermQueryBuilder("name", name));
}
if(StringUtils.isNotEmpty(param.getNickname())){
boolQueryBuilder.must(new TermQueryBuilder("nickname", param.getNickname()));
}
if(StringUtils.isNotEmpty(param.getArtists_name())){
boolQueryBuilder.must(new TermQueryBuilder("artists_name", param.getArtists_name()));
}
if(StringUtils.isNotEmpty(param.getKw())){
boolQueryBuilder.must(new MatchQueryBuilder("comment", param.getKw()));
}
if(StringUtils.isNotEmpty(param.getStarttime()) && StringUtils.isNotEmpty(param.getEndtime())){
RangeQueryBuilder time = new RangeQueryBuilder("time");
time.from(param.getStarttime());
time.to(param.getEndtime());
boolQueryBuilder.must(time);
}
Page<Comments> search = commentsRepository.search(boolQueryBuilder, pageable);
return search;
}
- CommentsRepository
@Component
public interface CommentsRepository extends ElasticsearchRepository<Comments, String>{
}
springboot data es
其他查询方式
NativeSearchQueryBuilder
好像我们上面的查询方式有点不太正确, 网上多用NativeSearchQueryBuilder, 然后with带其他条件, 使用Querybuilders 直接生成其他query, 方便简单
@Test
public void nativeQuery(){
PageRequest pageable = PageRequest.of(0, 20);
NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
nativeSearchQueryBuilder
.withQuery(QueryBuilders.boolQuery()
.must(QueryBuilders.termsQuery("artists_name", "赵紫骅", "华晨宇"))
.must(QueryBuilders.matchQuery("comment", "自己")))
.withSort(SortBuilders.fieldSort("time").order(SortOrder.ASC))
.withPageable(pageable);
// repository 和 template 都可以查询
Page<Comments> search = commentsRepository.search(nativeSearchQueryBuilder.build());
System.out.println(JSON.toJSONString(search));
Page<Comments> comments = template.queryForPage(nativeSearchQueryBuilder.build(), Comments.class);
System.out.println(search.getContent().size() == comments.getContent().size());
}
注意: 多个withQuery会被下一个覆盖掉, 多条件使用BooleanQuery
public NativeSearchQueryBuilder withQuery(QueryBuilder queryBuilder) { this.queryBuilder = queryBuilder; return this; }
也就是你用多个withQuery, 生效的永远都是最后一个, 其他的都被覆盖掉了
使用注解方式查询
/*
从kibana中复制过来的
*/
@Query("{\"term\":{\"nickname\":{\"value\":\"?0\"}}}")
Page<Comments> findByNickname(String nickname, Pageable pageable);
注意: 这边并没有把query这一层复制过来(找了小半天的原因...)
{
"query": {
"term": {
"nickname": {
"value": "遇见橙色温暖"
}
}
}
}
@Test
public void queryByNicknameTest(){
PageRequest pageable = PageRequest.of(0, 20);
Page<Comments> comments = commentsRepository.findByNickname("城南花已开", pageable);
System.out.println(JSON.toJSONString(comments));
}