如何基于go-elasticsearch实现聊天应用的消息与文件高级搜索?
基于go-elasticsearch实现聊天消息与文件的高级搜索方案
核心思路
高级搜索本质是多条件组合查询,用Elasticsearch的bool查询整合所有筛选条件:
- 固定条件(如
channelid)放入filter子句(不影响评分,且能利用缓存提升性能) - 可选的高级筛选条件(如时间范围、发送人、文件类型等)根据用户输入动态加入
must/filter子句 - 全文搜索类条件(如消息内容关键词、文件名关键词)放入
match或multi_match子句
1. 消息高级搜索的ES查询构建
假设高级搜索字段包含:关键词(消息内容)、发送人ID、消息类型、时间范围、是否包含附件,对应的ES查询JSON示例:
{ "query": { "bool": { "filter": [ {"term": {"channelid": "xxx"}}, {"range": {"send_time": {"gte": "2024-01-01T00:00:00", "lte": "2024-06-01T23:59:59"}}}, {"term": {"sender_id": "user_123"}}, {"term": {"msg_type": "text"}}, {"term": {"has_attachment": false}} ], "must": [ {"match": {"content": "会议纪要"}} ] } }, "sort": [{"send_time": {"order": "desc"}}] }
Go代码实现逻辑
用go-elasticsearch的esapi.Search动态拼接查询条件:
package main import ( "context" "encoding/json" "github.com/elastic/go-elasticsearch/v8" "github.com/elastic/go-elasticsearch/v8/esapi" "strings" ) func buildMessageAdvancedQuery(channelID string, keyword string, senderID string, msgType string, startTime string, endTime string, hasAttachment *bool) map[string]interface{} { boolQuery := map[string]interface{}{ "bool": map[string]interface{}{ "filter": []map[string]interface{}{ {"term": map[string]interface{}{"channelid": channelID}}, }, "must": []map[string]interface{}{}, }, } // 动态添加时间范围条件 if startTime != "" && endTime != "" { boolQuery["bool"].(map[string]interface{})["filter"] = append( boolQuery["bool"].(map[string]interface{})["filter"].([]map[string]interface{}), map[string]interface{}{ "range": map[string]interface{}{ "send_time": map[string]interface{}{"gte": startTime, "lte": endTime}, }, }, ) } // 添加发送人条件 if senderID != "" { boolQuery["bool"].(map[string]interface{})["filter"] = append( boolQuery["bool"].(map[string]interface{})["filter"].([]map[string]interface{}), map[string]interface{}{"term": map[string]interface{}{"sender_id": senderID}}, ) } // 添加消息类型条件 if msgType != "" { boolQuery["bool"].(map[string]interface{})["filter"] = append( boolQuery["bool"].(map[string]interface{})["filter"].([]map[string]interface{}), map[string]interface{}{"term": map[string]interface{}{"msg_type": msgType}}, ) } // 添加是否含附件条件 if hasAttachment != nil { boolQuery["bool"].(map[string]interface{})["filter"] = append( boolQuery["bool"].(map[string]interface{})["filter"].([]map[string]interface{}), map[string]interface{}{"term": map[string]interface{}{"has_attachment": *hasAttachment}}, ) } // 添加关键词全文搜索 if keyword != "" { boolQuery["bool"].(map[string]interface{})["must"] = append( boolQuery["bool"].(map[string]interface{})["must"].([]map[string]interface{}), map[string]interface{}{"match": map[string]interface{}{"content": keyword}}, ) } return map[string]interface{}{"query": boolQuery, "sort": []map[string]interface{}{{"send_time": map[string]interface{}{"order": "desc"}}}} } func searchMessages(es *elasticsearch.Client, ctx context.Context, query map[string]interface{}) (*esapi.Response, error) { reqBody, err := json.Marshal(query) if err != nil { return nil, err } req := esapi.SearchRequest{ Index: []string{"messages"}, Body: strings.NewReader(string(reqBody)), } return req.Do(ctx, es) }
2. 文件高级搜索的ES查询构建
假设高级搜索字段包含:文件名关键词、上传人ID、文件类型、上传时间范围、文件大小范围,对应的ES查询JSON示例:
{ "query": { "bool": { "filter": [ {"term": {"channelid": "xxx"}}, {"range": {"upload_time": {"gte": "2024-01-01T00:00:00", "lte": "2024-06-01T23:59:59"}}}, {"range": {"file_size": {"gte": 1024, "lte": 1048576}}}, {"term": {"uploader_id": "user_456"}}, {"term": {"file_type": "pdf"}} ], "must": [ {"match": {"name": "项目方案"}} ] } }, "sort": [{"upload_time": {"order": "desc"}}] }
Go代码实现逻辑
类似消息搜索,动态拼接条件:
func buildFileAdvancedQuery(channelID string, keyword string, uploaderID string, fileType string, startTime string, endTime string, minSize, maxSize int64) map[string]interface{} { boolQuery := map[string]interface{}{ "bool": map[string]interface{}{ "filter": []map[string]interface{}{ {"term": map[string]interface{}{"channelid": channelID}}, }, "must": []map[string]interface{}{}, }, } // 上传时间范围 if startTime != "" && endTime != "" { boolQuery["bool"].(map[string]interface{})["filter"] = append( boolQuery["bool"].(map[string]interface{})["filter"].([]map[string]interface{}), map[string]interface{}{ "range": map[string]interface{}{ "upload_time": map[string]interface{}{"gte": startTime, "lte": endTime}, }, }, ) } // 文件大小范围 if minSize > 0 || maxSize > 0 { sizeRange := map[string]interface{}{} if minSize > 0 { sizeRange["gte"] = minSize } if maxSize > 0 { sizeRange["lte"] = maxSize } boolQuery["bool"].(map[string]interface{})["filter"] = append( boolQuery["bool"].(map[string]interface{})["filter"].([]map[string]interface{}), map[string]interface{}{"range": map[string]interface{}{"file_size": sizeRange}}, ) } // 上传人ID if uploaderID != "" { boolQuery["bool"].(map[string]interface{})["filter"] = append( boolQuery["bool"].(map[string]interface{})["filter"].([]map[string]interface{}), map[string]interface{}{"term": map[string]interface{}{"uploader_id": uploaderID}}, ) } // 文件类型 if fileType != "" { boolQuery["bool"].(map[string]interface{})["filter"] = append( boolQuery["bool"].(map[string]interface{})["filter"].([]map[string]interface{}), map[string]interface{}{"term": map[string]interface{}{"file_type": fileType}}, ) } // 文件名关键词 if keyword != "" { boolQuery["bool"].(map[string]interface{})["must"] = append( boolQuery["bool"].(map[string]interface{})["must"].([]map[string]interface{}), map[string]interface{}{"match": map[string]interface{}{"name": keyword}}, ) } return map[string]interface{}{"query": boolQuery, "sort": []map[string]interface{}{{"upload_time": map[string]interface{}{"order": "desc"}}}} } func searchFiles(es *elasticsearch.Client, ctx context.Context, query map[string]interface{}) (*esapi.Response, error) { reqBody, err := json.Marshal(query) if err != nil { return nil, err } req := esapi.SearchRequest{ Index: []string{"files"}, Body: strings.NewReader(string(reqBody)), } return req.Do(ctx, es) }
关键注意事项
- 字段映射匹配:确保
messages和files索引映射中,时间字段为date类型、数字字段为long/integer类型,避免查询时类型不匹配报错。 - 空值处理:所有高级筛选参数需做非空判断,仅将有值的条件加入查询,避免无效空条件影响性能。
- 性能优化:固定条件(如
channelid)放在filter里,Elasticsearch会缓存这些条件的结果,重复查询时速度更快。 - 分页与排序:高级搜索通常需要分页,可在
SearchRequest中添加From和Size参数,排序规则可根据时间或相关性调整。
内容的提问来源于stack exchange,提问作者Manzoor.faisal
相关产品推荐
相关产品推荐

