likes
comments
collection
share

如何使用Golang操作elasticsearch进行增、删、改、查(复合查询)?----新手入门指南

作者站长头像
站长
· 阅读数 20

一、概述

Elasticsearch原生提供restful的接口用于操作,但是在实际开发时,往往我们是使用与自己开发语言对应的库来对Es进行操作,这会使开发更为方便,而且代码可读性和维护性都比较高。官方也提供了多语言的库方便用户进行开发。

Golang的Es官方包为:go-elasticsearch地址:github.com/elastic/go-…

Golang的Es官方包提供的功能很齐全,但是比较底层,很多功能没有封装,使用有一定难度,不太友好。在日常开发中,我们经常使用的开源库为:github.com/olivere/elastic,目前最高支持到Es的V7。地址:github.com/olivere/ela…

二、版本对齐

本文操作使用到的组件对应的版本:

Elasticsearch:7.17.1 golang 1.17 github.com/olivere/elastic/v7 v7.0.32

三、使用Go进行操作

1、操作说明

本次操作,将会分别对单条数据、多条数据进行对应的增、删、改、查操作,为了代码编写方便,将会分别创建single和bulk文件夹,在这两个文件夹分别创建main.go文件,所有示例代码均写在对应的main.go文件中。在实际开发中,需要自行根据业务进行拆分、封装。 以下是本文代码文件结构:

$ 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定义、基本操作类的封装。在singlebulk的操作都以此为基本,进行对应的编码。所以此代码需要同时放入./single/main.go./bulk/main.go

2.1 代码内容

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 代码内容


//接通用代码内容进行延续
//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中即可。

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关键字实现的。其基本请求格式为:

action_and_meta_data\n   //操作类型和文档信息
optional_source\n        //操作类型对应的资源数据,比如:添加操作的需要添加的文档数据
action_and_meta_data\n
optional_source\n
...

每个操作及对应的文档资源都通过\n进行区分,同时每个操作及对象都是一个独立的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 增加、删除、修改操作

批量添加、删除、修改操作非常相似、也很简单。主要是以下几步:

  1. 通过Client.Bulk().Index(index name)获取批量操作的对象*BulkService
  2. 通过elastic.NewBulkIndexRequest()获取BulkIndexRequest对象,设定文档id和文档内容;根据不同的操作,有不同的New函数:NewBulkIndexRequest()NewBulkIndexRequest()NewBulkDeleteRequest()
  3. 通过func (s *BulkService) Add(requests ...BulkableRequest) *BulkService方法将需要修改的信息添加到请求中;
  4. 使用func (s *BulkService) NumberOfActions() int 查看本次批量操作请求数量,如果为0则可以直接返回,不用发起请求;
  5. 最后执行func (s *BulkService) Do(ctx context.Context) (*BulkResponse, error)方法,执行批量操作。

./bulk/main.go


/*
    基本通用代码
*/

//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查询基本用法不再赘述,直接看代码实现:


/*
    批量操作代码
*/

//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查询语句:

{
  "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接口的结构体对象添加到评分查询中实现自己的需求。

// 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)
}

目前已实现的评分类型:

如何使用Golang操作elasticsearch进行增、删、改、查(复合查询)?----新手入门指南

更多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

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

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]}

新手入门,记录收藏,如有错误,敬请指正