Go 牵手 ES

📚 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"`
}
相关推荐
不像程序员的程序媛3 小时前
es查询是否存在某个字段
java·前端·elasticsearch
Lufeidata3 小时前
go语言学习记录-入门阶段2
学习·microsoft·golang
Elastic 中国社区官方博客3 小时前
使用 OpenTelemetry 和 Elastic 的 ML 和 AI Ops 可观测性
大数据·人工智能·elasticsearch·搜索引擎·全文检索
ywf121515 小时前
Go基础之环境搭建
开发语言·后端·golang
尽兴-18 小时前
Elasticsearch 性能调优指南:写入、检索、聚合与缓存全链路优化
大数据·elasticsearch·缓存·性能优化·es 读写原理
deep_drink21 小时前
1.2、Python 与编程基础:文件处理与常用库
开发语言·python·elasticsearch·llm
好家伙VCC21 小时前
**CQRS模式实战:用Go语言构建高并发读写分离架构**在现代分布式系统中,随着业务复杂度的提升和用户量的增长,传统的单数据库模型逐
java·数据库·python·架构·golang
切糕师学AI1 天前
Elasticsearch 深度解析:从核心原理到开发者实战
大数据·elasticsearch·搜索引擎·分布式搜索分析引擎