快速了解GO+ElasticSearch

news2025/6/6 17:52:44

更多个人笔记见:
注意点击“继续”,而不是“发现新项目”
github个人笔记仓库 https://github.com/ZHLOVEYY/IT_note
gitee 个人笔记仓库 https://gitee.com/harryhack/it_note
个人学习,学习过程中还会不断补充~ (后续会更新在github上)

文章目录

    • 简单介绍
    • 经典例子
        • 示范 demo 代码
        • 优化后的代码
        • 参考:es 返回格式示例:
        • 空指针问题

简单介绍

ES 是一个分布式搜索和分析引擎,用于高效全文搜索。
与 Mysql 存储后查询相比,ES支持用户快速搜索“包含某关键词的文章,以及实现“附近动态”或“热门话题”等复杂排序和过滤

经典例子

GO 相关包:go get -u github.com/elastic/go-elasticsearch/v8
docker 快速部署:docker run -d --name elasticsearch -p 9200:9200 -e "discovery.type=single-node" elasticsearch:8.14.0

# 启用现有容器
docker start elasticsearch

#如果希望重新部署:
# 首先停止容器(如果它在运行)
docker stop elasticsearch

# 然后移除容器
docker rm elasticsearch

# 查看容器
docker ps -a | grep elasticsearch

启动后你会发现,访问不行。这是因为 es 设置了密码和安全验证,那么我们在开发环境下可以这么启动:

docker run -d --name elasticsearch \
  -p 9200:9200 \
  -e "discovery.type=single-node" \
  -e "xpack.security.enabled=false" \
  elasticsearch:8.14.0

记得先用上面的指令暂停之前的容器并移除,重新 docker 启动

示范 demo 代码
package main

import (
	"log"
	"strings"

	"github.com/elastic/go-elasticsearch/v8"
)

func main() {
	cfg := elasticsearch.Config{
		Addresses: []string{"http://localhost:9200"},
	}
	es, err := elasticsearch.NewClient(cfg)
	if err != nil {
		log.Fatalf("Error creating Elasticsearch client: %v", err)
	}

	// 索引一个文档
	_, err = es.Index("posts", strings.NewReader(`{"title":"Test Post","content":"Hello world"}`))
	if err != nil {
		log.Fatalf("Error indexing document: %v", err)
	}

	// 搜索
	res, err := es.Search(
		es.Search.WithIndex("posts"),
		es.Search.WithQuery("hello"),
	)
	if err != nil {
		log.Fatalf("Error searching: %v", err)
	}
	defer res.Body.Close()
	log.Println(res.String())
}

发现看到的结果不太能看懂,进行进一步解析:

优化后的代码
package main

import (
	"encoding/json"
	"log"
	"strings"

	"github.com/elastic/go-elasticsearch/v8"
)

func main() {
	cfg := elasticsearch.Config{
		Addresses: []string{"http://localhost:9200"},
	}
	es, err := elasticsearch.NewClient(cfg)
	if err != nil {
		log.Fatalf("Error creating Elasticsearch client: %v", err)
	}

	// 在搜索前刷新索引
	_, err = es.Indices.Refresh(es.Indices.Refresh.WithIndex("posts"))
	if err != nil {
		log.Fatalf("Error refreshing index: %v", err)
	}

	// 索引一个文档
	_, err = es.Index("posts", strings.NewReader(`{"title":"Test Post","content":"Hello world"}`))
	if err != nil {
		log.Fatalf("Error indexing document: %v", err)
	}

	// 搜索
	res, err := es.Search(
		es.Search.WithIndex("posts"),
		es.Search.WithBody(strings.NewReader(`{
			"query": {
				"match": {
					"content": "hello"
				}
			}
		}`)),
	)
	if err != nil {
		log.Fatalf("Error searching: %v", err)
	}
	defer res.Body.Close()

	// 解析搜索结果 (这个算是一个通用的模板)
	var r map[string]interface{}
	if err := json.NewDecoder(res.Body).Decode(&r); err != nil {
		log.Fatalf("Error parsing the response body: %s", err)
	}

	// 现在可以访问解析后的结果
	hits := r["hits"].(map[string]interface{})["hits"].([]interface{})
	for _, hit := range hits {
		source := hit.(map[string]interface{})["_source"]
		log.Printf("Found document: %v", source)
	}
}

这样就可以看到很好的输出结果。
不过记得如果不注释,每次执行程序都会新建一个索引的

参考:es 返回格式示例:
{
  "took": 10,                  // 查询耗时(毫秒)
  "timed_out": false,          // 是否超时
  "hits": {                    // 命中结果
    "total": {                 // 总匹配数
      "value": 2,              // 具体数量
      "relation": "eq"         // 计数关系(eq 表示精确值)
    },
    "max_score": 1.0,          // 最高相关性得分
    "hits": [                  // 文档数组
      {
        "_index": "my_index",  // 索引名
        "_id": "1",            // 文档ID
        "_score": 1.0,         // 当前文档得分
        "_source": {           // 原始文档数据   这是我们需要的!!
          "title": "Elasticsearch入门",
          "content": "学习ES基础用法"
        }
      }
    ]
  }
}
空指针问题

下面这个函数在解析 es 结果的时候会报空指针的错误,分析下原因

func SearchPosts(query string) ([]models.Post, error) {
	var posts []models.Post
	res, err := esClient.Search(
		esClient.Search.WithIndex("posts"),
		esClient.Search.WithQuery(`{"match": {"title": {"query": "`+query+`"}}}`), //查询针对的是文档的 title 字段,使用的是 match 查询,这是一种全文检索查询,用变量 query 的值作为搜索词
	)
	if err != nil {
		return nil, err
	}
	defer res.Body.Close()

	// 解析结果(简化处理,仅提取 hits)
	var result map[string]interface{}
	if err := json.NewDecoder(res.Body).Decode(&result); err != nil { //将结果放入 result 中
		return nil, err
	}
	hits := result["hits"].(map[string]interface{})["hits"].([]interface{})
	for _, hit := range hits {
		source := hit.(map[string]interface{})["_source"].(map[string]interface{})
		posts = append(posts, models.Post{
			Title:   source["title"].(string), //结构化输出,放入结构体,然后放入列表中
			Content: source["content"].(string),
		})
	}
	return posts, nil
}

这个函数存在空指针错误,原因如下:

  • 如果 ES 返回的响应中缺少 hits 字段(如查询语法错误或索引不存在),result["hits"].(map[string]interface{}) 会触发 panic
  • 即使 hits 存在,若搜索结果为空(hits.hits 为空数组),循环内的 hit(map[string]interface{}) 虽不会报错,但后续对 _source 的访问仍需校验
  • 进一步的,如果某文档无 titlecontent 字段,source["title"].(string) 会因类型断言失败或字段不存在而崩溃

结合前面的参照 es 的返回格式更好理解

所以可以采用逐层校验的方式:

hits, ok := result["hits"].(map[string]interface{})
if !ok {
    return nil, fmt.Errorf("invalid hits format in ES response")
}

hitList, ok := hits["hits"].([]interface{})
if !ok {
    return nil, fmt.Errorf("invalid hits.hits format")
}

for _, hit := range hitList {
    hitMap, ok := hit.(map[string]interface{})
    if !ok {
        continue // 跳过无效条目
    }
    source, ok := hitMap["_source"].(map[string]interface{})
    if !ok {
        continue
    }
    // 安全获取字段(支持缺省值)
    title, _ := source["title"].(string)    // 若字段不存在,title=""
    content, _ := source["content"].(string)
    posts = append(posts, models.Post{
        Title:   title,
        Content: content,
    })
}

这就是优化后对于数据的解析

我的博客中还有后续进阶的例子可以查看~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2399160.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

定制开发开源AI智能名片驱动下的海报工厂S2B2C商城小程序运营策略——基于社群口碑传播与子市场细分的实证研究

摘要 本文聚焦“定制开发开源AI智能名片S2B2C商城小程序”技术与海报工厂业务的融合实践,探讨其如何通过风格化海报矩阵的精细化开发、AI技术驱动的用户体验升级,以及S2B2C模式下的社群裂变机制,实现“工具功能-社交传播-商业变现”的生态…

【Unity开发】控制手机移动端的震动

🐾 个人主页 🐾 阿松爱睡觉,横竖醒不来 🏅你可以不屠龙,但不能不磨剑🗡 目录 一、前言二、Unity的Handheld.Vibrate()三、调用Android原生代码四、NiceVibrations插件五、DeviceVibration插件六、控制游戏手…

Cesium快速入门到精通系列教程二:添加地形与添加自定义地形、相机控制

一、添加地形与添加自定义地形 在 Cesium 1.93 中添加地形可以通过配置terrainProvider实现。Cesium 支持多种地形数据源,包括 Cesium Ion 提供的全球地形、自定义地形服务以及开源地形数据。下面介绍几种常见的添加地形的方法: 使用 Cesium Ion 全球地…

python学习打卡day43

DAY 43 复习日 作业: kaggle找到一个图像数据集,用cnn网络进行训练并且用grad-cam做可视化 浙大疏锦行 数据集使用猫狗数据集,训练集中包含猫图像4000张、狗图像4005张。测试集包含猫图像1012张,狗图像1013张。以下是数据集的下…

Microsoft Word使用技巧分享(本科毕业论文版)

小铃铛最近终于完成了毕业答辩后空闲下来了,但是由于学校没有给出准确地参考模板,相信诸位朋友们也在调整排版时感到头疼,接下来小铃铛就自己使用到的一些排版技巧分享给大家。 注:以下某些设置是根据哈尔滨工业大学(威…

windows安装多个版本composer

一、需求场景 公司存在多个项目,有的项目比较老,需要composer 1.X版本才能使用 新的项目又需要composer 2.X版本才能使用 所以需要同时安装多个版本的composer二、下载多个版本composer #composer官网 https://getcomposer.org/download/三、放到指定目…

【办公类-22-05】20250601Python模拟点击鼠标上传CSDN12篇

、 背景需求: 每周为了获取流量券,每天上传2篇,获得1500流量券,每周共上传12篇,才能获得3000和500的券。之前我用UIBOT模拟上传12篇。 【办公类-22-04】20240418 UIBOT模拟上传每天两篇,获取流量券,并删除内容_csdn 每日任务流量券-CSDN博客文章浏览阅读863次,点赞18…

贪心算法应用:边着色问题详解

贪心算法应用:边着色问题详解 贪心算法是一种在每一步选择中都采取当前状态下最优的选择,从而希望导致结果是全局最优的算法策略。边着色问题是图论中的一个经典问题,贪心算法可以有效地解决它。下面我将从基础概念到具体实现,全…

ck-editor5的研究 (2):对 CKEditor5 进行设计,并封装成一个可用的 vue 组件

前言 在上一篇文章中—— ck-editor5的研究(1):快速把 CKEditor5 集成到 nuxt 中 ,我仅仅是把 ckeditor5 引入到了 nuxt 中,功能还不算通用。 这一篇内容将会对其进行设计,并封装成可复用的 vue 组件&…

Java-redis实现限时在线秒杀功能

1.使用redisson pom文件添加redisson <!--redisson--><dependency><groupId>org.redisson</groupId><artifactId>redisson-spring-boot-starter</artifactId><version>3.23.4</version></dependency> 2.mysql数据库表设…

simulink mask、sfunction和tlc的联动、接口

这里全部是讲的level2 sfunction&#xff08;用m语言编写&#xff09;&#xff0c;基于matlab 2020a。 1.mask的参数操作 1&#xff09;mask通过set_param和get_param这2个函数接口对mask里面定义的Parameters&Dialog的参数的大部分属性进行读写&#xff0c;一般是Value值…

VMWare安装常见问题

如果之前安装过VMWare软件&#xff0c;只要是 15/16 版本的&#xff0c;可以正常使用的&#xff0c;不用卸载&#xff01;&#xff01;&#xff01; 如果之前安装过&#xff0c;卸载了&#xff0c;一定要保证通过正常的渠道去卸载&#xff08;通过控制面板卸载软件&#xff09…

【北邮 操作系统】第十二章 文件系统实现

一、文件的物理结构 1.1 文件块、磁盘块 类似于内存分页&#xff0c;磁盘中的存储单元也会被分为一个个“块/磁盘块/物理块”。很多操作系统中&#xff0c;磁盘块的大小与内存块、页面的大小相同 内存与磁盘之间的数据交换(即读/写操作、磁盘I/0)都是以“块”为单位进行的。即…

Docker 插件生态:从网络插件到存储插件的扩展能力解析

Docker 容器技术以其轻量、快速、可移植的特性,迅速成为构建和部署现代应用的核心工具。然而,尽管 Docker Engine 自身功能强大,但在面对多样化的生产环境和复杂业务需求时,仅靠核心功能往往无法满足所有场景。 例如,跨主机的容器网络通信、异构存储系统的持久化数据管理…

WordPress搜索引擎优化的最佳重定向插件:进阶指南

在管理网站时&#xff0c;我们经常需要调整网页地址或修复错误链接。这时&#xff0c;通过重定向不仅能有效解决这些问题&#xff0c;还能显著提升网站在搜索引擎中的排名。对于熟悉基础重定向插件的用户来说&#xff0c;一些功能更强大的工具可以帮助你更全面地管理网站&#…

org.junit.runners.model.InvalidTestClassError:此类问题的解决

不知道大家是否遇见过以上这种情况&#xff0c;我也是今天被这个错误搞得很烦&#xff0c;后来通过网上查找资料终于找到了问题所在————就是简单的Test注解的错误使用 Test注解的注意情况 &#xff1a;1 权限必须是public 2 不能有参数 3 返回值类型是void 4 本类的其他的…

用户管理页面(解决toggleRowSelection在dialog用不了的隐患,包含el-table的plus版本的组件)

新增/编辑/删除/分配角色&#xff0c;图片上传在此文章分类下另一个文章 1.重点分配角色&#xff1a; <template><!-- 客户资料 --><div class"pageBox"><elPlusTable :tableData"tableData" :tablePage"tablePage" onSi…

Linux-GCC、makefile、GDB

GCC gcc -E test.c -o test.i预处理(-o指定文件名) gcc -S test.i -o test.s编译gcc -c test.s -o test.o汇编gcc test.o -o test链接(生成一个可执行程序的软连接) gcc test.c -o test一条指令可以完成以上所有内容 gcc *.c -I(大写的i) include由于在main.c中找不到当前文件…

[MySQL初阶]MySQL(7) 表的内外连接

标题&#xff1a;[MySQL初阶]MySQL(7)表的内外连接 水墨不写bug 文章目录 一. 内连接 (INNER JOIN)二. 外连接 (OUTER JOIN)关键区别总结 三、 如何选择 在 MySQL 中&#xff0c;连接&#xff08;JOIN&#xff09;用于根据两个或多个表之间的相关列组合行。内连接&#xff08;I…

【R语言编程绘图-mlbench】

mlbench库简介 mlbench是一个用于机器学习的R语言扩展包&#xff0c;主要用于提供经典的基准数据集和工具&#xff0c;常用于算法测试、教学演示或研究场景。该库包含多个知名数据集&#xff0c;涵盖分类、回归、聚类等任务。 包含的主要数据集 BostonHousing 波士顿房价数据…