📚 olivere/elastic v7 详细介绍
这个库是由 Oliver Eilhard 开发并维护的,主要用于在 Go 语言应用程序中与 Elasticsearch 进行交互。v7 代表它是专门为 Elasticsearch 7.x 版本设计的客户端。
github.com/olivere/elastic/v7 是 Go 语言(Golang)生态中最著名、最广泛使用的 Elasticsearch 客户端库。
客户端链接
go
func EsConnect() {
/*
ES连接问题分析与解决方案
========================
【之前连接不上的原因】
--------------------
1. 协议不匹配
- ES配置:xpack.security.http.ssl.enabled: true(要求HTTPS)
- 代码配置:使用 http:// 连接
- 结果:ES拒绝HTTP连接
2. SSL证书验证失败
- ES使用自签名证书(certs/http.p12)
- Go的HTTP客户端默认验证SSL证书有效性
- 结果:证书验证失败,连接被拒绝
【现在能连接上的原因】
--------------------
1. 使用正确的协议
- 代码改用 https:// 连接,与ES配置匹配
- ES接受HTTPS连接请求
2. 跳过SSL证书验证
- 设置 InsecureSkipVerify: true
- 跳过ES自签名证书的验证
- 连接不被证书问题阻止
【关键配置总结】
----------------
ES配置:
- xpack.security.enabled: true(启用安全认证)
- xpack.security.http.ssl.enabled: true(启用HTTPS)
- xpack.security.transport.ssl.enabled: true(启用SSL加密)
代码配置:
- URL: https://118.195.189.68:9200(使用HTTPS)
- SSL验证: InsecureSkipVerify: true(跳过证书验证)
- SetSniff: false(禁用节点发现)
*/
config := struct {
name string
username string
password string
useHTTP bool
}{"带BasicAuth连接", "elastic", "abcdefghigklmn", true}
var client *elastic.Client
var err error
// 设置跳过SSL证书验证的HTTP客户端
// 原因:ES使用自签名证书,需要跳过验证才能连接
httpClient := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true, // 跳过证书验证
},
},
}
if config.useHTTP {
client, err = elastic.NewClient(
elastic.SetURL("https://118.195.189.68:9200"), // 使用HTTPS协议,适配ES的SSL配置
elastic.SetSniff(false), // 禁用节点发现,防止自动连接其他节点
elastic.SetBasicAuth(config.username, config.password), // 设置用户名密码认证
elastic.SetHttpClient(httpClient), // 使用自定义HTTP客户端,跳过SSL证书验证
)
if err != nil {
log.Fatalf("Error creating Elasticsearch client: %s", err)
panic("ESClient 链接失败")
}
}
// 测试基本连接
ctx := context.Background()
_, _, err = client.Ping("https://118.195.189.68:9200").Do(ctx)
if err != nil {
fmt.Printf(" ✗ Ping失败: %s\n", err)
} else {
fmt.Printf(" ✓ Ping成功\n")
}
// 测试获取集群信息
info, err := client.ClusterHealth().Do(ctx)
if err != nil {
fmt.Printf(" ✗ 获取集群信息失败: %s\n", err)
} else {
fmt.Printf(" ✓ 集群信息获取成功 (状态: %s)\n", info.Status)
}
// 测试获取索引列表
indices, err := client.CatIndices().Do(ctx)
if err != nil {
fmt.Printf(" ✗ 获取索引列表失败: %s\n", err)
} else {
fmt.Printf(" ✓ 获取索引列表成功 (共%d个索引)\n", len(indices))
}
ESClient = client
}
IK分词器测试
go
func TestIKAnalyzer(ctx context.Context) {
// 检查是否已存在测试索引
exists, err := ESClient.IndexExists("ik_test_index").Do(ctx)
if err != nil {
fmt.Printf("检查索引失败: %s\n", err)
return
}
// 如果已存在,先删除
if exists {
ESClient.DeleteIndex("ik_test_index").Do(ctx)
}
// 创建使用IK分词器的测试索引
createIndex, err := ESClient.CreateIndex("ik_test_index").BodyJson(map[string]any{
"settings": map[string]any{
"number_of_shards": 1,
"number_of_replicas": 0,
"analysis": map[string]any{
"analyzer": map[string]any{
"ik_max_word_analyzer": map[string]any{
"type": "custom",
"tokenizer": "ik_max_word",
},
"ik_smart_analyzer": map[string]any{
"type": "custom",
"tokenizer": "ik_smart",
},
},
},
},
"mappings": map[string]any{
"properties": map[string]any{
"id": map[string]any{
"type": "integer",
},
"text": map[string]any{
"type": "text",
"analyzer": "ik_max_word",
"search_analyzer": "ik_smart",
"fields": map[string]any{
"keyword": map[string]any{
"type": "keyword",
},
},
},
},
},
}).Do(ctx)
if err != nil {
fmt.Printf("IK分词器测试失败,创建索引失败: %s\n", err)
fmt.Println("提示: 请先安装IK分词器插件")
fmt.Println("安装命令(在ES安装目录执行): ./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.17.0/elasticsearch-analysis-ik-7.17.0.zip")
fmt.Println("安装后需要重启Elasticsearch")
return
}
if !createIndex.Acknowledged {
fmt.Println("IK分词器测试失败,索引创建未被确认")
return
}
fmt.Println("✓ IK分词器索引创建成功")
// 添加测试文档
testTexts := []struct {
ID int
Text string
}{
{1, "中华人民共和国国歌"},
{2, "北京天安门广场"},
{3, "人工智能与大数据技术"},
{4, "Elasticsearch搜索引擎"},
{5, "中文分词技术"},
}
fmt.Println("\n添加测试文档:")
for _, item := range testTexts {
putResult, err := ESClient.Index().
Index("ik_test_index").
Id(fmt.Sprintf("%d", item.ID)).
BodyJson(map[string]any{
"id": item.ID,
"text": item.Text,
}).
Do(ctx)
if err != nil {
fmt.Printf(" 添加文档失败: %s\n", err)
continue
}
fmt.Printf(" ✓ 添加成功: ID=%s, 文本=%s\n", putResult.Id, item.Text)
}
// 等待索引刷新
ESClient.Refresh("ik_test_index").Do(ctx)
// 测试分词效果 - 搜索测试
fmt.Println("\n========== IK分词搜索测试 ==========")
keywords := []string{
"中华",
"人民",
"共和国",
"国歌",
"分词",
"人工智能",
"搜索",
"数据",
"天安",
"门",
}
for _, keyword := range keywords {
query := elastic.NewMatchQuery("text", keyword)
searchResult, err := ESClient.Search().
Index("ik_test_index").
Query(query).
Size(10).
Do(ctx)
if err != nil {
fmt.Printf("搜索关键词 '%s' 失败: %s\n", keyword, err)
continue
}
if searchResult.Hits.TotalHits.Value > 0 {
fmt.Printf("\n搜索关键词: '%s', 找到 %d 个匹配文档\n", keyword, searchResult.Hits.TotalHits.Value)
for _, hit := range searchResult.Hits.Hits {
var doc struct {
ID int `json:"id"`
Text string `json:"text"`
}
json.Unmarshal(hit.Source, &doc)
fmt.Printf(" - 文档%d: %s\n", doc.ID, doc.Text)
}
} else {
fmt.Printf("搜索关键词: '%s', 未找到匹配文档\n", keyword)
}
}
// 测试短语匹配
fmt.Println("\n========== 短语匹配测试 ==========")
phrases := []string{
"中华",
"人民共和",
"共和国",
"搜索引擎",
"分词技术",
"天安",
}
for _, phrase := range phrases {
query := elastic.NewMatchPhraseQuery("text", phrase)
searchResult, err := ESClient.Search().
Index("ik_test_index").
Query(query).
Size(10).
Do(ctx)
if err != nil {
fmt.Printf("短语搜索 '%s' 失败: %s\n", phrase, err)
continue
}
if searchResult.Hits.TotalHits.Value > 0 {
fmt.Printf("\n短语匹配: '%s', 找到 %d 个匹配文档\n", phrase, searchResult.Hits.TotalHits.Value)
for _, hit := range searchResult.Hits.Hits {
var doc struct {
ID int `json:"id"`
Text string `json:"text"`
}
json.Unmarshal(hit.Source, &doc)
fmt.Printf(" - 文档%d: %s\n", doc.ID, doc.Text)
}
} else {
fmt.Printf("短语匹配: '%s', 未找到匹配文档\n", phrase)
}
}
// IK分词器验证总结
fmt.Println("\n========== IK分词器验证总结 ==========")
fmt.Println("✓ 如果能搜索到 '中华'、'人民'、'共和国',说明IK分词器正常工作")
fmt.Println("✓ 如果能短语匹配到 '共和国',说明分词正确")
fmt.Println("✗ 如果无法搜索到 '中华',说明IK分词器可能未安装或配置有问题")
fmt.Println("提示:可以使用 Kibana Dev Tools 或 curl 直接测试分词效果:")
fmt.Println(`POST /_analyze`)
fmt.Println(`{`)
fmt.Println(` "analyzer": "ik_max_word",`)
fmt.Println(` "text": "中华人民共和国国歌"`)
fmt.Println(`}`)
// 清理测试索引
DeleteIndex(ctx, "ik_test_index")
}
// CreateArticleIndex 创建使用IK分词器的文章索引
func CreateArticleIndex(ctx context.Context) {
exists, err := ESClient.IndexExists("articles_index").Do(ctx)
if err != nil {
log.Fatalf("Error checking index existence: %s", err)
}
if exists {
fmt.Println("文章索引已存在")
return
}
// 创建索引并指定IK分词器的mapping
createIndex, err := ESClient.CreateIndex("articles_index").BodyJson(map[string]any{
"settings": map[string]any{
"number_of_shards": 1,
"number_of_replicas": 0,
"analysis": map[string]any{
"analyzer": map[string]any{
"ik_pinyin_analyzer": map[string]any{
"type": "custom",
"tokenizer": "ik_max_word",
},
},
},
},
"mappings": map[string]any{
"properties": map[string]any{
"id": map[string]any{
"type": "integer",
},
"title": map[string]any{
"type": "text",
"analyzer": "ik_max_word",
"search_analyzer": "ik_smart",
"fields": map[string]any{
"keyword": map[string]any{
"type": "keyword",
},
},
},
"content": map[string]any{
"type": "text",
"analyzer": "ik_max_word",
"search_analyzer": "ik_smart",
},
"author": map[string]any{
"type": "text",
"analyzer": "ik_max_word",
},
"tags": map[string]any{
"type": "keyword",
},
"publish_time": map[string]any{
"type": "date",
},
},
},
}).Do(ctx)
if err != nil {
log.Fatalf("Error creating articles index: %s", err)
}
if createIndex.Acknowledged {
fmt.Println("文章索引创建成功(使用IK分词器)")
}
}
// TestIKSearch 测试IK分词器搜索
func TestIKSearch(ctx context.Context) {
// 创建使用IK分词器的索引
CreateArticleIndex(ctx)
// 添加一些中文文章数据
articles := []ArticleModel{
{
ID: 1,
Title: "Elasticsearch简介",
Content: "Elasticsearch是一个基于Lucene的搜索引擎,它提供了一个分布式多用户能力的全文搜索引擎。",
Author: "张三",
Tags: []string{"搜索引擎", "数据库", "大数据"},
PublishTime: time.Now(),
},
{
ID: 2,
Title: "中文分词技术详解",
Content: "中文分词是中文信息处理的基础,常用的分词器有IK分词器、结巴分词器等。",
Author: "李四",
Tags: []string{"NLP", "分词", "中文"},
PublishTime: time.Now(),
},
{
ID: 3,
Title: "大数据技术在企业中的应用",
Content: "大数据技术包括数据采集、存储、处理、分析等环节,为企业提供决策支持。",
Author: "王五",
Tags: []string{"大数据", "企业应用", "数据分析"},
PublishTime: time.Now(),
},
{
ID: 4,
Title: "人工智能与机器学习",
Content: "人工智能是计算机科学的一个分支,机器学习是人工智能的核心技术之一。",
Author: "赵六",
Tags: []string{"AI", "机器学习", "人工智能"},
PublishTime: time.Now(),
},
}
fmt.Println("\n添加中文测试数据...")
for _, article := range articles {
AddArticleDocument(ctx, "articles_index", fmt.Sprintf("%d", article.ID), article)
}
// 测试中文搜索
fmt.Println("\n========== 中文搜索测试 ==========")
// 1. 精准短语搜索
SearchArticle(ctx, "articles_index", "分词")
// 2. 模糊搜索
SearchArticle(ctx, "articles_index", "数据")
// 3. 复合搜索
SearchArticle(ctx, "articles_index", "搜索引擎")
// 4. 短语匹配搜索
SearchArticleByPhrase(ctx, "articles_index", "中文分词")
// 清理测试索引
DeleteIndex(ctx, "articles_index")
}
索引操作
go
// CreateIndex 创建索引
func CreateIndex(ctx context.Context, indexName string) {
exists, err := ESClient.IndexExists(indexName).Do(ctx)
if err != nil {
log.Fatalf("Error checking index existence: %s", err)
}
if exists {
fmt.Printf("索引 %s 已存在\n", indexName)
return
}
// 创建索引并指定mapping
createIndex, err := ESClient.CreateIndex(indexName).BodyJson(map[string]any{
"settings": map[string]any{
"number_of_shards": 1,
"number_of_replicas": 0,
"analysis": map[string]any{
"analyzer": map[string]any{
"ik_pinyin_analyzer": map[string]any{
"type": "custom",
"tokenizer": "ik_max_word",
},
},
},
},
"mappings": map[string]any{
"properties": map[string]any{
"id": map[string]any{
"type": "integer",
},
"user_name": map[string]any{
"type": "keyword",
},
"nick_name": map[string]any{
"type": "text",
"analyzer": "ik_max_word",
"search_analyzer": "ik_smart",
"fields": map[string]any{
"keyword": map[string]any{
"type": "keyword",
},
},
},
"create_time": map[string]any{
"type": "date",
},
},
},
}).Do(ctx)
if err != nil {
log.Fatalf("Error creating index: %s", err)
}
if !createIndex.Acknowledged {
fmt.Printf("索引 %s 创建未被确认\n", indexName)
} else {
fmt.Printf("索引 %s 创建成功\n", indexName)
}
}
// CheckIndexExists 检查索引是否存在
func CheckIndexExists(ctx context.Context, indexName string) {
exists, err := ESClient.IndexExists(indexName).Do(ctx)
if err != nil {
log.Fatalf("Error checking index existence: %s", err)
}
if exists {
fmt.Printf("索引 %s 存在\n", indexName)
} else {
fmt.Printf("索引 %s 不存在\n", indexName)
}
}
// DeleteIndex 删除索引
func DeleteIndex(ctx context.Context, indexName string) {
exists, err := ESClient.IndexExists(indexName).Do(ctx)
if err != nil {
log.Fatalf("Error checking index existence: %s", err)
}
if !exists {
fmt.Printf("索引 %s 不存在,无需删除\n", indexName)
return
}
deleteIndex, err := ESClient.DeleteIndex(indexName).Do(ctx)
if err != nil {
log.Fatalf("Error deleting index: %s", err)
}
if !deleteIndex.Acknowledged {
fmt.Printf("索引 %s 删除未被确认\n", indexName)
} else {
fmt.Printf("索引 %s 删除成功\n", indexName)
}
}
文档操作
增
go
// AddDocument 添加单个文档
func AddDocument(ctx context.Context, indexName string, docID string, doc any) {
putResult, err := ESClient.Index().
Index(indexName).
Id(docID). // 指定文档ID,如果不指定,ES会自动生成一个唯一ID
BodyJson(doc).
Do(ctx)
if err != nil {
log.Fatalf("Error adding document: %s", err)
}
fmt.Printf("文档添加成功 - ID: %s, Index: %s, Type: %s, Result: %s\n",
putResult.Id, putResult.Index, putResult.Type, putResult.Result)
}
// BulkAddDocuments 批量添加文档
func BulkAddDocuments(ctx context.Context, indexName string, docs []UserModel) {
bulkRequest := ESClient.Bulk()
for _, doc := range docs {
docID := fmt.Sprintf("%d", doc.ID)
req := elastic.NewBulkIndexRequest().
Index(indexName).
Id(docID).
Doc(doc)
bulkRequest = bulkRequest.Add(req)
}
bulkResponse, err := bulkRequest.Do(ctx)
if err != nil {
log.Fatalf("Error bulk adding documents: %s", err)
}
if bulkResponse.Errors {
fmt.Println("批量添加文档时发生错误:")
for _, failedItem := range bulkResponse.Failed() {
fmt.Printf("- 文档ID: %s, 错误: %s\n", failedItem.Id, failedItem.Error)
}
} else {
fmt.Printf("批量添加 %d 个文档成功\n", len(docs))
}
}
查
go
// GetDocumentByID 根据ID获取文档
func GetDocumentByID(ctx context.Context, indexName string, docID string) {
getResult, err := ESClient.Get().
Index(indexName).
Id(docID).
Do(ctx)
if err != nil {
log.Fatalf("Error getting document: %s", err)
}
if getResult.Found {
var user UserModel
err := json.Unmarshal(getResult.Source, &user)
if err != nil {
log.Fatalf("Error unmarshaling document: %s", err)
}
fmt.Printf("根据ID查询到文档: %+v\n", user)
} else {
fmt.Printf("文档ID %s 不存在\n", docID)
}
}
// ListAllDocuments 列出所有文档
func ListAllDocuments(ctx context.Context, indexName string) {
searchResult, err := ESClient.Search().
Index(indexName).
Query(elastic.NewMatchAllQuery()).
Size(100).
Do(ctx)
if err != nil {
log.Fatalf("Error searching documents: %s", err)
}
fmt.Printf("总共找到 %d 个文档\n", searchResult.Hits.TotalHits.Value)
for _, hit := range searchResult.Hits.Hits {
var user UserModel
err := json.Unmarshal(hit.Source, &user)
if err != nil {
log.Printf("Error unmarshaling document: %s", err)
continue
}
fmt.Printf("- 文档ID: %s, 数据: %+v\n", hit.Id, user)
}
}
// SearchByExactMatch 精准匹配查询
func SearchByExactMatch(ctx context.Context, indexName string, fieldName string, value string) {
// 使用term查询进行精准匹配
query := elastic.NewTermQuery(fieldName, value)
searchResult, err := ESClient.Search().
Index(indexName).
Query(query).
Do(ctx)
if err != nil {
log.Fatalf("Error searching by exact match: %s", err)
}
fmt.Printf("精准匹配 %s=%s 找到 %d 个文档\n", fieldName, value, searchResult.Hits.TotalHits.Value)
for _, hit := range searchResult.Hits.Hits {
var user UserModel
err := json.Unmarshal(hit.Source, &user)
if err != nil {
log.Printf("Error unmarshaling document: %s", err)
continue
}
fmt.Printf("- 文档ID: %s, 数据: %+v\n", hit.Id, user)
}
}
// SearchByFuzzyMatch 模糊匹配查询
func SearchByFuzzyMatch(ctx context.Context, indexName string, fieldName string, value string) {
// 使用match查询进行模糊匹配
query := elastic.NewMatchQuery(fieldName, value)
searchResult, err := ESClient.Search().
Index(indexName).
Query(query).
Do(ctx)
if err != nil {
log.Fatalf("Error searching by fuzzy match: %s", err)
}
fmt.Printf("模糊匹配 %s='%s' 找到 %d 个文档\n", fieldName, value, searchResult.Hits.TotalHits.Value)
for _, hit := range searchResult.Hits.Hits {
var user UserModel
err := json.Unmarshal(hit.Source, &user)
if err != nil {
log.Printf("Error unmarshaling document: %s", err)
continue
}
fmt.Printf("- 文档ID: %s, 数据: %+v\n", hit.Id, user)
}
}
// SearchByMatch 分词查询 (最为 重要、常用)
func SearchByMatch(ctx context.Context, indexName string, fieldName string, value string) {
// 使用match查询进行分词匹配,会对搜索词进行分词后再搜索
query := elastic.NewMatchQuery(fieldName, value)
searchResult, err := ESClient.Search().
Index(indexName).
Query(query).
Size(10).
Do(ctx)
if err != nil {
log.Fatalf("Error searching by match: %s", err)
}
fmt.Printf("分词查询 %s='%s' 找到 %d 个文档\n", fieldName, value, searchResult.Hits.TotalHits.Value)
for _, hit := range searchResult.Hits.Hits {
var user UserModel
err := json.Unmarshal(hit.Source, &user)
if err != nil {
log.Printf("Error unmarshaling document: %s", err)
continue
}
fmt.Printf("- 文档ID: %s, 用户名: %s, 昵称: %s\n", hit.Id, user.UserName, user.NickName)
}
}
// SearchByWildcard 通配符查询
func SearchByWildcard(ctx context.Context, indexName string, fieldName string, pattern string) {
// 使用wildcard查询
query := elastic.NewWildcardQuery(fieldName, pattern)
searchResult, err := ESClient.Search().
Index(indexName).
Query(query).
Do(ctx)
if err != nil {
log.Fatalf("Error searching by wildcard: %s", err)
}
fmt.Printf("通配符查询 %s='%s' 找到 %d 个文档\n", fieldName, pattern, searchResult.Hits.TotalHits.Value)
for _, hit := range searchResult.Hits.Hits {
var user UserModel
err := json.Unmarshal(hit.Source, &user)
if err != nil {
log.Printf("Error unmarshaling document: %s", err)
continue
}
fmt.Printf("- 文档ID: %s, 数据: %+v\n", hit.Id, user)
}
}
改
go
// UpdateDocument 更新文档
func UpdateDocument(ctx context.Context, indexName string, docID string, doc any) {
// 先检查文档是否存在
getResult, err := ESClient.Get().
Index(indexName).
Id(docID).
Do(ctx)
if err != nil {
log.Fatalf("Error getting document: %s", err)
}
if !getResult.Found {
fmt.Printf("文档ID %s 不存在,无法更新\n", docID)
return
}
// 使用UpdateWithRetry更新文档
updateResult, err := ESClient.Update().
Index(indexName).
Id(docID).
Doc(doc).
Do(ctx)
if err != nil {
log.Fatalf("Error updating document: %s", err)
}
fmt.Printf("文档更新成功 - ID: %s, Result: %s\n", updateResult.Id, updateResult.Result)
}
// UpdateDocumentByScript 使用脚本更新文档
func UpdateDocumentByScript(ctx context.Context, indexName string, docID string) {
// 使用painless脚本更新文档
script := elastic.NewScriptInline(
"ctx._source.nick_name = params.new_name",
).Param("new_name", "脚本更新昵称")
updateResult, err := ESClient.Update().
Index(indexName).
Id(docID).
Script(script).
Do(ctx)
if err != nil {
log.Fatalf("Error updating document by script: %s", err)
}
fmt.Printf("使用脚本更新文档成功 - ID: %s\n", updateResult.Id)
}
// UpdateDocumentByPartialUpdate 部分更新文档
func UpdateDocumentByPartialUpdate(ctx context.Context, indexName string, docID string) {
// 只更新部分字段
partialDoc := map[string]any{
"nick_name": "部分更新昵称",
}
updateResult, err := ESClient.Update().
Index(indexName).
Id(docID).
Doc(partialDoc).
Do(ctx)
if err != nil {
log.Fatalf("Error partially updating document: %s", err)
}
fmt.Printf("部分更新文档成功 - ID: %s\n", updateResult.Id)
}
删
go
// DeleteDocumentByID 根据ID删除文档
func DeleteDocumentByID(ctx context.Context, indexName string, docID string) {
// 先检查文档是否存在
getResult, err := ESClient.Get().
Index(indexName).
Id(docID).
Do(ctx)
if err != nil {
log.Fatalf("Error getting document: %s", err)
}
if !getResult.Found {
fmt.Printf("文档ID %s 不存在,无需删除\n", docID)
return
}
deleteResult, err := ESClient.Delete().
Index(indexName).
Id(docID).
Do(ctx)
if err != nil {
log.Fatalf("Error deleting document: %s", err)
}
fmt.Printf("文档删除成功 - ID: %s, Result: %s\n", deleteResult.Id, deleteResult.Result)
}
// BulkDeleteDocuments 批量删除文档
func BulkDeleteDocuments(ctx context.Context, indexName string, docIDs []string) {
bulkRequest := ESClient.Bulk()
for _, docID := range docIDs {
req := elastic.NewBulkDeleteRequest().
Index(indexName).
Id(docID)
bulkRequest = bulkRequest.Add(req)
}
bulkResponse, err := bulkRequest.Do(ctx)
if err != nil {
log.Fatalf("Error bulk deleting documents: %s", err)
}
if bulkResponse.Errors {
fmt.Println("批量删除文档时发生错误:")
for _, failedItem := range bulkResponse.Failed() {
fmt.Printf("- 文档ID: %s, 错误: %s\n", failedItem.Id, failedItem.Error)
}
} else {
fmt.Printf("批量删除 %d 个文档成功\n", len(docIDs))
}
}
// DeleteByQuery 根据查询条件删除文档
func DeleteByQuery(ctx context.Context, indexName string, fieldName string, value string) {
query := elastic.NewTermQuery(fieldName, value)
deleteResult, err := ESClient.DeleteByQuery(indexName).
Query(query).
Do(ctx)
if err != nil {
log.Fatalf("Error deleting by query: %s", err)
}
fmt.Printf("根据查询条件删除文档成功, 删除数量: %d\n", deleteResult.Deleted)
}
嵌套字段
go
// CreateProductIndex 创建带嵌套字段的商品索引
func CreateProductIndex(ctx context.Context) {
exists, err := ESClient.IndexExists("products_index").Do(ctx)
if err != nil {
log.Fatalf("Error checking index existence: %s", err)
}
if exists {
fmt.Println("商品索引已存在")
return
}
// 创建索引并指定包含嵌套字段的mapping
createIndex, err := ESClient.CreateIndex("products_index").BodyJson(map[string]any{
"settings": map[string]any{
"number_of_shards": 1,
"number_of_replicas": 0,
},
"mappings": map[string]any{
"properties": map[string]any{
"id": map[string]any{
"type": "integer",
},
"name": map[string]any{
"type": "text",
},
"category": map[string]any{
"type": "keyword",
},
"tags": map[string]any{
"type": "keyword",
},
"details": map[string]any{
"type": "object",
"properties": map[string]any{
"brand": map[string]any{
"type": "keyword",
},
"model": map[string]any{
"type": "keyword",
},
"price": map[string]any{
"type": "float",
},
"description": map[string]any{
"type": "text",
},
},
},
},
},
}).Do(ctx)
if err != nil {
log.Fatalf("Error creating products index: %s", err)
}
if createIndex.Acknowledged {
fmt.Println("商品索引创建成功")
}
}
// AddProductDocument 添加商品文档
func AddProductDocument(ctx context.Context, indexName string, docID string, doc ProductModel) {
putResult, err := ESClient.Index().
Index(indexName).
Id(docID).
BodyJson(doc).
Do(ctx)
if err != nil {
log.Fatalf("Error adding product document: %s", err)
}
fmt.Printf("商品文档添加成功 - ID: %s\n", putResult.Id)
}
// SearchByNestedField 搜索嵌套字段
func SearchByNestedField(ctx context.Context, indexName string, nestedField string, value string) {
// 使用简单的object字段查询
query := elastic.NewTermQuery(nestedField, value)
searchResult, err := ESClient.Search().
Index(indexName).
Query(query).
Do(ctx)
if err != nil {
log.Fatalf("Error searching by nested field: %s", err)
}
fmt.Printf("嵌套字段查询 %s=%s 找到 %d 个文档\n", nestedField, value, searchResult.Hits.TotalHits.Value)
for _, hit := range searchResult.Hits.Hits {
var product ProductModel
err := json.Unmarshal(hit.Source, &product)
if err != nil {
log.Printf("Error unmarshaling document: %s", err)
continue
}
fmt.Printf("- 文档ID: %s, 名称: %s, 品牌: %s\n", hit.Id, product.Name, product.Details.Brand)
}
}
完整代码
- go.mod
mod
module esgogogo
go 1.25.0
require github.com/olivere/elastic/v7 v7.0.32
require (
github.com/josharian/intern v1.0.0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/pkg/errors v0.9.1 // indirect
)
- main.go
go
package main
import (
"context"
"crypto/tls"
"encoding/json"
"fmt"
"log"
"net/http"
"time"
"github.com/olivere/elastic/v7"
)
var ESClient *elastic.Client
var ctx = context.Background()
func init() {
EsConnect()
}
func main() {
fmt.Println("\n========== 嵌套字段搜索 ==========")
// 搜索嵌套字段
SearchByNestedField(ctx, "products_index", "details.brand", "联想")
}
func main2() {
// 执行各种ES操作案例
// 0. IK分词器测试
fmt.Println("========== IK分词器测试 ==========")
TestIKAnalyzer(ctx)
TestIKSearch(ctx)
// 1. 索引操作
fmt.Println("========== 索引操作 ==========")
CreateIndex(ctx, "users_index")
CheckIndexExists(ctx, "users_index")
DeleteIndex(ctx, "users_index")
// 重新创建索引用于后续测试
CreateIndex(ctx, "users_index")
// 2. 文档添加
fmt.Println("\n========== 文档添加 ==========")
user1 := UserModel{
ID: 1,
UserName: "zhangsan",
NickName: "张三",
CreateTime: time.Now(),
}
AddDocument(ctx, "users_index", "1", user1)
user2 := UserModel{
ID: 2,
UserName: "lisi",
NickName: "李四",
CreateTime: time.Now(),
}
AddDocument(ctx, "users_index", "2", user2)
user3 := UserModel{
ID: 3,
UserName: "wangwu",
NickName: "王五",
CreateTime: time.Now(),
}
AddDocument(ctx, "users_index", "3", user3)
// 批量添加
fmt.Println("\n========== 批量添加文档 ==========")
users := []UserModel{
{ID: 4, UserName: "zhaoliu", NickName: "赵六", CreateTime: time.Now()},
{ID: 5, UserName: "sunqi", NickName: "孙七", CreateTime: time.Now()},
{ID: 6, UserName: "zhouba", NickName: "周八", CreateTime: time.Now()},
}
BulkAddDocuments(ctx, "users_index", users)
// 3. 文档查询
fmt.Println("\n========== 文档查询 ==========")
GetDocumentByID(ctx, "users_index", "1")
ListAllDocuments(ctx, "users_index")
SearchByExactMatch(ctx, "users_index", "user_name", "zhangsan")
SearchByFuzzyMatch(ctx, "users_index", "user_name", "zhang")
SearchByWildcard(ctx, "users_index", "user_name", "*si*")
SearchByMatch(ctx, "users_index", "nick_name", "张")
// 4. 嵌套字段搜索
fmt.Println("\n========== 嵌套字段搜索 ==========")
// 先创建一个带嵌套字段的索引
CreateProductIndex(ctx)
// 添加带嵌套字段的文档
product1 := ProductModel{
ID: 101,
Name: "笔记本电脑",
Category: "电子产品",
Tags: []string{"办公", "游戏", "轻薄"},
Details: ProductDetails{
Brand: "联想",
Model: "ThinkPad",
Price: 5999,
Description: "高性能办公笔记本",
},
}
AddProductDocument(ctx, "products_index", "101", product1)
product2 := ProductModel{
ID: 102,
Name: "游戏鼠标",
Category: "电子产品",
Tags: []string{"游戏", "鼠标", "无线"},
Details: ProductDetails{
Brand: "罗技",
Model: "G502",
Price: 299,
Description: "高性能游戏鼠标",
},
}
AddProductDocument(ctx, "products_index", "102", product2)
// 搜索嵌套字段
SearchByNestedField(ctx, "products_index", "details.brand", "联想")
// 5. 文档更新
fmt.Println("\n========== 文档更新 ==========")
user := UserModel{
ID: 1,
UserName: "zhangsan_new",
NickName: "张三更新",
CreateTime: time.Now(),
}
UpdateDocument(ctx, "users_index", "1", user)
GetDocumentByID(ctx, "users_index", "1")
// 6. 文档删除
fmt.Println("\n========== 文档删除 ==========")
DeleteDocumentByID(ctx, "users_index", "6")
BulkDeleteDocuments(ctx, "users_index", []string{"4", "5"})
// 清理测试索引
DeleteIndex(ctx, "users_index")
DeleteIndex(ctx, "products_index")
}
func EsConnect() {
/*
ES连接问题分析与解决方案
========================
【之前连接不上的原因】
--------------------
1. 协议不匹配
- ES配置:xpack.security.http.ssl.enabled: true(要求HTTPS)
- 代码配置:使用 http:// 连接
- 结果:ES拒绝HTTP连接
2. SSL证书验证失败
- ES使用自签名证书(certs/http.p12)
- Go的HTTP客户端默认验证SSL证书有效性
- 结果:证书验证失败,连接被拒绝
【现在能连接上的原因】
--------------------
1. 使用正确的协议
- 代码改用 https:// 连接,与ES配置匹配
- ES接受HTTPS连接请求
2. 跳过SSL证书验证
- 设置 InsecureSkipVerify: true
- 跳过ES自签名证书的验证
- 连接不被证书问题阻止
【关键配置总结】
----------------
ES配置:
- xpack.security.enabled: true(启用安全认证)
- xpack.security.http.ssl.enabled: true(启用HTTPS)
- xpack.security.transport.ssl.enabled: true(启用SSL加密)
代码配置:
- URL: https://118.195.189.68:9200(使用HTTPS)
- SSL验证: InsecureSkipVerify: true(跳过证书验证)
- SetSniff: false(禁用节点发现)
*/
config := struct {
name string
username string
password string
useHTTP bool
}{"带BasicAuth连接", "elastic", "abcdefghigklmn", true}
var client *elastic.Client
var err error
// 设置跳过SSL证书验证的HTTP客户端
// 原因:ES使用自签名证书,需要跳过验证才能连接
httpClient := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true, // 跳过证书验证
},
},
}
if config.useHTTP {
client, err = elastic.NewClient(
elastic.SetURL("https://118.195.189.68:9200"), // 使用HTTPS协议,适配ES的SSL配置
elastic.SetSniff(false), // 禁用节点发现,防止自动连接其他节点
elastic.SetBasicAuth(config.username, config.password), // 设置用户名密码认证
elastic.SetHttpClient(httpClient), // 使用自定义HTTP客户端,跳过SSL证书验证
)
if err != nil {
log.Fatalf("Error creating Elasticsearch client: %s", err)
panic("ESClient 链接失败")
}
}
// 测试基本连接
ctx := context.Background()
_, _, err = client.Ping("https://118.195.189.68:9200").Do(ctx)
if err != nil {
fmt.Printf(" ✗ Ping失败: %s\n", err)
} else {
fmt.Printf(" ✓ Ping成功\n")
}
// 测试获取集群信息
info, err := client.ClusterHealth().Do(ctx)
if err != nil {
fmt.Printf(" ✗ 获取集群信息失败: %s\n", err)
} else {
fmt.Printf(" ✓ 集群信息获取成功 (状态: %s)\n", info.Status)
}
// 测试获取索引列表
indices, err := client.CatIndices().Do(ctx)
if err != nil {
fmt.Printf(" ✗ 获取索引列表失败: %s\n", err)
} else {
fmt.Printf(" ✓ 获取索引列表成功 (共%d个索引)\n", len(indices))
}
ESClient = client
}
// ========== IK分词器测试 ==========
// TestIKAnalyzer 测试IK分词器 是否正常工作 【✅】
func TestIKAnalyzer(ctx context.Context) {
// 检查是否已存在测试索引
exists, err := ESClient.IndexExists("ik_test_index").Do(ctx)
if err != nil {
fmt.Printf("检查索引失败: %s\n", err)
return
}
// 如果已存在,先删除
if exists {
ESClient.DeleteIndex("ik_test_index").Do(ctx)
}
// 创建使用IK分词器的测试索引
createIndex, err := ESClient.CreateIndex("ik_test_index").BodyJson(map[string]any{
"settings": map[string]any{
"number_of_shards": 1,
"number_of_replicas": 0,
"analysis": map[string]any{
"analyzer": map[string]any{
"ik_max_word_analyzer": map[string]any{
"type": "custom",
"tokenizer": "ik_max_word",
},
"ik_smart_analyzer": map[string]any{
"type": "custom",
"tokenizer": "ik_smart",
},
},
},
},
"mappings": map[string]any{
"properties": map[string]any{
"id": map[string]any{
"type": "integer",
},
"text": map[string]any{
"type": "text",
"analyzer": "ik_max_word",
"search_analyzer": "ik_smart",
"fields": map[string]any{
"keyword": map[string]any{
"type": "keyword",
},
},
},
},
},
}).Do(ctx)
if err != nil {
fmt.Printf("IK分词器测试失败,创建索引失败: %s\n", err)
fmt.Println("提示: 请先安装IK分词器插件")
fmt.Println("安装命令(在ES安装目录执行): ./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.17.0/elasticsearch-analysis-ik-7.17.0.zip")
fmt.Println("安装后需要重启Elasticsearch")
return
}
if !createIndex.Acknowledged {
fmt.Println("IK分词器测试失败,索引创建未被确认")
return
}
fmt.Println("✓ IK分词器索引创建成功")
// 添加测试文档
testTexts := []struct {
ID int
Text string
}{
{1, "中华人民共和国国歌"},
{2, "北京天安门广场"},
{3, "人工智能与大数据技术"},
{4, "Elasticsearch搜索引擎"},
{5, "中文分词技术"},
}
fmt.Println("\n添加测试文档:")
for _, item := range testTexts {
putResult, err := ESClient.Index().
Index("ik_test_index").
Id(fmt.Sprintf("%d", item.ID)).
BodyJson(map[string]any{
"id": item.ID,
"text": item.Text,
}).
Do(ctx)
if err != nil {
fmt.Printf(" 添加文档失败: %s\n", err)
continue
}
fmt.Printf(" ✓ 添加成功: ID=%s, 文本=%s\n", putResult.Id, item.Text)
}
// 等待索引刷新
ESClient.Refresh("ik_test_index").Do(ctx)
// 测试分词效果 - 搜索测试
fmt.Println("\n========== IK分词搜索测试 ==========")
keywords := []string{
"中华",
"人民",
"共和国",
"国歌",
"分词",
"人工智能",
"搜索",
"数据",
"天安",
"门",
}
for _, keyword := range keywords {
query := elastic.NewMatchQuery("text", keyword)
searchResult, err := ESClient.Search().
Index("ik_test_index").
Query(query).
Size(10).
Do(ctx)
if err != nil {
fmt.Printf("搜索关键词 '%s' 失败: %s\n", keyword, err)
continue
}
if searchResult.Hits.TotalHits.Value > 0 {
fmt.Printf("\n搜索关键词: '%s', 找到 %d 个匹配文档\n", keyword, searchResult.Hits.TotalHits.Value)
for _, hit := range searchResult.Hits.Hits {
var doc struct {
ID int `json:"id"`
Text string `json:"text"`
}
json.Unmarshal(hit.Source, &doc)
fmt.Printf(" - 文档%d: %s\n", doc.ID, doc.Text)
}
} else {
fmt.Printf("搜索关键词: '%s', 未找到匹配文档\n", keyword)
}
}
// 测试短语匹配
fmt.Println("\n========== 短语匹配测试 ==========")
phrases := []string{
"中华",
"人民共和",
"共和国",
"搜索引擎",
"分词技术",
"天安",
}
for _, phrase := range phrases {
query := elastic.NewMatchPhraseQuery("text", phrase)
searchResult, err := ESClient.Search().
Index("ik_test_index").
Query(query).
Size(10).
Do(ctx)
if err != nil {
fmt.Printf("短语搜索 '%s' 失败: %s\n", phrase, err)
continue
}
if searchResult.Hits.TotalHits.Value > 0 {
fmt.Printf("\n短语匹配: '%s', 找到 %d 个匹配文档\n", phrase, searchResult.Hits.TotalHits.Value)
for _, hit := range searchResult.Hits.Hits {
var doc struct {
ID int `json:"id"`
Text string `json:"text"`
}
json.Unmarshal(hit.Source, &doc)
fmt.Printf(" - 文档%d: %s\n", doc.ID, doc.Text)
}
} else {
fmt.Printf("短语匹配: '%s', 未找到匹配文档\n", phrase)
}
}
// IK分词器验证总结
fmt.Println("\n========== IK分词器验证总结 ==========")
fmt.Println("✓ 如果能搜索到 '中华'、'人民'、'共和国',说明IK分词器正常工作")
fmt.Println("✓ 如果能短语匹配到 '共和国',说明分词正确")
fmt.Println("✗ 如果无法搜索到 '中华',说明IK分词器可能未安装或配置有问题")
fmt.Println("提示:可以使用 Kibana Dev Tools 或 curl 直接测试分词效果:")
fmt.Println(`POST /_analyze`)
fmt.Println(`{`)
fmt.Println(` "analyzer": "ik_max_word",`)
fmt.Println(` "text": "中华人民共和国国歌"`)
fmt.Println(`}`)
// 清理测试索引
DeleteIndex(ctx, "ik_test_index")
}
// CreateArticleIndex 创建使用IK分词器的文章索引
func CreateArticleIndex(ctx context.Context) {
exists, err := ESClient.IndexExists("articles_index").Do(ctx)
if err != nil {
log.Fatalf("Error checking index existence: %s", err)
}
if exists {
fmt.Println("文章索引已存在")
return
}
// 创建索引并指定IK分词器的mapping
createIndex, err := ESClient.CreateIndex("articles_index").BodyJson(map[string]any{
"settings": map[string]any{
"number_of_shards": 1,
"number_of_replicas": 0,
"analysis": map[string]any{
"analyzer": map[string]any{
"ik_pinyin_analyzer": map[string]any{
"type": "custom",
"tokenizer": "ik_max_word",
},
},
},
},
"mappings": map[string]any{
"properties": map[string]any{
"id": map[string]any{
"type": "integer",
},
"title": map[string]any{
"type": "text",
"analyzer": "ik_max_word",
"search_analyzer": "ik_smart",
"fields": map[string]any{
"keyword": map[string]any{
"type": "keyword",
},
},
},
"content": map[string]any{
"type": "text",
"analyzer": "ik_max_word",
"search_analyzer": "ik_smart",
},
"author": map[string]any{
"type": "text",
"analyzer": "ik_max_word",
},
"tags": map[string]any{
"type": "keyword",
},
"publish_time": map[string]any{
"type": "date",
},
},
},
}).Do(ctx)
if err != nil {
log.Fatalf("Error creating articles index: %s", err)
}
if createIndex.Acknowledged {
fmt.Println("文章索引创建成功(使用IK分词器)")
}
}
// TestIKSearch 测试IK分词器搜索
func TestIKSearch(ctx context.Context) {
// 创建使用IK分词器的索引
CreateArticleIndex(ctx)
// 添加一些中文文章数据
articles := []ArticleModel{
{
ID: 1,
Title: "Elasticsearch简介",
Content: "Elasticsearch是一个基于Lucene的搜索引擎,它提供了一个分布式多用户能力的全文搜索引擎。",
Author: "张三",
Tags: []string{"搜索引擎", "数据库", "大数据"},
PublishTime: time.Now(),
},
{
ID: 2,
Title: "中文分词技术详解",
Content: "中文分词是中文信息处理的基础,常用的分词器有IK分词器、结巴分词器等。",
Author: "李四",
Tags: []string{"NLP", "分词", "中文"},
PublishTime: time.Now(),
},
{
ID: 3,
Title: "大数据技术在企业中的应用",
Content: "大数据技术包括数据采集、存储、处理、分析等环节,为企业提供决策支持。",
Author: "王五",
Tags: []string{"大数据", "企业应用", "数据分析"},
PublishTime: time.Now(),
},
{
ID: 4,
Title: "人工智能与机器学习",
Content: "人工智能是计算机科学的一个分支,机器学习是人工智能的核心技术之一。",
Author: "赵六",
Tags: []string{"AI", "机器学习", "人工智能"},
PublishTime: time.Now(),
},
}
fmt.Println("\n添加中文测试数据...")
for _, article := range articles {
AddArticleDocument(ctx, "articles_index", fmt.Sprintf("%d", article.ID), article)
}
// 测试中文搜索
fmt.Println("\n========== 中文搜索测试 ==========")
// 1. 精准短语搜索
SearchArticle(ctx, "articles_index", "分词")
// 2. 模糊搜索
SearchArticle(ctx, "articles_index", "数据")
// 3. 复合搜索
SearchArticle(ctx, "articles_index", "搜索引擎")
// 4. 短语匹配搜索
SearchArticleByPhrase(ctx, "articles_index", "中文分词")
// 清理测试索引
DeleteIndex(ctx, "articles_index")
}
// AddArticleDocument 添加文章文档
func AddArticleDocument(ctx context.Context, indexName string, docID string, doc ArticleModel) {
putResult, err := ESClient.Index().
Index(indexName).
Id(docID).
BodyJson(doc).
Do(ctx)
if err != nil {
log.Printf("Error adding article document: %s", err)
return
}
fmt.Printf("文章文档添加成功 - ID: %s, 标题: %s\n", putResult.Id, doc.Title)
}
// SearchArticle 文章搜索
func SearchArticle(ctx context.Context, indexName string, keyword string) {
// 使用match查询进行中文分词搜索
query := elastic.NewMultiMatchQuery(keyword, "title", "content").
Type("best_fields")
searchResult, err := ESClient.Search().
Index(indexName).
Query(query).
Highlight(elastic.NewHighlight().Field("title").Field("content")).
Do(ctx)
if err != nil {
log.Printf("Error searching articles: %s", err)
return
}
fmt.Printf("\n搜索关键词: %s, 找到 %d 个文档\n", keyword, searchResult.Hits.TotalHits.Value)
for _, hit := range searchResult.Hits.Hits {
var article ArticleModel
err := json.Unmarshal(hit.Source, &article)
if err != nil {
log.Printf("Error unmarshaling document: %s", err)
continue
}
fmt.Printf("- 标题: %s, 作者: %s\n", article.Title, article.Author)
fmt.Printf(" 内容摘要: %s...\n", truncateString(article.Content, 50))
// 显示高亮
if hit.Highlight != nil {
if highlight, ok := hit.Highlight["title"]; ok && len(highlight) > 0 {
fmt.Printf(" 标题高亮: %s\n", highlight[0])
}
if highlight, ok := hit.Highlight["content"]; ok && len(highlight) > 0 {
fmt.Printf(" 内容高亮: %s\n", highlight[0])
}
}
}
}
// SearchArticleByPhrase 短语匹配搜索
func SearchArticleByPhrase(ctx context.Context, indexName string, phrase string) {
// 使用match_phrase查询进行短语匹配
query := elastic.NewMatchPhraseQuery("content", phrase)
searchResult, err := ESClient.Search().
Index(indexName).
Query(query).
Do(ctx)
if err != nil {
log.Printf("Error searching articles by phrase: %s", err)
return
}
fmt.Printf("\n短语匹配: \"%s\", 找到 %d 个文档\n", phrase, searchResult.Hits.TotalHits.Value)
for _, hit := range searchResult.Hits.Hits {
var article ArticleModel
err := json.Unmarshal(hit.Source, &article)
if err != nil {
log.Printf("Error unmarshaling document: %s", err)
continue
}
fmt.Printf("- 标题: %s, 作者: %s\n", article.Title, article.Author)
}
}
// truncateString 截断字符串
func truncateString(s string, maxLen int) string {
if len(s) <= maxLen {
return s
}
return s[:maxLen] + "..."
}
// ========== 索引操作 ==========
// CreateIndex 创建索引
func CreateIndex(ctx context.Context, indexName string) {
exists, err := ESClient.IndexExists(indexName).Do(ctx)
if err != nil {
log.Fatalf("Error checking index existence: %s", err)
}
if exists {
fmt.Printf("索引 %s 已存在\n", indexName)
return
}
// 创建索引并指定mapping
createIndex, err := ESClient.CreateIndex(indexName).BodyJson(map[string]any{
"settings": map[string]any{
"number_of_shards": 1,
"number_of_replicas": 0,
"analysis": map[string]any{
"analyzer": map[string]any{
"ik_pinyin_analyzer": map[string]any{
"type": "custom",
"tokenizer": "ik_max_word",
},
},
},
},
"mappings": map[string]any{
"properties": map[string]any{
"id": map[string]any{
"type": "integer",
},
"user_name": map[string]any{
"type": "keyword",
},
"nick_name": map[string]any{
"type": "text",
"analyzer": "ik_max_word",
"search_analyzer": "ik_smart",
"fields": map[string]any{
"keyword": map[string]any{
"type": "keyword",
},
},
},
"create_time": map[string]any{
"type": "date",
},
},
},
}).Do(ctx)
if err != nil {
log.Fatalf("Error creating index: %s", err)
}
if !createIndex.Acknowledged {
fmt.Printf("索引 %s 创建未被确认\n", indexName)
} else {
fmt.Printf("索引 %s 创建成功\n", indexName)
}
}
// CheckIndexExists 检查索引是否存在
func CheckIndexExists(ctx context.Context, indexName string) {
exists, err := ESClient.IndexExists(indexName).Do(ctx)
if err != nil {
log.Fatalf("Error checking index existence: %s", err)
}
if exists {
fmt.Printf("索引 %s 存在\n", indexName)
} else {
fmt.Printf("索引 %s 不存在\n", indexName)
}
}
// DeleteIndex 删除索引
func DeleteIndex(ctx context.Context, indexName string) {
exists, err := ESClient.IndexExists(indexName).Do(ctx)
if err != nil {
log.Fatalf("Error checking index existence: %s", err)
}
if !exists {
fmt.Printf("索引 %s 不存在,无需删除\n", indexName)
return
}
deleteIndex, err := ESClient.DeleteIndex(indexName).Do(ctx)
if err != nil {
log.Fatalf("Error deleting index: %s", err)
}
if !deleteIndex.Acknowledged {
fmt.Printf("索引 %s 删除未被确认\n", indexName)
} else {
fmt.Printf("索引 %s 删除成功\n", indexName)
}
}
// ========== 文档添加 ==========
// AddDocument 添加单个文档
func AddDocument(ctx context.Context, indexName string, docID string, doc any) {
putResult, err := ESClient.Index().
Index(indexName).
Id(docID). // 指定文档ID,如果不指定,ES会自动生成一个唯一ID
BodyJson(doc).
Do(ctx)
if err != nil {
log.Fatalf("Error adding document: %s", err)
}
fmt.Printf("文档添加成功 - ID: %s, Index: %s, Type: %s, Result: %s\n",
putResult.Id, putResult.Index, putResult.Type, putResult.Result)
}
// BulkAddDocuments 批量添加文档
func BulkAddDocuments(ctx context.Context, indexName string, docs []UserModel) {
bulkRequest := ESClient.Bulk()
for _, doc := range docs {
docID := fmt.Sprintf("%d", doc.ID)
req := elastic.NewBulkIndexRequest().
Index(indexName).
Id(docID).
Doc(doc)
bulkRequest = bulkRequest.Add(req)
}
bulkResponse, err := bulkRequest.Do(ctx)
if err != nil {
log.Fatalf("Error bulk adding documents: %s", err)
}
if bulkResponse.Errors {
fmt.Println("批量添加文档时发生错误:")
for _, failedItem := range bulkResponse.Failed() {
fmt.Printf("- 文档ID: %s, 错误: %s\n", failedItem.Id, failedItem.Error)
}
} else {
fmt.Printf("批量添加 %d 个文档成功\n", len(docs))
}
}
// ========== 文档查询 ==========
// GetDocumentByID 根据ID获取文档
func GetDocumentByID(ctx context.Context, indexName string, docID string) {
getResult, err := ESClient.Get().
Index(indexName).
Id(docID).
Do(ctx)
if err != nil {
log.Fatalf("Error getting document: %s", err)
}
if getResult.Found {
var user UserModel
err := json.Unmarshal(getResult.Source, &user)
if err != nil {
log.Fatalf("Error unmarshaling document: %s", err)
}
fmt.Printf("根据ID查询到文档: %+v\n", user)
} else {
fmt.Printf("文档ID %s 不存在\n", docID)
}
}
// ListAllDocuments 列出所有文档
func ListAllDocuments(ctx context.Context, indexName string) {
searchResult, err := ESClient.Search().
Index(indexName).
Query(elastic.NewMatchAllQuery()).
Size(100).
Do(ctx)
if err != nil {
log.Fatalf("Error searching documents: %s", err)
}
fmt.Printf("总共找到 %d 个文档\n", searchResult.Hits.TotalHits.Value)
for _, hit := range searchResult.Hits.Hits {
var user UserModel
err := json.Unmarshal(hit.Source, &user)
if err != nil {
log.Printf("Error unmarshaling document: %s", err)
continue
}
fmt.Printf("- 文档ID: %s, 数据: %+v\n", hit.Id, user)
}
}
// SearchByExactMatch 精准匹配查询
func SearchByExactMatch(ctx context.Context, indexName string, fieldName string, value string) {
// 使用term查询进行精准匹配
query := elastic.NewTermQuery(fieldName, value)
searchResult, err := ESClient.Search().
Index(indexName).
Query(query).
Do(ctx)
if err != nil {
log.Fatalf("Error searching by exact match: %s", err)
}
fmt.Printf("精准匹配 %s=%s 找到 %d 个文档\n", fieldName, value, searchResult.Hits.TotalHits.Value)
for _, hit := range searchResult.Hits.Hits {
var user UserModel
err := json.Unmarshal(hit.Source, &user)
if err != nil {
log.Printf("Error unmarshaling document: %s", err)
continue
}
fmt.Printf("- 文档ID: %s, 数据: %+v\n", hit.Id, user)
}
}
// SearchByFuzzyMatch 模糊匹配查询
func SearchByFuzzyMatch(ctx context.Context, indexName string, fieldName string, value string) {
// 使用match查询进行模糊匹配
query := elastic.NewMatchQuery(fieldName, value)
searchResult, err := ESClient.Search().
Index(indexName).
Query(query).
Do(ctx)
if err != nil {
log.Fatalf("Error searching by fuzzy match: %s", err)
}
fmt.Printf("模糊匹配 %s='%s' 找到 %d 个文档\n", fieldName, value, searchResult.Hits.TotalHits.Value)
for _, hit := range searchResult.Hits.Hits {
var user UserModel
err := json.Unmarshal(hit.Source, &user)
if err != nil {
log.Printf("Error unmarshaling document: %s", err)
continue
}
fmt.Printf("- 文档ID: %s, 数据: %+v\n", hit.Id, user)
}
}
// SearchByMatch 分词查询 (最为 重要、常用)
func SearchByMatch(ctx context.Context, indexName string, fieldName string, value string) {
// 使用match查询进行分词匹配,会对搜索词进行分词后再搜索
query := elastic.NewMatchQuery(fieldName, value)
searchResult, err := ESClient.Search().
Index(indexName).
Query(query).
Size(10).
Do(ctx)
if err != nil {
log.Fatalf("Error searching by match: %s", err)
}
fmt.Printf("分词查询 %s='%s' 找到 %d 个文档\n", fieldName, value, searchResult.Hits.TotalHits.Value)
for _, hit := range searchResult.Hits.Hits {
var user UserModel
err := json.Unmarshal(hit.Source, &user)
if err != nil {
log.Printf("Error unmarshaling document: %s", err)
continue
}
fmt.Printf("- 文档ID: %s, 用户名: %s, 昵称: %s\n", hit.Id, user.UserName, user.NickName)
}
}
// SearchByWildcard 通配符查询
func SearchByWildcard(ctx context.Context, indexName string, fieldName string, pattern string) {
// 使用wildcard查询
query := elastic.NewWildcardQuery(fieldName, pattern)
searchResult, err := ESClient.Search().
Index(indexName).
Query(query).
Do(ctx)
if err != nil {
log.Fatalf("Error searching by wildcard: %s", err)
}
fmt.Printf("通配符查询 %s='%s' 找到 %d 个文档\n", fieldName, pattern, searchResult.Hits.TotalHits.Value)
for _, hit := range searchResult.Hits.Hits {
var user UserModel
err := json.Unmarshal(hit.Source, &user)
if err != nil {
log.Printf("Error unmarshaling document: %s", err)
continue
}
fmt.Printf("- 文档ID: %s, 数据: %+v\n", hit.Id, user)
}
}
// ========== 嵌套字段搜索 ==========
// CreateProductIndex 创建带嵌套字段的商品索引
func CreateProductIndex(ctx context.Context) {
exists, err := ESClient.IndexExists("products_index").Do(ctx)
if err != nil {
log.Fatalf("Error checking index existence: %s", err)
}
if exists {
fmt.Println("商品索引已存在")
return
}
// 创建索引并指定包含嵌套字段的mapping
createIndex, err := ESClient.CreateIndex("products_index").BodyJson(map[string]any{
"settings": map[string]any{
"number_of_shards": 1,
"number_of_replicas": 0,
},
"mappings": map[string]any{
"properties": map[string]any{
"id": map[string]any{
"type": "integer",
},
"name": map[string]any{
"type": "text",
},
"category": map[string]any{
"type": "keyword",
},
"tags": map[string]any{
"type": "keyword",
},
"details": map[string]any{
"type": "object",
"properties": map[string]any{
"brand": map[string]any{
"type": "keyword",
},
"model": map[string]any{
"type": "keyword",
},
"price": map[string]any{
"type": "float",
},
"description": map[string]any{
"type": "text",
},
},
},
},
},
}).Do(ctx)
if err != nil {
log.Fatalf("Error creating products index: %s", err)
}
if createIndex.Acknowledged {
fmt.Println("商品索引创建成功")
}
}
// AddProductDocument 添加商品文档
func AddProductDocument(ctx context.Context, indexName string, docID string, doc ProductModel) {
putResult, err := ESClient.Index().
Index(indexName).
Id(docID).
BodyJson(doc).
Do(ctx)
if err != nil {
log.Fatalf("Error adding product document: %s", err)
}
fmt.Printf("商品文档添加成功 - ID: %s\n", putResult.Id)
}
// SearchByNestedField 搜索嵌套字段
func SearchByNestedField(ctx context.Context, indexName string, nestedField string, value string) {
// 使用简单的object字段查询
query := elastic.NewTermQuery(nestedField, value)
searchResult, err := ESClient.Search().
Index(indexName).
Query(query).
Do(ctx)
if err != nil {
log.Fatalf("Error searching by nested field: %s", err)
}
fmt.Printf("嵌套字段查询 %s=%s 找到 %d 个文档\n", nestedField, value, searchResult.Hits.TotalHits.Value)
for _, hit := range searchResult.Hits.Hits {
var product ProductModel
err := json.Unmarshal(hit.Source, &product)
if err != nil {
log.Printf("Error unmarshaling document: %s", err)
continue
}
fmt.Printf("- 文档ID: %s, 名称: %s, 品牌: %s\n", hit.Id, product.Name, product.Details.Brand)
}
}
// ========== 文档更新 ==========
// UpdateDocument 更新文档
func UpdateDocument(ctx context.Context, indexName string, docID string, doc any) {
// 先检查文档是否存在
getResult, err := ESClient.Get().
Index(indexName).
Id(docID).
Do(ctx)
if err != nil {
log.Fatalf("Error getting document: %s", err)
}
if !getResult.Found {
fmt.Printf("文档ID %s 不存在,无法更新\n", docID)
return
}
// 使用UpdateWithRetry更新文档
updateResult, err := ESClient.Update().
Index(indexName).
Id(docID).
Doc(doc).
Do(ctx)
if err != nil {
log.Fatalf("Error updating document: %s", err)
}
fmt.Printf("文档更新成功 - ID: %s, Result: %s\n", updateResult.Id, updateResult.Result)
}
// UpdateDocumentByScript 使用脚本更新文档
func UpdateDocumentByScript(ctx context.Context, indexName string, docID string) {
// 使用painless脚本更新文档
script := elastic.NewScriptInline(
"ctx._source.nick_name = params.new_name",
).Param("new_name", "脚本更新昵称")
updateResult, err := ESClient.Update().
Index(indexName).
Id(docID).
Script(script).
Do(ctx)
if err != nil {
log.Fatalf("Error updating document by script: %s", err)
}
fmt.Printf("使用脚本更新文档成功 - ID: %s\n", updateResult.Id)
}
// UpdateDocumentByPartialUpdate 部分更新文档
func UpdateDocumentByPartialUpdate(ctx context.Context, indexName string, docID string) {
// 只更新部分字段
partialDoc := map[string]any{
"nick_name": "部分更新昵称",
}
updateResult, err := ESClient.Update().
Index(indexName).
Id(docID).
Doc(partialDoc).
Do(ctx)
if err != nil {
log.Fatalf("Error partially updating document: %s", err)
}
fmt.Printf("部分更新文档成功 - ID: %s\n", updateResult.Id)
}
// ========== 文档删除 ==========
// DeleteDocumentByID 根据ID删除文档
func DeleteDocumentByID(ctx context.Context, indexName string, docID string) {
// 先检查文档是否存在
getResult, err := ESClient.Get().
Index(indexName).
Id(docID).
Do(ctx)
if err != nil {
log.Fatalf("Error getting document: %s", err)
}
if !getResult.Found {
fmt.Printf("文档ID %s 不存在,无需删除\n", docID)
return
}
deleteResult, err := ESClient.Delete().
Index(indexName).
Id(docID).
Do(ctx)
if err != nil {
log.Fatalf("Error deleting document: %s", err)
}
fmt.Printf("文档删除成功 - ID: %s, Result: %s\n", deleteResult.Id, deleteResult.Result)
}
// BulkDeleteDocuments 批量删除文档
func BulkDeleteDocuments(ctx context.Context, indexName string, docIDs []string) {
bulkRequest := ESClient.Bulk()
for _, docID := range docIDs {
req := elastic.NewBulkDeleteRequest().
Index(indexName).
Id(docID)
bulkRequest = bulkRequest.Add(req)
}
bulkResponse, err := bulkRequest.Do(ctx)
if err != nil {
log.Fatalf("Error bulk deleting documents: %s", err)
}
if bulkResponse.Errors {
fmt.Println("批量删除文档时发生错误:")
for _, failedItem := range bulkResponse.Failed() {
fmt.Printf("- 文档ID: %s, 错误: %s\n", failedItem.Id, failedItem.Error)
}
} else {
fmt.Printf("批量删除 %d 个文档成功\n", len(docIDs))
}
}
// DeleteByQuery 根据查询条件删除文档
func DeleteByQuery(ctx context.Context, indexName string, fieldName string, value string) {
query := elastic.NewTermQuery(fieldName, value)
deleteResult, err := ESClient.DeleteByQuery(indexName).
Query(query).
Do(ctx)
if err != nil {
log.Fatalf("Error deleting by query: %s", err)
}
fmt.Printf("根据查询条件删除文档成功, 删除数量: %d\n", deleteResult.Deleted)
}
// ========== 数据结构 ==========
type ArticleModel struct {
ID uint `json:"id"`
Title string `json:"title"`
Content string `json:"content"`
Author string `json:"author"`
Tags []string `json:"tags"`
PublishTime time.Time `json:"publish_time"`
}
type UserModel struct {
ID uint `json:"id"`
UserName string `json:"user_name"`
NickName string `json:"nick_name"`
CreateTime time.Time `json:"create_time"`
}
type ProductModel struct {
ID uint `json:"id"`
Name string `json:"name"`
Category string `json:"category"`
Tags []string `json:"tags"`
Details ProductDetails `json:"details"`
}
type ProductDetails struct {
Brand string `json:"brand"`
Model string `json:"model"`
Price float64 `json:"price"`
Description string `json:"description"`
}