1. 準備工作
go get github.com/elastic/go-elasticsearch/v9
小貼士
- 如果你的集群啟用了安全特性,需要在
elasticsearch.Config
中配置Username/Password
或APIKey
。- Typed Client 通過
NewTypedClient
創建,內部復用*http.Client
,建議全局單例化,避免頻繁建立連接。
cfg := elasticsearch.Config{Addresses: []string{"https://es.local:9200"},Username: "elastic",Password: "mypassword",// APIKey: "…", 二選一
}
es, _ := elasticsearch.NewTypedClient(cfg)
2. 創建索引并映射字段
import ("context""github.com/elastic/go-elasticsearch/v9/typedapi/indices/create""github.com/elastic/go-elasticsearch/v9/typedapi/types"
)_, err := es.Indices.Create("test-index").Request(&create.Request{Mappings: &types.TypeMapping{Properties: map[string]types.Property{"price": types.NewIntegerNumberProperty(), // 自動設置 "type":"integer"},},}).Do(context.Background())
if err != nil { /* 處理異常 */ }
要點
關鍵點 | 說明 |
---|---|
types.NewIntegerNumberProperty() | Builder 會自動注入 type=integer ,避免手寫字符串 |
.Request() | 接收強類型結構體,寫錯字段編譯期即報錯 |
.Do(ctx) | 統一執行入口,返回 *Response 與 error |
3. 寫入文檔
3.1 直接傳遞結構體(最常用)
doc := struct {Id int `json:"id"`Name string `json:"name"`Price int `json:"price"`
}{1, "Foo", 10}if _, err := es.Index("test-index").Request(doc).Do(context.Background()); err != nil {log.Fatal(err)
}
3.2 傳遞已序列化的 JSON
payload := []byte(`{"id":1,"name":"Foo","price":10}`)
_, err := es.Index("test-index").Raw(payload).Do(context.Background())
適用于手動拼裝或性能敏感場景,跳過
encoding/json
二次編碼。
4. 讀取文檔
resp, err := es.Get("test-index", "1").Do(context.Background())
if err != nil { … }
fmt.Printf("Source=%s\n", resp.Source_)
5. 判斷文檔是否存在
ok, err := es.Exists("test-index", "1").IsSuccess(context.Background())
switch {
case err != nil:// 請求失敗
case ok:fmt.Println("文檔存在")
default:fmt.Println("文檔不存在")
}
IsSuccess
通過 HTTP 狀態碼 判斷,內部已處理 404
與重定向。
6. 搜索
6.1 使用強類型 Request 構建 DSL
import "github.com/elastic/go-elasticsearch/v9/typedapi/search"resp, err := es.Search().Index("test-index").Request(&search.Request{Query: &types.Query{Match: map[string]types.MatchQuery{"name": {Query: "Foo"},},},}).Do(context.Background())
生成的 DSL
{"query": { "match": { "name": { "query": "Foo" } } }
}
6.2 快速解析命中
for _, hit := range resp.Hits.Hits {var d struct {Name string `json:"name"`Price int `json:"price"`}if err := json.Unmarshal(hit.Source_, &d); err != nil { … }fmt.Printf("%s => %d\n", d.Name, d.Price)
}
7. 聚合 —— 求價格總和
total, err := es.Search().Index("test-index").Request(&search.Request{Size: some.Int(0), // 只要聚合結果,不要文檔Aggregations: map[string]types.Aggregations{"total_prices": {Sum: &types.SumAggregation{Field: some.String("price")},},},}).Do(context.Background())
if err != nil { … }sum := total.Aggregations.Sum("total_prices").Value
fmt.Printf("價格總和:%.0f\n", *sum)
some.Int(0)
與some.String()
是 Typed API 的 可空包裝器,避免*int
指針聲明的繁瑣。- 結果通過
Aggregations.Sum("name")
類型安全地取出,無需手寫 map 斷言。
8. 進階實踐 & 性能建議
- 單例客戶端:在
main()
或 DI 容器里初始化一次,復用連接池。 - Context 超時:在高并發場景用
context.WithTimeout
控制慢查詢。 - Bulk 批量索引:大規模寫入請使用
es.Bulk()
+ 并行 worker。 - 顯式
Refresh
:測試場景可在寫入后.Refresh("wait_for")
,生產環境由索引刷新策略決定。 - Typed vs. Raw:Typed API 更安全、可讀;Raw/Low-level Client 更靈活。可按模塊混用。
9. 小結
- Typed Request + Builder 讓映射、DSL、聚合都擁有 IDE 自動補全和編譯期校驗。
- 常見場景(建索引 / CRUD / Search / Aggs)幾乎只需一兩行鏈式調用即可搞定。
- 掌握 some 包 的可空包裝器、
IsSuccess
以及Do(ctx)
的范式,能寫出既易讀又健壯的 Go-ES 代碼。
一步到位搭建起 Go × Elasticsearch 的類型安全數據層,讓你的業務邏輯遠離魔法字符串,盡享編譯器的守護!