一、概述
Elasticsearch原生提供restful的接口用于操作,但是在实际开发时,往往我们是使用与自己开发语言对应的库来对Es进行操作,这会使开发更为方便,而且代码可读性和维护性都比较高。
官方也提供了多语言的库方便用户进行开发。
Golang的Es官方包为:go-elasticsearch
Golang的Es官方包提供的功能很齐全,但是比较底层,很多功能没有封装,使用有一定难度,不太友好。在日常开发中,我们经常使用的开源库为:github.com/olivere/elastic
,目前最高支持到Es的V7。地址:github.com/olivere/ela...
对Es基本入门及Restful操作、docker部署不是很清楚的同学,可以参考我上一篇文章: Elasticsearch基础入门及使用docker部署集群(详细操作)
二、版本对齐
本文操作使用到的组件对应的版本:
Elasticsearch:7.17.1
golang 1.17
github.com/olivere/elastic/v7 v7.0.32
三、使用Go进行操作
1、操作说明
本次操作,将会分别对单条数据、多条数据进行对应的增、删、改、查操作,为了代码编写方便,将会分别创建single和bulk文件夹,在这两个文件夹分别创建main.go文件,所有示例代码均写在对应的main.go文件中。在实际开发中,需要自行根据业务进行拆分、封装。
以下是本文代码文件结构:
shell
$ tree ./es_demo
./es_demo
├── bulk
│ └── main.go
└── single
└── main.go
1、创建文件并引入github.com/olivere/elastic/v7 包
根据以上文件树创建文件夹及main.go文件
在命令行使用go get -u github.com/olivere/elastic/v7
拉取golang操作es的包。
2、基本通用代码编写
基本通用代码主要包括使用github.com/olivere/elastic/v7
创建与Es的连接、基本配置、本文使用的示例数据model、mapping定义、基本操作类的封装。在single
和bulk
的操作都以此为基本,进行对应的编码。所以此代码需要同时放入./single/main.go
和./bulk/main.go
中
2.1 代码内容
golang
package main
import (
"context"
"encoding/json"
"errors"
"fmt"
"github.com/olivere/elastic/v7"
"log"
"os"
"strconv"
)
var Client *elastic.Client
// Init 初始化链接
func Init() {
var err error
urls := []string{"http://192.168.94.128:19200"}
//初始化Client
Client, err = elastic.NewClient(
//设置es的url,支持多节点
elastic.SetURL(urls...),
//允许指定弹性是否应该定期检查集群(默认为true),在使用docker部署时,应该设置为false,否则检查集群会获取其节点内网地址,导致健康检查失败,导致错误
elastic.SetSniff(false),
//基于http base auth 验证机制的账号密码。
elastic.SetBasicAuth("elastic", "pasword"),
//设置日志输出,传入实现elastic.logger接口的日志对象
elastic.SetErrorLog(log.New(os.Stderr, "ELASTIC_ERROR ", log.LstdFlags)),
elastic.SetInfoLog(log.New(os.Stdout, "ELASTIC ", log.LstdFlags)),
)
if err != nil {
panic(err)
}
}
// 建立测试数据模型
// User 假定有一个user数据,字段内容为id,name,age,city,tags
// 对应的index名称为new_es_user
const userIndex = "new_es_user"
//mapping json 字符串
var userMapping = `{
"mappings": {
"properties": {
"id": {
"type": "keyword"
},
"name": {
"type": "text"
},
"gender": {
"type": "keyword"
},
"age": {
"type": "integer"
},
"City": {
"type": "text"
},
"Tags": {
"type": "keyword"
}
}
}
}`
// UserModel 与mapping对应的model,用于插入、修改、查询
type UserModel struct {
Id int `json:"id"`
Name string `json:"name"`
Gender int `json:"gender"` //1-男 2-女
Age int `json:"age"`
City string `json:"city"`
Tags []string `json:"tags"`
}
// UserIndex 对user信息进行操作对象
type UserIndex struct {
index string
mapping string
}
// 新建一个新的操作对象
func NewUserIndex() (*UserIndex, error) {
user := &UserIndex{
index: userIndex,
mapping: userMapping,
}
err := user.init()
if err != nil {
return nil, err
}
return user, nil
}
// 初始化index,保证对应的index在Es中存在,并定义mapping,方便后续操作
func (u *UserIndex) init() error {
ctx := context.Background()
//查询指定index是否存在,返回bool
exist, err := Client.IndexExists(u.index).Do(ctx)
if err != nil {
fmt.Println("index check exist failed", err)
return err
}
if !exist {
//创建index,并在body中指定mapping。
//在elasticsearch7中不再区分type,直接默认为_doc,所以此处的mapping及代码中均不用指定type
_, err = Client.CreateIndex(u.index).Body(u.mapping).Do(ctx)
if err != nil {
fmt.Println("create index failed", err)
return err
}
}
return nil
}
2.2 代码解析
func Init(){}
函数中,elastic.Client初始化的配置项设置中,主要是通过传入ClientOptionFunc
函数类型进行配置项的设置。
除代码中用到的部分设置方法,还有一些可用的:
elastic.SetGzip(true)
启动gzip压缩
Elastic.SetMaxRetries(10)
设置http请求到es的最大重试次数,达到最大次数返回错误
elastic.SetHealthcheckInterval(10*time.Second)
用来设置健康检查时间间隔
除此之外,还有很多设置初始化配置的函数,可以利用IDE(golnd使用Ctrl+鼠标左键跳转到目标函数)进入到elastic包的client.go文件中直接查看源码,源码中都具有比较清晰的注释。
其余注释均在代码中。
3、单条数据操作
单条数据的代码操作均在./single/main.go文件中。 在创建文档时,为了和业务保持同步,将文档的id和数据的id设为一致。
3.1 代码内容
golang
//接通用代码内容进行延续
//CreateOne 添加单条记录
func (u *UserIndex) CreateOne(user UserModel) error {
id := strconv.FormatInt(int64(user.Id), 10)
//获取IndexService 调用index指定index名称、Id指定文档id,请求体内直接传入model结构体,在底层会解析为json格式字符串
_, err := Client.Index().Index(u.index).Id(id).BodyJson(user).Do(context.Background())
if err != nil {
log.Println("create doc failed", err)
return err
}
return nil
}
func (u *UserIndex) FindOne(uid int) (*UserModel, error) {
id := strconv.FormatInt(int64(uid), 10)
//使用Client.Get()返回GetService,其余操作均与上面一致
resp, err := Client.Get().Index(u.index).Id(id).Do(context.Background())
if err != nil {
log.Println("find doc failed", err)
return nil, err
}
if resp.Found {
//若获取到数据,将目标数据解析到UserModel中
var user UserModel
_ = json.Unmarshal(resp.Source, &user)
return &user, nil
} else {
return nil, errors.New("data not found")
}
}
func (u *UserIndex) UpdateOne(user UserModel) error {
id := strconv.FormatInt(int64(user.Id), 10)
//使用Client.Get()返回GetService,其余操作均与上面一致
_, err := Client.Update().Index(u.index).Id(id).Doc(user).Do(context.Background())
if err != nil {
log.Println("update doc failed", err)
return err
}
return nil
}
func (u *UserIndex) DeleteOne(user UserModel) error {
id := strconv.FormatInt(int64(user.Id), 10)
_, err := Client.Delete().Index(u.index).Id(id).Do(context.Background())
if err != nil {
log.Println("update doc failed", err)
return err
}
return nil
}
3.2 代码解析
单条数据操作代码较为简单。主要是直接通过elastic.Client
对象调用对应的方法,获取对应的Service对象,使用链式调用的形式设置index
,id
,body
,最后通过do方法发起请求。
Get()方法的返回值,与使用Restful的GET接口获取的值一致。当Get()执行成功后,会将返回值序列化到GetResult
结构体中,查询内容保存在Source
字段中。只需要使用json
包进行反序列化至数据model中即可。
golang
type GetResult struct {
Index string `json:"_index"`
Type string `json:"_type"`
Id string `json:"_id"`
Uid string `json:"_uid"`
Routing string `json:"_routing"`
Parent string `json:"_parent"`
Version *int64 `json:"_version"`
SeqNo *int64 `json:"_seq_no"`
PrimaryTerm *int64 `json:"_primary_term"`
Source json.RawMessage `json:"_source,omitempty"`
Found bool `json:"found,omitempty"`
Fields map[string]interface{} `json:"fields,omitempty"`
Error *ErrorDetails `json:"error,omitempty"`
}
4、数据批量操作
4.1 Bulk批量操作API说明
在Elasticsearch中,批量查是通过POST _bulk
关键字实现的。其基本请求格式为:
arduino
action_and_meta_data\n //操作类型和文档信息
optional_source\n //操作类型对应的资源数据,比如:添加操作的需要添加的文档数据
action_and_meta_data\n
optional_source\n
...
每个操作及对应的文档资源都通过\n
进行区分,同时每个操作及对象都是一个独立的json
字符串。例如:
json
POST _bulk
{ "index" : { "_index" : "test", "_id" : "1" } }
{ "field1" : "value1" }
{ "delete" : { "_index" : "test", "_id" : "2" } }
{ "create" : { "_index" : "test", "_id" : "3" } }
{ "field1" : "value3" }
{ "update" : {"_id" : "1", "_index" : "test"} }
{ "doc" : {"field2" : "value2"} }
在elastic
包中,以上操作通过Client.Bulk()
返回的BulkService
对象来实现。
更多关于Bulk操作的信息请见文档,地址为:www.elastic.co/guide/en/el...
4.2 复合查询简介
Elasticsearch复合查询主要有Bool查询、聚合查询、Function Score评分、Fuzzy模糊查询等。本文主要涉及bool查询和评分查询。
4.2.1 Bool查询
Bool查询使用布尔逻辑组合多个查询条件,最终返回符合条件的数据。涉及的查询关键字有以下几个:
-
must:这个关键字用于指定必须匹配的查询条件。如果一个文档不满足must子句中的所有条件,它将被认为是不匹配的。
-
filter:filter关键字与must关键字类似,但它对结果不进行评分。它主要用于过滤查询结果,提高查询性能。
-
should:这个关键字用于指定应该匹配的查询条件,但不是必需的。如果一个文档满足should子句中的任何条件,它将被认为是匹配的。should查询可以用于实现与或非逻辑。
-
must_not:这个关键字用于指定不应该匹配的查询条件。如果一个文档满足must_not子句中的条件,它将被认为是不匹配的。
-
minimum_should_match:这个关键字用于指定should子句中至少需要满足的条件数量。
-
boost:这个关键字用于给某个查询条件设置权重,以影响匹配的相关性评分。
-
_name:这个关键字用于给一个bool查询命名,方便在复杂查询中引用。
4.2.2 Function Score
Function Score可以根据查询条件指定匹配权重,最终按照评分高低进行排序,提取查询结果。在Elasticsearch中的Function Score查询中,可以使用以下关键字:
- query:这个关键字用于指定一个子查询(query)来计算文档的相关性分数。子查询可以是任何类型的查询,例如match、term等。
- boost:这个关键字用于通过设置权重来影响文档的相关性分数。较高的权重值将增加文档的相关性,较低的权重值将减少相关性。
- functions:这个关键字用于指定一个或多个函数来修改文档的相关性分数。每个函数都可以根据不同的规则和逻辑来计算分数。
- script_score:这个关键字用于通过自定义脚本来计算文档的相关性分数。使用脚本语言(如Painless)编写自定义逻辑来计算分数。
- boost_mode:这个关键字用于指定如何组合query子查询得分和functions或script_score的分数。可选的组合模式有multiply、replace、sum、avg和max。
- score_mode:这个关键字用于指定如何计算多个函数得分的组合分数。可选的计算模式有multiply、sum、avg、first和max。
- max_boost:这个关键字用于限制相关性分数的最大增益。通过设置一个较小的值,可以防止某些函数过度增加分数。
- min_score:这个关键字用于设置结果集中文档的最小相关性分数。低于指定的分数的文档将被过滤掉。
4.3 代码内容
bulk操作的代码均在./bulk/main.go
中。
4.3.1 增加、删除、修改操作
批量添加、删除、修改操作非常相似、也很简单。主要是以下几步:
- 通过
Client.Bulk().Index(index name)
获取批量操作的对象*BulkService
; - 通过
elastic.NewBulkIndexRequest()
获取BulkIndexRequest
对象,设定文档id和文档内容;根据不同的操作,有不同的New函数:NewBulkIndexRequest()
、NewBulkIndexRequest()
、NewBulkDeleteRequest()
。 - 通过
func (s *BulkService) Add(requests ...BulkableRequest) *BulkService
方法将需要修改的信息添加到请求中; - 使用
func (s *BulkService) NumberOfActions() int
查看本次批量操作请求数量,如果为0则可以直接返回,不用发起请求; - 最后执行
func (s *BulkService) Do(ctx context.Context) (*BulkResponse, error)
方法,执行批量操作。
./bulk/main.go
golang
/*
基本通用代码
*/
//BulkCreate 批量创建
func (u *UserIndex) BulkCreate(ctx context.Context, users []UserModel) error {
request := Client.Bulk().Index(u.index)
for _, user := range users {
if user.Id == 0 {
continue
}
doc := elastic.NewBulkIndexRequest().Id(strconv.FormatInt(int64(user.Id), 10)).Doc(user)
request.Add(doc)
}
if request.NumberOfActions() < 0 {
return nil
}
if _, err := request.Do(ctx); err != nil {
return err
}
return nil
}
// BulkUpdate 批量修改
func (u *UserIndex) BulkUpdate(ctx context.Context, users []UserModel) error {
request := Client.Bulk().Index(u.index)
for _, user := range users {
if user.Id == 0 {
continue
}
doc := elastic.NewBulkUpdateRequest().Id(strconv.FormatInt(int64(user.Id), 10)).Doc(user)
request.Add(doc)
}
if request.NumberOfActions() < 0 {
return nil
}
if _, err := request.Do(ctx); err != nil {
return err
}
return nil
}
//BulkDelete 通过doc的id进行批量删除
func (u *UserIndex) BulkDelete(ctx context.Context, Ids []int64) error {
req := Client.Bulk().Index(u.index)
for _, id := range Ids {
doc := elastic.NewBulkDeleteRequest().Id(strconv.FormatInt(id, 10))
req.Add(doc)
}
if req.NumberOfActions() < 0 {
return nil
}
if _, err := req.Do(ctx); err != nil {
return err
}
return nil
}
4.3.2 条件查询
Elasticsearch中最重要的就是数据的检索,对应到操作中就是对数据的条件查询。Bool查询基本用法不再赘述,直接看代码实现:
golang
/*
批量操作代码
*/
//SearchUser 复合查询
//此处匹配条件为gender为2、id不为1、age与传入的值相差5岁的权重得3分,相差5-10岁的得2分,相差10岁以上不得分
//结果按照得分情况,分值由高到低排序
func (u *UserIndex) SearchUser() ([]UserModel, error) {
//初始化bool Query
boolQuery := elastic.NewBoolQuery()
//匹配gender为2
boolQuery = boolQuery.Must(elastic.NewTermQuery("gender", 2))
//匹配id不为1的
boolQuery = boolQuery.MustNot(elastic.NewTermQuery("id", 1))
//使用script计算评分,age与传入的值相差5岁的权重得3分,相差5-10岁的得2分,相差10岁以上不得分
// 定义评分函数
scoreFuncs := make([]elastic.ScoreFunction, 0)
scoreFuncs = append(scoreFuncs, elastic.NewScriptFunction(elastic.NewScript(`
double diff = Math.abs(doc['age'].value - params.inputAge);
if (diff <= 5) {
return 3;
} else if (diff <= 10) {
return 2;
} else {
return 0;
}
`).Param("inputAge", 25))) // 假设传入的值是25
// 创建function Score 对象
funcScoreQuery := elastic.NewFunctionScoreQuery().Query(boolQuery).ScoreMode("sum").BoostMode("sum")
//循环添加评分条件函数
for _, scoreFunc := range scoreFuncs {
funcScoreQuery = funcScoreQuery.AddScoreFunc(scoreFunc)
}
//打印生成的查询体
soces, _ := funcScoreQuery.Source()
bd, _ := json.Marshal(soces)
fmt.Println(string(bd))
// 执行查询
resp, err := Client.Search().Index(u.index).Query(funcScoreQuery).Do(context.Background())
if err != nil {
log.Println(err)
return nil, err
}
fmt.Printf("共找到%d个匹配结果\n", resp.TotalHits())
for _, hit := range resp.Hits.Hits {
fmt.Printf("ID: %s, Score: %f\n", hit.Id, *hit.Score)
}
//获取返回信息
var users []UserModel
for _, v := range resp.Each(reflect.TypeOf(UserModel{})) {
us := v.(UserModel)
users = append(users, us)
}
return users, nil
以上代码生成的json查询语句:
json
{
"function_score": {
"boost_mode": "sum",
"functions": [
{
"script_score": {
"script": {
"params": {
"inputAge": 25
},
"source": "double diff = Math.abs(doc['age'].value - params.inputAge);\n\t\tif (diff \u003c= 5) {\n\t\t\treturn 3;\n\t\t} else if (diff \u003c= 10) {\n\t\t\treturn 2;\n\t\t} else {\n\t\t\treturn 0;\n\t\t}"
}
}
}
],
"query": {
"bool": {
"must": {
"term": {
"gender": 2
}
},
"must_not": {
"term": {
"id": 1
}
}
}
},
"score_mode": "sum"
}
}
4.3.3 评分查询接口扩展
在github.com/olivere/elastic/v7
包中,对评分查询定义了接口ScoreFunction
,但实现的查询类型不多,我们可以根据自己的查询需求,通过将实现ScoreFunction
接口的结构体对象添加到评分查询中实现自己的需求。
scss
// ScoreFunction is used in combination with the Function Score Query.
type ScoreFunction interface {
Name() string
//获取权重方法
GetWeight() *float64 // returns the weight which must be serialized at the level of FunctionScoreQuery
// 生成评分条件的json字符串方法
Source() (interface{}, error)
}
目前已实现的评分类型:
更多Es的评分操作参见文档:www.elastic.co/guide/en/el...
四、参考资料
官方文档:www.elastic.co/guide/en/el...
巨人的肩膀:cloud.tencent.com/developer/a...
五、全部代码
1、./single/main.go
./single/main.go
golang
package main
import (
"context"
"encoding/json"
"errors"
"fmt"
"github.com/olivere/elastic/v7"
"log"
"os"
"strconv"
)
var Client *elastic.Client
// Init 初始化链接
func Init() {
var err error
urls := []string{"http://192.168.94.128:19200"}
Client, err = elastic.NewClient(
elastic.SetURL(urls...),
elastic.SetSniff(false),
elastic.SetBasicAuth("elastic", "wangli123"),
elastic.SetErrorLog(log.New(os.Stderr, "ELASTIC_ERROR ", log.LstdFlags)),
elastic.SetInfoLog(log.New(os.Stdout, "ELASTIC ", log.LstdFlags)),
)
if err != nil {
panic(err)
}
}
// User 假定有一个user数据,字段内容为id,name,age,city,tags
//对应的index名称为new_es_user
const userIndex = "new_es_user"
//mapping json 字符串
var userMapping = `{
"mappings": {
"properties": {
"id": {
"type": "keyword"
},
"name": {
"type": "text"
},
"gender": {
"type": "keyword"
},
"age": {
"type": "integer"
},
"City": {
"type": "text"
},
"Tags": {
"type": "keyword"
}
}
}
}`
// UserModel 与mapping对应的model,用于插入、修改、查询
type UserModel struct {
Id int `json:"id"`
Name string `json:"name"`
Gender int `json:"gender"` //1-男 2-女
Age int `json:"age"`
City string `json:"city"`
Tags []string `json:"tags"`
}
// UserIndex 对user信息进行操作对象
type UserIndex struct {
index string
mapping string
}
func NewUserIndex() (*UserIndex, error) {
user := &UserIndex{
index: userIndex,
mapping: userMapping,
}
err := user.init()
if err != nil {
return nil, err
}
return user, nil
}
// 初始化index,保证对应的index在Es中存在,并定义mapping,方便后续操作
func (u *UserIndex) init() error {
ctx := context.Background()
exist, err := Client.IndexExists(u.index).Do(ctx)
if err != nil {
fmt.Println("index check exist failed", err)
return err
}
if !exist {
_, err = Client.CreateIndex(u.index).Body(u.mapping).Do(ctx)
if err != nil {
fmt.Println("create index failed", err)
return err
}
}
return nil
}
func (u *UserIndex) CreateOne(user UserModel) error {
id := strconv.FormatInt(int64(user.Id), 10)
_, err := Client.Index().Index(u.index).Id(id).BodyJson(user).Do(context.Background())
if err != nil {
log.Println("create doc failed", err)
return err
}
return nil
}
func (u *UserIndex) FindOne(uid int) (*UserModel, error) {
id := strconv.FormatInt(int64(uid), 10)
resp, err := Client.Get().Index(u.index).Id(id).Do(context.Background())
if err != nil {
log.Println("find doc failed", err)
return nil, err
}
if resp.Found {
var user UserModel
_ = json.Unmarshal(resp.Source, &user)
return &user, nil
} else {
return nil, errors.New("data not found")
}
}
func (u *UserIndex) UpdateOne(user UserModel) error {
id := strconv.FormatInt(int64(user.Id), 10)
_, err := Client.Update().Index(u.index).Id(id).Doc(user).Do(context.Background())
if err != nil {
log.Println("update doc failed", err)
return err
}
return nil
}
func (u *UserIndex) DeleteOne(user UserModel) error {
id := strconv.FormatInt(int64(user.Id), 10)
_, err := Client.Delete().Index(u.index).Id(id).Do(context.Background())
if err != nil {
log.Println("update doc failed", err)
return err
}
return nil
}
func main() {
Init()
OperationSingleDataTest()
}
func OperationSingleDataTest() {
index, err := NewUserIndex()
if err != nil {
panic(err)
}
userInfo := UserModel{
Id: 2,
Name: "tom",
Gender: 2,
Age: 15,
City: "北京",
Tags: []string{"handsome"},
}
err = index.CreateOne(userInfo)
if err != nil {
return
}
us, err := index.FindOne(userInfo.Id)
if err != nil {
return
}
fmt.Printf("create : %+v\n", us)
userInfo.Gender = 1
userInfo.Tags = []string{"lovely"}
err = index.UpdateOne(userInfo)
if err != nil {
return
}
us, err = index.FindOne(userInfo.Id)
if err != nil {
return
}
fmt.Printf("update : %+v\n", us)
err = index.DeleteOne(userInfo)
if err != nil {
return
}
us, err = index.FindOne(userInfo.Id)
if err != nil {
return
}
fmt.Printf("delete: %+v\n", us)
}
2、./bulk/main.go
./bulk/main.go
golang
package main
import (
"context"
"demo1/es-demo/random"
"encoding/json"
"fmt"
"github.com/olivere/elastic/v7"
"log"
"os"
"reflect"
"strconv"
)
var Client *elastic.Client
// Init 初始化链接
func Init() {
var err error
urls := []string{"http://192.168.94.128:19200"}
Client, err = elastic.NewClient(
elastic.SetURL(urls...),
elastic.SetSniff(false),
elastic.SetBasicAuth("elastic", "wangli123"),
elastic.SetErrorLog(log.New(os.Stderr, "ELASTIC_ERROR ", log.LstdFlags)),
elastic.SetInfoLog(log.New(os.Stdout, "ELASTIC ", log.LstdFlags)),
)
if err != nil {
panic(err)
}
}
// User 假定有一个user数据,字段内容为id,name,age,city,tags
//对应的index名称为new_es_user
const userIndex = "new_es_user"
//mapping json 字符串
var userMapping = `{
"mappings": {
"properties": {
"id": {
"type": "keyword"
},
"name": {
"type": "text"
},
"gender": {
"type": "keyword"
},
"age": {
"type": "integer"
},
"City": {
"type": "text"
},
"Tags": {
"type": "keyword"
}
}
}
}`
// UserModel 与mapping对应的model,用于插入、修改、查询
type UserModel struct {
Id int `json:"id"`
Name string `json:"name"`
Gender int `json:"gender"` //1-男 2-女
Age int `json:"age"`
City string `json:"city"`
Tags []string `json:"tags"`
}
// UserIndex 对user信息进行操作对象
type UserIndex struct {
index string
mapping string
}
func NewUserIndex() (*UserIndex, error) {
user := &UserIndex{
index: userIndex,
mapping: userMapping,
}
err := user.init()
if err != nil {
return nil, err
}
return user, nil
}
func (u *UserIndex) init() error {
ctx := context.Background()
exist, err := Client.IndexExists(u.index).Do(ctx)
if err != nil {
fmt.Println("index check exist failed", err)
return err
}
if !exist {
_, err = Client.CreateIndex(u.index).Body(u.mapping).Do(ctx)
if err != nil {
fmt.Println("create index failed", err)
return err
}
}
return nil
}
//BulkCreate 批量创建
func (u *UserIndex) BulkCreate(ctx context.Context, users []UserModel) error {
request := Client.Bulk().Index(u.index)
for _, user := range users {
if user.Id == 0 {
continue
}
doc := elastic.NewBulkIndexRequest().Id(strconv.FormatInt(int64(user.Id), 10)).Doc(user)
request.Add(doc)
}
if request.NumberOfActions() < 0 {
return nil
}
if _, err := request.Do(ctx); err != nil {
return err
}
return nil
}
// BulkUpdate 批量修改
func (u *UserIndex) BulkUpdate(ctx context.Context, users []UserModel) error {
request := Client.Bulk().Index(u.index)
for _, user := range users {
if user.Id == 0 {
continue
}
doc := elastic.NewBulkIndexRequest().Id(strconv.FormatInt(int64(user.Id), 10)).Doc(user)
request.Add(doc)
}
if request.NumberOfActions() < 0 {
return nil
}
if _, err := request.Do(ctx); err != nil {
return err
}
return nil
}
//BulkDelete 通过doc的id进行批量删除
func (u *UserIndex) BulkDelete(ctx context.Context, Ids []int64) error {
req := Client.Bulk().Index(u.index)
for _, id := range Ids {
doc := elastic.NewBulkDeleteRequest().Id(strconv.FormatInt(id, 10))
req.Add(doc)
}
if req.NumberOfActions() < 0 {
return nil
}
if _, err := req.Do(ctx); err != nil {
return err
}
return nil
}
//SearchUser 复合查询
//此处匹配条件为gender为2、id不为1、age与传入的值相差5岁的权重得3分,相差5-10岁的得2分,相差10岁以上不得分
//结果按照得分情况,分值由高到低排序
func (u *UserIndex) SearchUser() ([]UserModel, error) {
//初始化bool Query
boolQuery := elastic.NewBoolQuery()
//匹配gender为2
boolQuery = boolQuery.Must(elastic.NewTermQuery("gender", 2))
//匹配id不为1的
boolQuery = boolQuery.MustNot(elastic.NewTermQuery("id", 1))
//使用script计算评分,age与传入的值相差5岁的权重得3分,相差5-10岁的得2分,相差10岁以上不得分
// 定义评分函数
scoreFuncs := make([]elastic.ScoreFunction, 0)
scoreFuncs = append(scoreFuncs, elastic.NewScriptFunction(elastic.NewScript(`
double diff = Math.abs(doc['age'].value - params.inputAge);
if (diff <= 5) {
return 3;
} else if (diff <= 10) {
return 2;
} else {
return 0;
}
`).Param("inputAge", 25))) // 假设传入的值是25
funcScoreQuery := elastic.NewFunctionScoreQuery().Query(boolQuery).ScoreMode("sum").BoostMode("sum")
for _, scoreFunc := range scoreFuncs {
funcScoreQuery = funcScoreQuery.AddScoreFunc(scoreFunc)
}
//打印生成的查询体
soces, _ := funcScoreQuery.Source()
bd, _ := json.Marshal(soces)
fmt.Println(string(bd))
// 执行查询
resp, err := Client.Search().Index(u.index).Query(funcScoreQuery).Do(context.Background())
if err != nil {
log.Println(err)
return nil, err
}
fmt.Printf("共找到%d个匹配结果\n", resp.TotalHits())
for _, hit := range resp.Hits.Hits {
fmt.Printf("ID: %s, Score: %f\n", hit.Id, *hit.Score)
}
//获取返回信息
var users []UserModel
for _, v := range resp.Each(reflect.TypeOf(UserModel{})) {
us := v.(UserModel)
users = append(users, us)
}
return users, nil
}
func main() {
Init()
BulkOperationTest()
}
func BulkOperationTest() {
index, err := NewUserIndex()
if err != nil {
panic(err)
}
//users := GenerateInfo()
//err = index.BulkCreate(context.Background(), users)
//if err != nil {
// return
//}
userResp, err := index.SearchUser()
if err != nil {
return
}
for _, v := range userResp {
fmt.Printf("%+v\n", v)
}
//err = index.BulkUpdate(context.Background(), users)
//if err != nil {
// return
//}
//var docId []int64
//for _, v := range users {
// docId = append(docId, int64(v.Id))
//}
//
//err = index.BulkDelete(context.Background(), docId)
//if err != nil {
// return
//}
}
// GenerateInfo 生成测试user信息
func GenerateInfo() []UserModel {
var users []UserModel
for i := 1; i < 10; i++ {
user := UserModel{
Id: i,
Name: random.RandomOwner(),
Gender: int(random.RandomInt(1, 2)),
Age: int(random.RandomInt(10, 50)),
City: "天津",
Tags: []string{random.RandomTag(), random.RandomTag()},
}
users = append(users, user)
}
return users
}
//共找到4个匹配结果
//ID: 9, Score: 3.597837
//ID: 5, Score: 2.597837
//ID: 6, Score: 2.597837
//ID: 4, Score: 0.597837
//{Id:9 Name:ditdto Gender:2 Age:22 City:天津 Tags:[lovely lovely]}
//{Id:5 Name:fwlylj Gender:2 Age:35 City:天津 Tags:[handsome handsome]}
//{Id:6 Name:qivpgk Gender:2 Age:31 City:天津 Tags:[handsome awesome]}
//{Id:4 Name:fkuyyq Gender:2 Age:11 City:天津 Tags:[handsome awesome]}
新手入门,记录收藏,如有错误,敬请指正