首页 > 其他分享 >三种获取Go项目根目录的方式,让你做架构,选哪种?

三种获取Go项目根目录的方式,让你做架构,选哪种?

时间:2022-11-21 18:34:50浏览次数:67  
标签:架构 demo 程序 Go 根目录 os 目录

大家好,这里是每周都陪你进步的网管~

在搭建项目中一般都会有确定项目根目录的绝对路径的需求,一旦有了根目录的绝对路径,就能以这个根目录为基准,设置静态文件、配置文件所在的目录,这样做的好处是无论把项目部署到哪个目录下,执行程序时都不会出现No such file or directory 这样的错误。

今天就总结一下在 Go 程序里边怎么获取项目的根目录绝对路径。在网上搜索怎么获取 Go 项目的根目录,一般有三种,分别依赖 Go 的以下三个底层函数实现:

  • os.Getwd() 
  • os.Args[0]
  • runtime.Caller

虽然这三种方式都能获取到Go项目的根目录,但前两种方式在某些情况下拿到的结果并不是我们想要的,只有使用第三种才是在所有执行环境下都能正确拿到Go项目的根目录路径。

我们接下来对这三种方法一一进行解释。首先我们来探讨一下为什么我们要在程序里拿到项目的根目录路径。

为什么需要项目根目录路径

这个问题其实开头已经提过了,假如一个项目有如下这样的目录结构

.
|-- config
|   `-- config.go
|   `-- config_dev.yaml
|-- main.go
|-- go.mod
|-- go.sum

假设我们要在config.go 中使用 viper库把config_dev.yaml中的配置项加载到内存中,这里看到config.goconfig_dev.yaml在同一个目录里,一般人都会这么设置配置文件的路径:

vp.AddConfigPath("./")
vp.SetConfigType("yaml")

乍一看起来,没啥毛病,不过假如运行下程序你就会发现完全不行,.虽然代表当前目录,但在Go语言里边它不代表是当前代码文件所在的目录,而是代表执行程序可执行文件的目录。

比如执行下面一套操作后

cd /Code/demo
go build -o demo.app
./demo.app

刚才我们在代码里写的".",它代表的是/Code/demo这个目录

PS,并不是所有语言都是这样,如果是Java程序,"."就是代表当前代码文件的文件目录。

好,搞清楚了我们为什么要费劲获取Go项目的根目录后,我们来说下三种获取他们的方法,以及为什么前两种不够通用。

os.Getwd

os.Getwd() 方法,Go 语言os包的Getwd函数能够获取进程的当前工作目录,其实从函数名字中的wd—working directory (工作目录)的缩写,差不多就能猜出来它的功能。

os.Getwd 改进我们上面的程序,能保证拿到的项目根目录是正确的吗?我们先用它改一下程序试试。

wd, _ := os.Getwd()
// 输出目录,看看路径对不对
fmt.Println("工作目录: " + wd)
// 用工作目录拼接出正确的配置文件目录
vp.AddConfigPath(wd +"/config")
vp.SetConfigType("yaml")

打包执行一下程序,会输出:

cd /Code/demo
go build -o demo.app
./demo.app
=== 以下是输出内容 ===
工作目录: /Code/demo

看起来没什么问题,不过刚才我们是在项目的根目录下编译并执行的程序,假如我们切换到其他目录执行呢?

cd /Users/xxx
/Code/demo/demo.app
=== 以下是执行后的输出内容 ===
工作目录:/Users/xxx

切换目录后再执行程序,发现程序中输出的工作目录变成了/Users/xxx,此时后面用它拼接出项目的配置文件目录的代码自然是不对的。

所以os.Getwd()这个方法获取的是进程在OS系统所在的目录,仅当在可执行文件所在的目录下启动程序的情况下才能正确拿到 Go 项目的根目录,这种情况还是不够通用的,需要与运维约定项目的启动命令才行。

os.Args[0]

接下来我们看第二种方式,os.Args这个列表里保存的是程序的启动参数,而参数0按照约定是程序的可执行文件名。

下面我们用它改进一下我们的程序

filePath, _ := exec.LookPath(os.Args[0])
absFilePath, _ := filepath.Abs(filePath)
rootDir := path.Dir(absFilePath)
// 输出目录,看看路径对不对
fmt.Println("程序根目录: " + rootDir)
// 用程序根目录拼接出正确的配置文件目录
vp.AddConfigPath(rootDir +"/config")
vp.SetConfigType("yaml")

打包编译程序后,试着在程序所在目录和其他的目录下都执行一下程序,看看程序能不能拿到正确的路径

cd /Code/demo
go build -o demo.app
./demo.app
=== 以下是执行后的输出内容 ===
程序根目录:/Code/demo

cd /Users/xxx
/Code/demo/demo.app
=== 以下是执行后的输出内容 ===
程序根目录:/Code/demo

通过上面的输出我们能看到,这两种情况都能够正确拿到程序的目录路径,os.Args[0] 在这两种情况下的值分别是./demo.app/Code/demo/demo.app。示例程序里之后的两个方法调用会帮我们找到可执行文件所在目录的绝对路径。

这种方式看起来挺完美,不过有一种情况它是满足不了的,就是,如果我们在研发阶段用go run启动程序的时候是不行的,此时程序会输出一个临时目录。

/var/folders/3g/f2sh8sgs5ls_z62npf80v69w0000gn/T/go-build1053443992/b001/exe

接下来我们再看第三种方法,它能适配各种情况。

runtime.Caller

想获取到程序的根目录,如果能拿到当前正在执行的代码的文件路径,我们也就能推断出程序的根目录了。怎么能拿到当前正在执行的代码的文件路径呢?

之前我们在介绍日志库Zap的时候说过,好的日志是需要记录下来当时的现场信息的--比如记录日志的代码所在的文件路径、行号、函数名等。这些信息怎么获取到的呢?当时看了源码后我们发现用的是 runtime.Caller()

func Caller(skip int) (pc uintptr, file string, line int, ok bool)

所以我们在config.go中,能这样获取当前文件的路径:

// 获取当前文件的路径
 _, filename, _, _ := runtime.Caller(0)

runtime.Caller 这里不在详细介绍,感兴趣的请看这两篇旧文

用它再推断出项目的根目录即可

root := path.Dir(path.Dir(filename))

用这种方法改造代码后,我们再用上面的几种启动方式,会看到都能正确获取到程序的根目录。

应该用哪种方式?

应该用哪种方式呢?其实没有固定答案,第三种方式更通用,不论是在开发阶段使用 go run ,还是我们在项目里写单元测试,用InteliJ IDEA 这样的IDE帮我们执行单元测试的时候,都是把程序放到一个临时目录执行的,所以第三种方式更通用一些。

如果是在生产环境启动项目,要是能跟运维约定好启动命令,用前两种方式也是没有问题的,甚至我们可以让运维在系统里设置ROOTDIR之类的环境变量,把根目录放在环境变量里,在程序里用os.Getenv("ROOTDIR")取也行。

如果让你架构项目,你会用哪种方式呢?评论区里说说吧,喜欢今天的文章欢迎转发和点赞,我们下期再见。

标签:架构,demo,程序,Go,根目录,os,目录
From: https://www.cnblogs.com/cheyunhua/p/16912244.html

相关文章

  • JVM虚拟机(整体架构、类文件结构)我来了~~~
    虚拟机1.1发展历程1.1.1java往事​ Java诞生在一群懒惰、急躁而傲慢的程序天才之中。​ 1990年12月,Sun的工程师PatrickNaughton被当时糟糕的SunC++工具折磨的快疯......
  • Spring Data (数据)MongoDB(二)
    10.6.查询文档您可以使用theandclasses来表达您的查询。它们具有反映本机MongoDB运算符名称的方法名称,例如,,,等。Theandclasses遵循流畅的API样式,因此您可以将多个方......
  • Spring Data (数据)MongoDB(三)
    10.21.更改流从MongoDB3.6开始,ChangeStreams允许应用程序获得有关更改的通知,而无需跟踪oplog。更改流支持仅适用于副本集或分片集群。更改流可以与命令式和反应式MongoDB......
  • go环境以及编译器安装
    go安装1.win10安装GO去Go官网下载,安装包验证安装cmd输入goversion,能出现版本号即可2.Goland安装Goland官网下载,下载好后直接安装即可3.创建go项目选择第一......
  • django ORM之Q查询与F查询
    F对象查询与Q对象查询也是Django提供的查询方法,而且非常的简单的高效,对于一些特殊的场景需求应用起来非常的合适,在本文中我们将对这两种查询方法进行讲解,帮助大家掌握它......
  • OpenSergo 流量路由:从场景到标准化的探索
    简介: 本文我们将从流量路由这个场景入手,从常见的微服务治理场景出发。先是根据流量路由的实践设计流量路由的Spec,同时在SpringCloudAlibaba中实践遵循OpenSergo标......
  • 从零到一搭建基础架构(6)-让你的服务组件化
    Hello,这里是爱Coding,爱Hiphop,爱喝点小酒的AKA柏炎。本篇是手把手搭建基础架构专栏的第六篇,......
  • LR低代码快速开发平台 高效调整企业组织架构
    组织架构以及围绕组织架构的设计、实施和变革,是企业管理永恒的话题,它上承公司的业务战略和运营模式,下接业务流程和信息系统建设,重要性不言而喻。数字化变革浪潮之下,商业模式......
  • mongo索引
    1、通过在查询语句后加.explain() 能查看是否用到合适的索引扫描99999条才返回一条 高效的索引,扫描一个,返回一个。完全命中索引2、组合索引建索引 按esr原则 精确匹......
  • go 语言 http包详解
      首先,熟悉http协议的都知道,http协议是基于TCP实现的。 http服务器的工作方式大概就是监听socket端口,接受连接,获取到请求,处理请求,返回响应。 所以,对应的会有几个部分......