You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何基于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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 05:01:36