Go 语言

环境安装

下载安装

下载(Go 安装包下载)可执行文件版,直接点击下一步安装即可,默认会将 go 安装到/usr/local/go 目录下。

安装完成后,输入 go version 命令,查看安装的 Go 版本。

go version

配置 GOPATH

GOPATH 目录下一共包含了三个子目录,分别是:

  • bin:存储所编译生成的二进制文件。
  • pkg:存储预编译的目标文件,以加快程序的后续编译速度。
  • src:存储所有.go 文件或源代码。在编写 Go 应用程序,程序包和库时,一般会以 $GOPATH/src/github.com/foo/bar 的路径进行存放。

编辑 ~/.bash_profile(在终端中运行 vi ~/.bash_profile 即可)来添加下面这行代码(如果你找不到 .bash_profile,那就自己创建一个)。

export GOPATH=/Users/alvin/coder/go

保存然后退出你的编辑器。然后在终端中运行下面命令

source ~/.bash_profile

提示:$HOME 是每个电脑下的用户主目录,每个电脑可能不同,可以在终端运行 echo $HOME 获取

GOROOT 也就是 Go 开发包的安装目录默认是在 /usr/local/go,如果没有,可以在 bash_profile 文件中设置。

export GOROOT=/usr/local/go

然后保存并退出编辑器,运行 source ~/.bash_profile 命令即可。

使用 GOPATH 模式下,我们需要将应用代码存放在固定的 $GOPATH/src 目录下,并且如果执行 go get 来拉取外部依赖会自动下载并安装到$GOPATH 目录下。

GOPATH 模式下没有版本控制的概念,具有致命的缺陷,至少会造成以下问题:

1. 在执行go get的时候,你无法传达任何的版本信息的期望,也就是说你也无法知道自己当前更新的是哪一个版本,也无法通过指定来拉取自己所期望的具体版本。
2. 在运行 Go 应用程序的时候,你无法保证其它人与你所期望依赖的第三方库是相同的版本,也就是说在项目依赖库的管理上,你无法保证所有人的依赖版本都一致。
3. 你没办法处理 v1、v2、v3 等等不同版本的引用问题,因为 GOPATH 模式下的导入路径都是一样的,都是github.com/foo/bar。

Go Modules

在 Go modules 中,我们能够使用如下命令进行操作:

命令 作用
go mod init 生成 go.mod 文件
go mod download 下载 go.mod 文件中指明的所有依赖
go mod tidy 整理现有的依赖
go mod graph 查看现有的依赖结构
go mod edit 编辑 go.mod 文件
go mod vendor 导出项目所有的依赖到 vendor 目录
go mod verify 校验一个模块是否被篡改过
go mod why 查看为什么需要依赖某模块

所提供的环境变量

在 Go modules 中有如下常用环境变量,我们可以通过 go env 命令来进行查看,如下:

$ go env
GO111MODULE="auto"
GOPROXY="https://proxy.golang.org,direct"
GONOPROXY=""
GOSUMDB="sum.golang.org"
GONOSUMDB=""
GOPRIVATE=""

GO111MODULE

Go 语言提供了 GO111MODULE 这个环境变量来作为 Go modules 的开关,其允许设置以下参数:

  • auto:只要项目包含了 go.mod 文件的话启用 Go modules,目前在 Go1.11 至 Go1.14 中仍然是默认值。
  • on:启用 Go modules,推荐设置,将会是未来版本中的默认值。
  • off:禁用 Go modules,不推荐设置。

GOPROXY

这个环境变量主要是用于设置 Go 模块代理(Go module proxy),其作用是用于使 Go 在后续拉取模块版本时能够脱离传统的 VCS 方式,直接通过镜像站点来快速拉取。

GOPROXY 的默认值是:https://proxy.golang.org,direct,这有一个很严重的问题,就是 proxy.golang.org 在国内是无法访问的,因此这会直接卡住你的第一步,所以你必须在开启 Go modules 的时,同时设置国内的 Go 模块代理,执行如下命令:

$ go env -w GOPROXY=https://goproxy.cn,direct

GOPROXY 的值是一个以英文逗号 “,” 分割的 Go 模块代理列表,允许设置多个模块代理,假设你不想使用,也可以将其设置为 “off” ,这将会禁止 Go 在后续操作中使用任何 Go 模块代理。

direct是什么?

实际上 “direct” 是一个特殊指示符,用于指示 Go 回源到模块版本的源地址去抓取(比如 GitHub 等),场景如下:当值列表中上一个 Go 模块代理返回 404 或 410 错误时,Go 自动尝试列表中的下一个,遇见 “direct” 时回源,也就是回到源地址去抓取,而遇见 EOF 时终止并抛出类似 “invalid version: unknown revision...” 的错误。

GOSUMDB

它的值是一个 Go checksum database,用于在拉取模块版本时(无论是从源站拉取还是通过 Go module proxy 拉取)保证拉取到的模块版本数据未经过篡改,若发现不一致,也就是可能存在篡改,将会立即中止。

GOSUMDB 的默认值为:sum.golang.org,在国内也是无法访问的,但是 GOSUMDB 可以被 Go 模块代理所代理(详见:Proxying a Checksum Database)。

因此我们可以通过设置 GOPROXY 来解决,而先前我们所设置的模块代理 goproxy.cn 就能支持代理 sum.golang.org,所以这一个问题在设置 GOPROXY 后,你可以不需要过度关心。

GONOPROXY/GONOSUMDB/GOPRIVATE

这三个环境变量都是用在当前项目依赖了私有模块,例如像是你公司的私有 git 仓库,又或是 github 中的私有库,都是属于私有模块,都是要进行设置的,否则会拉取失败。

更细致来讲,就是依赖了由 GOPROXY 指定的 Go 模块代理或由 GOSUMDB 指定 Go checksum database 都无法访问到的模块时的场景。

而一般建议直接设置 GOPRIVATE,它的值将作为 GONOPROXY 和 GONOSUMDB 的默认值,所以建议的最佳姿势是直接使用 GOPRIVATE。

并且它们的值都是一个以英文逗号 “,” 分割的模块路径前缀,也就是可以设置多个,例如:

$ go env -w GOPRIVATE="git.example.com,github.com/eddycjy/mquote"

设置后,前缀为 git.xxx.comgithub.com/eddycjy/mquote 的模块都会被认为是私有模块。

如果不想每次都重新设置,我们也可以利用通配符,例如:

$ go env -w GOPRIVATE="*.example.com"

这样子设置的话,所有模块路径为 example.com 的子域名(例如:git.example.com)都将不经过 Go module proxy 和 Go checksum database,需要注意的是不包括 example.com 本身。

初始化项目

在完成 Go modules 的开启后,我们需要创建一个示例项目来进行演示,执行如下命令:

cd /Users/alvin/coder/go/src/alvinhtml.com/module-repo
go mod init alvinhtml.com/module-repo

go: creating new go.mod: module alvinhtml.com/module-repo

在执行 go mod init 命令时,我们指定了模块导入路径为 alvinhtml.com/module-repo。接下来我们在该项目根目录下创建 main.go 文件,如下:

package main


import (
    "fmt"
    "github.com/gin-gonic/gin"
)


func main() {
    fmt.Println(mquote.GetHello())
}

然后在项目根目录执行 go get github.com/gin-gonic/gin 命令,如下:

go get github.com/gin-gonic/gin
# go: github.com/gin-gonic/gin upgrade => v1.6.3
# go: downloading gopkg.in/yaml.v2 v2.2.8
# go: downloading github.com/golang/protobuf v1.3.3
# go: downloading github.com/go-playground/validator/v10 v10.2.0
# go: downloading golang.org/x/sys v0.0.0-20200116001909-b77594299b42
# go: downloading github.com/json-iterator/go v1.1.9
# go: downloading github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742
# go: downloading github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421

查看 go.mod 文件

在初始化项目时,会生成一个 go.mod 文件,是启用了 Go modules 项目所必须的最重要的标识,同时也是 GO111MODULE 值为 auto 时的识别标识,它描述了当前项目(也就是当前模块)的元信息,每一行都以一个动词开头。

在我们刚刚进行了初始化和简单拉取后,我们再次查看 go.mod 文件,基本内容如下:

module alvinhtml.com/module-repo

go 1.15

require github.com/gin-gonic/gin v1.6.3 // indirect

为了更进一步的讲解,我们模拟引用如下:

module github.com/eddycjy/module-repo


go 1.13


require (
    example.com/apple v0.1.2
    example.com/banana v1.2.3
    example.com/banana/v2 v2.3.4
    example.com/pear // indirect
    example.com/strawberry // incompatible
)


exclude example.com/banana v1.2.4
replace example.com/apple v0.1.2 => example.com/fried v0.1.0
replace example.com/banana => example.com/fish

module:用于定义当前项目的模块路径。

go:用于标识当前模块的 Go 语言版本,值为初始化模块时的版本,目前来看还只是个标识作用。

require:用于设置一个特定的模块版本。

exclude:用于从使用中排除一个特定的模块版本。

replace:用于将一个模块版本替换为另外一个模块版本。

另外你会发现 example.com/pear 的后面会有一个 indirect 标识,indirect 标识表示该模块为间接依赖,也就是在当前应用程序中的 import 语句中,并没有发现这个模块的明确引用,有可能是你先手动 go get 拉取下来的,也有可能是你所依赖的模块所依赖的,情况有好几种。

查看 go.sum 文件

在第一次拉取模块依赖后,会发现多出了一个 go.sum 文件,其详细罗列了当前项目直接或间接依赖的所有模块版本,并写明了那些模块版本的 SHA-256 哈希值以备 Go 在今后的操作中保证项目所依赖的那些模块版本不会被篡改。

我们可以看到一个模块路径可能有如下两种:

github.com/eddycjy/mquote v0.0.1 h1:4QHXKo7J8a6J/k8UA6CiHhswJQs0sm2foAQQUq8GFHM=
github.com/eddycjy/mquote v0.0.1/go.mod h1:ZtlkDs7Mriynl7wsDQ4cU23okEtVYqHwl7F1eDh4qPg=

h1 hash 是 Go modules 将目标模块版本的 zip 文件开包后,针对所有包内文件依次进行 hash,然后再把它们的 hash 结果按照固定格式和算法组成总的 hash 值。

而 h1 hash 和 go.mod hash 两者,要不就是同时存在,要不就是只存在 go.mod hash。那什么情况下会不存在 h1 hash 呢,就是当 Go 认为肯定用不到某个模块版本的时候就会省略它的 h1 hash,就会出现不存在 h1 hash,只存在 go.mod hash 的情况。

查看全局缓存

我们刚刚成功的将 github.com/gin-gonic/gin 模块拉取了下来,其拉取的结果缓存在 $GOPATH/pkg/mod$GOPATH/pkg/sumdb 目录下,而在 mod 目录下会以 github.com/foo/bar 的格式进行存放,如下:

mod
├── cache
├── github.com
├── golang.org
├── google.golang.org
├── gopkg.in
...

需要注意的是同一个模块版本的数据只缓存一份,所有其它模块共享使用。如果你希望清理所有已缓存的模块版本数据,可以执行 go clean -modcache 命令。

Go Modules 下的 go get 行为

在拉取项目依赖时,你会发现拉取的过程总共分为了三大步,分别是 finding(发现)、downloading(下载)以及 extracting(提取), 并且在拉取信息上一共分为了三段内容:

v0.0.0-20200116001909-b77594299b42
  • v0.0.0 版本信息, v0.0.0 表示没有发布过任何 tag
  • 20200116001909 所拉取版本的 commit 时间, 以 UTC 时区为准
  • b77594299b42 所拉取版本的 commit 哈希

go get 的拉取行为

刚刚我们用 go get 命令拉取了新的依赖,那么 go get 又提供了哪些功能呢,常用的拉取命令如下:

命令 作用
go get 拉取依赖,会进行指定性拉取(更新),并不会更新所依赖的其它模块。
go get -u 更新现有的依赖,会强制更新它所依赖的其它全部模块,不包括自身。
go get -u -t ./... 更新所有直接依赖和间接依赖的模块版本,包括单元测试中用到的。

那么我想选择具体版本应当如何执行呢,如下:

go get golang.org/x/text@latest    # 拉取最新的版本,若存在tag,则优先使用。
go get golang.org/x/text@master    # 拉取 master 分支的最新 commit。
go get golang.org/x/text@v0.3.2    # 拉取 tag 为 v0.3.2 的 commit。
go get golang.org/x/text@342b2e    # 拉取 hash 为 342b231 的 commit,最终会被转换为 v0.3.2。

项目目录结构

.
├── bin
├── pkg
└── src
    ├── alvinhtml.com
    │   └── hello-world
    │       └── main.go
    └── github.com

go build

go build 表示将源代码编译成可执行文件。

hello-world 目录下执行:

go build

或者在其他目录执行以下命令:

go build alvinhtml.com/hello-world

go 编译器会去 GOPATH 的 src 目录下查找你要编译的 alvinhtml.com/hello-world 项目

编译得到的可执行文件会保存在执行编译命令的当前目录下,如果是 windows 平台会在当前目录下找到 hello-world.exe 可执行文件。

我们还可以使用 -o 参数来指定编译后得到的可执行文件的名字。

go build -o hello-world.exe

变量

标准声明

var 变量名 变量类型
var 变量名 类型 = 表达式

批量声明

var (
    a string
    b int
    c bool
    d float32
)

var name, age = "Q1mi", 20
  • 类型推导
    • var name = "Q1mi"
  • 短变量声明
    • 在函数内部,可以使用更简略的 := 方式声明并初始化变量。例如:m := 200
  • 匿名变量(哑元变量)
    • 匿名变量用一个下划线 _ 表示,例如:x, _ := foo()
    • 匿名变量不占用命名空间,不会分配内存,所以匿名变量之间不存在重复声明。
  • 注意事项:
    • 函数外的每个语句都必须以关键字开始(var、const、func 等)
    • := 不能使用在函数外。
    • _ 多用于占位,表示忽略值。

常量

相对于变量,常量是恒定不变的值,多用于定义程序运行期间不会改变的那些值。 常量的声明和变量声明非常类似,只是把 var 换成了 const,常量在定义的时候必须赋值。

const pi = 3.1415

const (
    a = 3.1415
    b = 2.7182
)

iota

iotago 语言的常量计数器,只能在常量的表达式中使用。

iotaconst 关键字出现时将被重置为 0。const 中每新增一行常量声明将使 iota 计数一次。 使用 iota 能简化定义,在定义枚举时很有用。

const (
    n1 = iota //0
    n2        //1
    _
    n4        //3
)

const (
    _  = iota
    KB = 1 << (10 * iota)
    MB = 1 << (10 * iota)
    GB = 1 << (10 * iota)
    TB = 1 << (10 * iota)
    PB = 1 << (10 * iota)
)

基本数据类型

整型

类型 描述
uint8 无符号 8 位整型 (0 到 255)
uint16 无符号 16 位整型 (0 到 65535)
uint32 无符号 32 位整型 (0 到 4294967295)
uint64 无符号 64 位整型 (0 到 18446744073709551615)
int8 有符号 8 位整型 (-128 到 127)
int16 有符号 16 位整型 (-32768 到 32767)
int32 有符号 32 位整型 (-2147483648 到 2147483647)
int64 有符号 64 位整型 (-9223372036854775808 到 9223372036854775807)

特殊整型

类型 描述
uint 32 位操作系统上就是 uint32,64 位操作系统上就是 uint64
int 32 位操作系统上就是 int32,64 位操作系统上就是 int64
uintptr 无符号整型,用于存放一个指针

浮点型

类型 描述
float32 最大范围约为 3.4e38,可以使用常量定义:math.MaxFloat32
float64 最大范围约为 1.8e308,可以使用常量定义:math.MaxFloat64

数字字面量语法(Number literals syntax)

布尔值

  • true
  • false

字符串

Go 语言中的字符串以原生数据类型出现,使用字符串就像使用其他原生数据类型(int、bool、float32、float64 等)一样。 Go 语言里的字符串的内部实现使用 UTF-8 编码。 字符串的值为双引号 (") 中的内容,可以在 Go 语言的源码中直接添加非 ASCII 码字符,例如:

s1 := "hello"
s2 := "你好"
  • 字符串转义符
    • \r 回车符(返回行首)
    • \n 换行符(直接跳到下一行的同列位置)
    • \t 制表符
    • \' 单引号
    • \" 双引号
    • \\ 反斜杠
  • 多行字符串
    • Go 语言中要定义一个多行字符串时,就必须使用反引号: `hello`

字符串的常用操作

方法 介绍
len(str) 求长度
+或 fmt.Sprintf 拼接字符串
strings.Split 分割
strings.contains 判断是否包含
strings.HasPrefix,strings.HasSuffix 前缀/后缀判断
strings.Index(),strings.LastIndex() 子串出现的位置
strings.Join(a[]string, sep string) join 操作

HTTP

理解 HTTP 相关的网络应用,主要关注两个地方-客户端(client)和服务端(server)

两者的交互主要是 client 的 request 以及 server 的 response,主要就在于如何接受 client 的 request 并向 client 返回 response

接收 request 的过程中,最重要的莫过于路由(router),即实现一个 Multiplexer 器。Go 中既可以使用内置的 mutilplexer --- DefautServeMux,也可以自定义。Multiplexer 路由的目的就是为了找到处理器函数(handler),后者将对 request 进行处理,同时构建 response

流程为:

Clinet -> Requests ->  [Multiplexer(router) -> handler  -> Response -> Clinet

理解 go 中的 http 服务,最重要就是要理解 Multiplexer 和 handler,Golang 中的 Multiplexer 基于 ServeMux 结构,同时也实现了 Handler 接口。下面对几个重要概念说明:

  • hander 函数: 具有 func(w http.ResponseWriter, r *http.Requests)签名的函数
  • handler 处理器(函数): 经过 HandlerFunc 结构包装的 handler 函数,它实现了 ServeHTTP 接口方法的函数。调用 handler 处理器的 ServeHTTP 方法时,即调用 handler 函数本身。


results matching ""

    No results matching ""