基于elasticsearch快速构建一个文档搜索服务
ElasticSearch是一款零部署的分布式搜索服务,支持数据分片和集群可以说轻松应对大数据搜索处理。ElasticSearch只需要复制能就运行在装有java环境的linux和windows下可以说是傻瓜式部署,不过对于入门者来说调用服务还是有些麻烦的。为了简单化调用的工作BeetleX封装了一个基于elasticsearch搜索的文档功能。
存储结构
组件封装了一个固定索引结构来提供一些文档基础的存储和搜索功能,具体结构如下:
[ ]
public class Document
{
[ ]
[ ]
public string ID { get; set; }
[ ]
public string Title { get; set; }
[ ]
public string Content { get; set; }
[ ]
public string Summary { get; set; }
[ ]
public string Category { get; set; }
[ ]
public string Tag { get; set; }
[ ]
public DateTime CreateTime { get; set; }
}
主要提供了标题,分类,标签,概述和内容等相关信息。并在这个结构的基础上提供对应查询和汇总的一些基础功能函数。
public class DocumentDB
{
public DocumentDB(string name);
public ESDB DB { get; }
public string Host { get; }
public bool Available { get; }
public string Name { get; }
public IESIndex Index { get; }
public IndexSetting IndexSetting { get; set; }
//分类汇总
public Task<object> AggsCategories(int top = 60);
//按年月分汇总
public Task<object> AggsMonth();
//按标签汇总
public Task<object> AggsTag(int top = 60);
//按年汇总
public Task<object> AggsYear();
//创建一个查询对象
public Search CreateSearch(int page, int size = 20);
//删除文档
public Task<bool> Delete(string id);
//删除索引
public Task DeleteIndex();
//根据ID获取文档
public Task<Document> GetDocument(string id);
//实始化文档对象并指定对应的ElasticSearch服务地址
public Task Init(string host = "http://localhost:9200");
//添加文档
public Task Put(Document doc);
//批量添加文档
public Task Put(IEnumerable<Document> docs);
//重建索引并除所有数据
public Task ReCreateIndex();
}
使用
结构功能都有定义接下来介绍一下这个组件是如何使用的,首先要装一个ElasticSearch服务版本是7.x或6.x(并安装IK中文分词插件);然后引用BeetleX.ESDoce组件根据需求创建一个DocumentDB对象,并进行初始化操作。
var document = new DocumentDB("document_test");
document.Init("http://localhost:9200").Wait(10000);
初始化文档数据库后就可以进行相关操作,以下定义了一些相关功能的Webapi函数
[ ]
public class HomeController
{
public void AddDocument(BeetleX.ESDoc.Document body, DocumentDB document)
{
if (string.IsNullOrEmpty(body.ID))
{
body.ID = Guid.NewGuid().ToString("N");
body.CreateTime = DateTime.Now;
}
document.Put(body);
}
public async Task<Document> GetDocument(string id, DocumentDB document)
{
return await document.GetDocument(id);
}
public async Task<Tuple<IList<Document>, long>> Search(string searchText, int page, DocumentDB document)
{
var search = document.CreateSearch(page, 10);
search.QueryText = searchText;
return await search.Execute();
}
public async Task<object> AggsCategories(DocumentDB document)
{
return await document.AggsCategories();
}
public async Task<object> AggsTags(DocumentDB document)
{
return await document.AggsTag();
}
public async Task<object> AggsYear(DocumentDB document)
{
return await document.AggsYear();
}
public async Task<object> AggsYearMonth(DocumentDB document)
{
return await document.AggsMonth();
}
}
接下来就可以在页面中调用相关功能,以下是使用Vue来调用相关WebApi实现查询功能
<div>
<el-form :inline="true" class="demo-form-inline" size="mini">
<el-form-item label="">
<el-input v-model="searchText" placeholder="查询内容"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="page=0;onList();">查询</el-button>
</el-form-item>
</el-form>
<el-table size="mini" :data="items">
<el-table-column type="selection" width="45"></el-table-column>
<el-table-column label="Title">
<template slot-scope="item">
<el-link @click="$emit('select',item.row)">{{item.row.Title}}</el-link>
</template>
</el-table-column>
<el-table-column label="Category">
<template slot-scope="item">
<label>{{item.row.Category}}</label>
</template>
</el-table-column>
<el-table-column label="Tag">
<template slot-scope="item">
<label>{{item.row.Tag}}</label>
</template>
</el-table-column>
<el-table-column label="CreateTime">
<template slot-scope="item">
<label>{{item.row.CreateTime}}</label>
</template>
</el-table-column>
<div slot="append" style="text-align:right;padding:5px;">
<el-pagination background layout="prev, pager, next" :page-size="10" :total="count" @current-change="onPageChange">
</el-pagination>
</div>
</el-table>
<el-row>
<el-col :span="6" style="padding:5px">
<el-card class="box-card">
<div slot="header" class="clearfix">
<span>分类汇总</span>
</div>
<el-tag v-for="item in categories" size="mini" style="margin:2px;"
type="info"
effect="dark">
{{ item.Name }}({{ item.Value }})
</el-tag>
</el-card>
</el-col>
<el-col :span="6" style="padding:5px">
<el-card class="box-card">
<div slot="header" class="clearfix">
<span>标签汇总</span>
</div>
<el-tag v-for="item in tags" size="mini" style="margin:2px;"
effect="dark">
{{ item.Name }}({{ item.Value }})
</el-tag>
</el-card>
</el-col>
<el-col :span="6" style="padding:5px">
<el-card class="box-card">
<div slot="header" class="clearfix">
<span>年汇总</span>
</div>
<el-tag v-for="item in years" size="mini" style="margin:2px;"
type="success"
effect="dark">
{{ item.Name }}({{ item.Value }})
</el-tag>
</el-card>
</el-col>
<el-col :span="6" style="padding:5px">
<el-card class="box-card">
<div slot="header" class="clearfix">
<span>年月汇总</span>
</div>
<el-tag v-for="item in months" size="mini" style="margin:2px;"
type="warning"
effect="dark">
{{ item.Name }}({{ item.Value }})
</el-tag>
</el-card>
</el-col>
</el-row>
</div>
<script>
export default {
props: [],
data() {
return {
searchText: '',
count: 0,
items: [],
page: 0,
categories: [],
tags: [],
years: [],
months: [],
};
},
methods: {
onPageChange(e) {
this.page = e - 1;
this.onList();
},
onAggs() {
this.$get("/AggsCategories").then(r => {
this.categories = r;
});
this.$get("/AggsTags").then(r => {
this.tags = r;
});
this.$get("/AggsYear").then(r => {
this.years = r;
});
this.$get("/AggsYearMonth").then(r => {
this.months = r;
});
},
onList() {
this.onAggs();
this.$get("/Search", { searchText: this.searchText, page: this.page }).then(r => {
this.items = r.Item1;
this.count = r.Item2;
console.log(r);
});
}
},
mounted() {
this.onList();
}
}
</script>
然后运行示例得到一个简单的数据查询和汇总结果
下载完全示例
https://github.com/beetlex-io/BeetleX-Samples/tree/master/BeetleX.DocumentSearch
BeetleX
开源跨平台通讯框架(支持TLS)
提供HTTP,Websocket,Redis,RPC和服务网关开源组件
https://beetlex-io.com