组织代码

项目结构:
默认情况下 go 的项目源代码存放在 GOPATH/src 目录下
推荐的项目组织:

个人开发
image.png

团队协作开发:
image.png

企业开发:
image.png

5、函数

定义:组织好的、可重复使用的、用于执行指定任务的 代码块。

go 中使用func 关键字定义函数

  1. func 函数名(参数) 返回值{
  2. 函数体
  3. }

1、包管理

  • 为了组织复杂的库 和 系统代码, 引入包的知识

go 语言中,包名遵循 go 项目的目录结构
demo
建立了一个购物系统, 可以使用 “shopping” 包名作为一个开始
然后把所有的源代码文件放到 $GOPATH/src/shopping/目录中

但是由于代码众多, 可能不想把所有东西都放在这个文件夹下 ~

  • 可能想单独把 数据库逻辑放在一个单独的目录

为了实现:
创建了一个子目录 $GOPATH/src/shopping/db/,

  • 子目录中文件的包名就是 db
  • 但是为了从另一个包访问它, 包括 shopping 包,需要导入 shopping/db
  1. // 目录结构
  2. $GOPATH/src/shopping/
  3. /db/
  4. db.go
  5. pricecheck.go
  6. /main/
  7. main.go 程序入口
  8. // db.go
  9. package db
  10. type Item struct{
  11. Price float64
  12. }
  13. func LoadItem(id int) *Item {
  14. return &Item{
  15. Price: 9.10,
  16. }
  17. }
  18. // 包名 和 文件夹名是一样的,package name 就是所在的文件夹的名字
  19. // pricecheck.go
  20. package shopping
  21. import(
  22. "shoppping/db"
  23. )
  24. func PriceCheck(itemId int)(float64, bool) {
  25. item := db.LoadItem( itemId )
  26. if item == nil{
  27. return 0,false
  28. }
  29. return item.Price, true
  30. }
  31. // main.go
  32. package main
  33. import (
  34. "shopping"
  35. "fmt"
  36. )
  37. func main() {
  38. fmt.Println(shopping.PriceCheck(4343))
  39. }
  40. 然后在 shopping/ 目录
  41. go run main/mian.go

[ 循环导入 ]

编写更复杂系统时,会遇到 循环导入
demo:
A 包导入 B 包
B 包又导入 A 包 ( 间接或直接导入 ) 编译器所不允许

demo//
目录结构:
image.png


复现 循环导入包 的错误代码:

  1. $GOPATH/src/shopping/
  2. /db/
  3. db.go
  4. pricecheck.go
  5. /main/
  6. main.go 程序入口
  7. //-----------------------------------------------------------------------------------//
  8. // shopping/pricecheck.go
  9. package shopping
  10. import(
  11. "shoppping/db"
  12. )
  13. type Item struct {
  14. Price float64
  15. }
  16. func PriceCheck(itemId int)(float64, bool) {
  17. item := db.LoadItem( itemId )
  18. if item == nil{
  19. return 0,false
  20. }
  21. return item.Price, true
  22. }
  23. //-----------------------------------------------------------------------------------//
  24. // shopping/db/db.go
  25. package db
  26. import "shopping" //因为当前的 Item 定义在 shopping/pricecheck.go
  27. func LoadItem(id int) *shopping.Item {
  28. return &shopping.Item{
  29. Price: 9.10,
  30. }
  31. }
  32. //-----------------------------------------------------------------------------------//
  33. // shopping/main/main.go
  34. package main
  35. import "shopping"
  36. func main(){
  37. shopping.PriceCheck(344)
  38. }

image.png
原因是什么?

Item struct 定义在 shopping/pricecheck.go 文件中
pricecheck.go import "shopping/db"
pricecheck.go 中的 PriceCheck 方法

db/db.go 中 import "shopping" 因为 LoadItem 要调用 shopping.Item 结构
main.go 中 import "shopping" ,

db 中 import 了 shopping , shopping 中的 pricecheck 又 import 了 db , 所以循环导入报错 //

如何 修正 ?

引入另外一个 包含共享结构体的 包来解决问题:
image.png
models/item.go 的内容:

  1. package models
  2. type Item struct{
  3. Price float64
  4. }

[ 关于可见性 ]

go 中对于可见性的简单规则:

  • 类型 或者函数名称 以一个大写字母开始就具有包外可见性
  • 以小写字母开始, 就不具有
    • 也可以应用到结构体字段。如果一个字段名以一个小写字母开始,只有包内的代码可以访问他们。

[ 包管理 ]

go 除了 build、run 还有一个go get 命令,用于获取第三方库

demo:
从 Github 中获取一个库

  1. go get github.com/mattn/go-sqlite3

go get 获取远端的文件并把它们存储在工作区
$GOPATH/src/ 目录下, 会多一个 github.com 目录
导入

  1. import(
  2. "github.com/mattn/go-sqlite3"
  3. )
  4. // 这看起来像一个 URL,
  5. // 实际上,它只是希望导入在 $GOPATH/src/github.com/mattn/go-sqlite3 找到的 go-sqlite3 包。

[ 依赖管理 ]

go get 还有其他使用

  • 如果在一个项目内 使用 go get,它将浏览所有文件,然后查找 import 的第三方库然后下载他们

go 里面有两个保留的函数,

  • init函数 (能够应用于所有的 package )
  • main函数 (只能应用于package main)

这两个函数在 定义式不能有任何的参数和返回值。

go 程序会自动调用init()main()
所以你无需再任何地方调用 这两个函数

  • 每个 package 中的 init 函数都是可选的
  • 但是 package main 中必须包含一个 main 函数

程序的初始化和执行都起始于 main 包,如果 main 包还导入了其他包,就会在编译时将它们依次导入。
有时一个包会被多个包同时导入,那么它只会被导入一次(例如很多包可能都会用到fmt包,但它只会被导入一次,因为没有必要导入多次)。

当一个包被导入时, 如果该包还导入了 其它的包,那么会先将其他包 导入进来
然后再对这些包中的 包级常量和变量 进行初始化,
接着 执行 init()函数 (如果有的话) ,依次类推。

等所有被导入的包都加载完毕
就会开始对main包中的包级常量和变量进行初始化,然后执行main包中的init函数(如果存在的话),最后执
行main函数。下图详细地解释了整个执行过程:

image.png

main 函数和 main 包:

每个可执行的应用程序必须包含一个主函数。这个函数是执行的入口点。主函数应该存在main包中。

  1. // $GOPATH/src/test/test.go
  2. package main //指定这个 go文件属于 main 包
  3. import "fmt" // 用于导入现有的一个包名,当前导入的是包含Println方法的 fmt 包
  4. func main(){ // 主函数, 作为程序执行的入口
  5. fmt.Println("the first step")
  6. }

go 的 import 还支持以下两种方式来加载 自己写的模块

  • 1、相对路径
    • import "./model" // 当前文件同一目录的 model 目录,但是不建议这种方式 import
  • 2、绝对路径
    • import "test/model" //加载 $GOPATH/src/test/model 模块

特殊的 import 方式:
1、点操作

  1. import (
  2. . "fmt"
  3. )
  4. 这个点操作的含义就是:
  5. 这个包导入之后在你调用这个包的函数时, 可以省略前缀的包名,
  6. 也就是前面的 fmt.Println("hello world") 可以省略为 Println("hello world")

2、别名操作
别名操作顾名思义我们可以把包命名成另一个我们用起来容易记忆的名字

  1. import (
  2. f "fmt"
  3. )
  4. f.Println("hello world")

3、_ 操作

  1. import (
  2. "database/sql"
  3. _ "github.com/ziutek/mysql/godrv"
  4. )
  5. _ 操作实际上是 引入该包, 而不直接使用包里面的函数, 而是引入时候调用包里面的 init 函数

[ go mod 使用 ]

go 传统采用 go path方式管理, 需要手动管理依赖

常见的包管理工具

  • maven
  • pip
  • npm
  • gradle

先讨论下 go path 的问题:

  • 1、代码开发必须在 $GOPATH/src/ 目录下, 不然就有问题
  • 2、依赖需要手动管理
  • 3、依赖包没有版本管理可言 ( 很致命)

在 GOPATH 模式下,需要把应用代码固定在 $GOPATH/src/ 目录下
并且执行 go get 来拉取外部依赖会自动下载并安装到 $GOPATH 目录下

为什么弃用 GOPATH 模式 ?

在 GOAPTH 的 $GOPATH/src/ 下进行 .go 文件或源文件的存储,称之为 GOPATH 的模式
弃用原因:

GOPATH 模式没有版本控制的概念

  • 执行 go get 的时候,无法传达任何的版本信息的期望,即 无法通过指定来拉取自己所期望的具体版本
  • 运行 go 后才能续无法保证其他人和你的期望依赖三方库的版本一致
    • 即 无法保证所有人的 依赖版本都一致
    • 因为 GOPATH 模式下的导入路径都是一样的, 都是 github.com/xxx/xxx

go modules 基本使用:
首先将从头开始创建一个 go modules 项目

  • 原则上创建的目录 应该不要放在 $GOPATH 之中

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 verify 验证依赖是否正确
go mod why 查看为什么需要依赖某模块

比较常用的是 init、 tidy、 edit

[ go mod 使用 ]
1、初始化项目
随便找一个目录创建项目

  1. yufu@LAPTOP-MMCN9FEB MINGW64 /d/Env/golang/GOPATH
  2. $ pwd
  3. /d/Env/golang/GOPATH
  4. yufu@LAPTOP-MMCN9FEB MINGW64 /d/Env/golang/GOPATH
  5. $ ls
  6. bin/ pkg/ src/
  7. yufu@LAPTOP-MMCN9FEB MINGW64 /d/Env/golang/GOPATH
  8. $ cd src/ && mkdir zhang01 && cd zhang01
  9. yufu@LAPTOP-MMCN9FEB MINGW64 /d/Env/golang/GOPATH/src/zhang01
  10. $ go mod init zhang01
  11. go: creating new go.mod: module zhang01
  12. yufu@LAPTOP-MMCN9FEB MINGW64 /d/Env/golang/GOPATH/src/zhang01
  13. $ cat go.mod
  14. module zhang01
  15. go 1.16

go,mod 文件一旦创建, 它的内容就会被 go toolchain 全面掌控
go toolchain 会在各类命令执行时,
比如go get、go build、go mod等修改和维护go.mod文件

go.mod 文件 提供了module, require、replace和exclude 四个命令

  • module: 指定包的名字(路径)
  • require: 指定的依赖项模块
  • replace: 替换依赖项模块
  • exclude: 忽略依赖项模块

创建 main.go 文件

  1. package main
  2. import(
  3. "github.com/gin-gonic/gin" // 引入了github的 一个相关包, 需要 go get 下载
  4. )
  5. func main(){
  6. r := gin.Default()
  7. r.GET("/ping", func(c *gin.Context) {
  8. c.JSON(200, gin.H{
  9. "message": "pong",
  10. })
  11. })
  12. r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")
  13. }
  1. yufu@LAPTOP-MMCN9FEB MINGW64 /d/Env/golang/GOPATH/src/zhang01
  2. $ go run main.go
  3. main.go:5:5: no required module provides package github.com/gin-gonic/gin; to add it:
  4. go get github.com/gin-gonic/gin
  5. yufu@LAPTOP-MMCN9FEB MINGW64 /d/Env/golang/GOPATH/src/zhang01
  6. $ go get github.com/gin-gonic/gin
  7. go: downloading github.com/gin-gonic/gin v1.7.2
  8. ---
  9. yufu@LAPTOP-MMCN9FEB MINGW64 /d/Env/golang/GOPATH/src/zhang01
  10. $ go run main.go
  11. [GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
  12. [GIN-debug] GET /ping --> main.main.func1 (3 handlers)
  13. [GIN-debug] Environment variable PORT is undefined. Using port :8080 by default
  14. [GIN-debug] Listening and serving HTTP on :8080
  15. yufu@LAPTOP-MMCN9FEB MINGW64 /d/Env/golang/GOPATH/src/zhang01
  16. $ cat go.mod
  17. module zhang01
  18. go 1.16
  19. require github.com/gin-gonic/gin v1.7.2 // indirect
  20. yufu@LAPTOP-MMCN9FEB MINGW64 /d/Env/golang/GOPATH/src/zhang01
  21. $

说明:
go module 安装 package 的原則是先拉最新的 release tag,若无tag则拉最新的commit
go 会自动生成一个 go.sum 文件来记录 dependency tree (依赖关系树 )

go list -m -u all 命令检查可以升级的 package
go get -u 升级所有依赖
缓存包在 pkg/ 目录下
image.png

使用 go get 进行升级:

  • go get -u // 会升级到最新的次要版本 或者 修订版本 (x.y.z, z是修订版本号, y是次要版本号)
  • go get -u=patch // 将会升级到最新的修订版本
  • go get package@version //将会升级到指定的 版本号version

使用 replace 替换无法直接获取的 package
由于某些已知原因,并不是所有的 package 都可成功下载, 比如 golang.org 下的包

modules 可以通过在 go.mod 文件中使用 replace 指令替换成github上对应的库,比如:

  1. replace (
  2. golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a => github.com/golang/crypto v0.0.0-20190313024323-a1f597ede03a
  3. )

创建模块:

设置好 go mod 之后可以在任何目录下随便创建:

所提供的环境变量

go modules 中有如下环境变量

$ go env // 命令查看

  1. yufu@LAPTOP-MMCN9FEB MINGW64 ~/Desktop
  2. $ go env
  3. set GO111MODULE=
  4. set GOARCH=amd64
  5. set GOBIN=D:\Env\golang\Go\bin
  6. set GOCACHE=C:\Users\34348\AppData\Local\go-build
  7. set GOENV=C:\Users\34348\AppData\Roaming\go\env
  8. set GOEXE=.exe
  9. set GOFLAGS=
  10. set GOHOSTARCH=amd64
  11. set GOHOSTOS=windows
  12. set GOINSECURE=
  13. set GOMODCACHE=D:\Env\golang\GOPATH\pkg\mod
  14. set GONOPROXY=
  15. set GONOSUMDB=
  16. set GOOS=windows
  17. set GOPATH=D:\Env\golang\GOPATH
  18. set GOPRIVATE=
  19. set GOPROXY=https://proxy.golang.org,direct
  20. set GOROOT=D:\Env\golang\Go
  21. set GOSUMDB=sum.golang.org
  22. set GOTMPDIR=
  23. set GOTOOLDIR=D:\Env\golang\Go\pkg\tool\windows_amd64
  24. set GOVCS=
  25. set GOVERSION=go1.16.5
  26. set GCCGO=gccgo
  27. set AR=ar
  28. set CC=gcc
  29. set CXX=g++
  30. set CGO_ENABLED=1
  31. set GOMOD=NUL
  32. set CGO_CFLAGS=-g -O2
  33. set CGO_CPPFLAGS=
  34. set CGO_CXXFLAGS=-g -O2
  35. set CGO_FFLAGS=-g -O2
  36. set CGO_LDFLAGS=-g -O2
  37. set PKG_CONFIG=pkg-config
  38. set GOGCCFLAGS=-m64 -mthreads -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=C:\Users\34348\AppData\Local\Temp\go-build1795380983=/tmp/go-build -gno-record-gcc-switches

go 提供了GO111MODULE 这个环境变量来作为 go modules 的开关

  • auto:只要项目包含了 go.mod 文件的话启用 Go modules
  • on:启用 go modules, 推荐设置
  • off:禁用

win 下:
image.png

GOPROXY:
该环境变量主要设置 go 模块代理

  • 用于 go 可以直接通过镜像站点来快速拉取

GOPROXY 的默认值:https://proxy.golang.org,direct
国内无法访问
所以在开启 go modules 时同时设置国内的 go 模块代理

  1. yufu@LAPTOP-MMCN9FEB MINGW64 ~/Desktop
  2. $ go env -w GOPROXY=https://goproxy.cn,direct
  3. go env: reading go env config: read C:\Users\34348\AppData\Roaming\go\env: The handle is invalid.
  4. // 不可用, windows 下还是通过环境变量设置吧

GOPROXY的值是一个以英文逗号 “,” 分割的 Go 模块代理列表,允许设置多个模块代理
image.png

  1. yufu@LAPTOP-MMCN9FEB MINGW64 ~/Desktop
  2. $ go env
  3. set GO111MODULE=on
  4. set GOARCH=amd64
  5. set GOBIN=D:\Env\golang\Go\bin
  6. set GOCACHE=C:\Users\34348\AppData\Local\go-build
  7. set GOENV=C:\Users\34348\AppData\Roaming\go\env
  8. set GOEXE=.exe
  9. set GOHOSTARCH=amd64
  10. set GOHOSTOS=windows
  11. set GOMODCACHE=D:\Env\golang\GOPATH\pkg\mod
  12. set GOPROXY=https://proxy.golang.org,direct

网址列表值中有 direct 标识:

direct 是一个特殊指示符,用于指示 go 回源到模块版本的源地址去抓取,比如 Github 等 场景: 当值列表 上一个 go 模块代理返回 404 或者 410 错误时, go自动尝试列表中的下一个

GOSUMDB:
它的值是一个 go checksum database
用于在拉取模块时(无论是从源站拉取还是 通过go module proxy 代理拉取)
保证拉取到的模块版本数据都未经过篡改, 如果发现不一致,就会立即终止

GOSUMDB 的默认值是: sum.golang.org

  • 经典的国内无法访问

但是 GPSUMDB 可以被 Go 的模块代理所代理

先前所设置的模块代理 goproxy.cn 就能支持代理 sum.golang.org,

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"
同样的 windows 下通过环境变量设置

image.png
假设私有仓库是 git.example.com,github.com/eddycjy/mquote
则所有模块路径为 example.com 的子域名

演示:
初始化项目

创建一个示例项目演示:

  1. yufu@LAPTOP-MMCN9FEB MINGW64 /d/Env/golang
  2. $ mkdir -p modules/test01
  3. yufu@LAPTOP-MMCN9FEB MINGW64 /d/Env/golang
  4. $ cd modules/test01/
  5. // 然后进行 go modules 的 初始化
  6. yufu@LAPTOP-MMCN9FEB MINGW64 /d/Env/golang/modules/test01
  7. $ go mod init github.com/laoyufu1/module1
  8. go: creating new go.mod: module github.com/laoyufu1/module1
  9. yufu@LAPTOP-MMCN9FEB MINGW64 /d/Env/golang/modules/test01
  10. $ ls
  11. go.mod
  12. yufu@LAPTOP-MMCN9FEB MINGW64 /d/Env/golang/modules/test01
  13. $ cat go.mod
  14. module github.com/laoyufu1/module1
  15. go 1.16
  16. yufu@LAPTOP-MMCN9FEB MINGW64 /d/Env/golang/modules/test01
  17. $

go mod init 时指定了模块的导入路径为 github.com/laoyufu1/module1

2、接口
接口是 定义了但是并没有实现的类型

  1. type Logger interface{
  2. Log(message string)
  3. }

作用:
接口有助于 将代码与特定的实现进行分离
例如,我们可能有各种类型的日志记录器

  1. type SqlLogger struct{ ... }
  2. type ConsoleLogger struct { ... }
  3. type FileLogger struct { ... }

针对接口而不是具体实现的编程会使我们很轻松的修改(或者测试)任何代码都不会产生影响。
比如:

  1. type Server struct{
  2. logger Logger
  3. }

go mod:
说明:go modules 是 golang 1.11 新加的特性

模块是 相关go包的集合
modules 是源代码交换和 版本控制的单元
go 命令支持 直接使用 modules, 包括记录和解析对其他模块的依赖性

使用 go mod:
必须使得 go 升级到 >=1.11

查看版本信息:

  1. yufu@LAPTOP-MMCN9FEB MINGW64 ~/Desktop
  2. $ go version
  3. go version go1.16.5 windows/amd64

设置 go mod 和 go proxy

  1. yufu@LAPTOP-MMCN9FEB MINGW64 ~/Desktop
  2. $ go env
  3. set GO111MODULE=
  4. set GOARCH=amd64
  5. set GOBIN=D:\Env\golang\Go\bin
  6. set GOCACHE=C:\Users\34348\AppData\Local\go-build
  7. set GOENV=C:\Users\34348\AppData\Roaming\go\env

配置环境变量 GOENV 修改为

image.png
image.png
测试:

  1. yufu@LAPTOP-MMCN9FEB MINGW64 ~/Desktop
  2. $ go env
  3. set GO111MODULE=
  4. set GOARCH=amd64
  5. set GOBIN=D:\Env\golang\Go\bin
  6. set GOCACHE=C:\Users\34348\AppData\Local\go-build
  7. set GOENV=D:\Env\golang\goenv

go env -w 命令会将配置写到 GOENV=D:\Env\golang\goenv

关于 goproxy :
简单来说就是一个代理, 更方便的下载 由于墙的原因导致无法下载的 第三方包
比如golang.org/x/ 下的包

虽然也有各种方法解决,
但是,如果是你在拉取第三方包的时候,而这个包又依赖于golang.org/x/下的包,
你本地又恰恰没有,当然不嫌麻烦的话,也可以先拉取golang.org/x/下的包,再拉取第三方包。

proxy.golang.org 在中国无法访问,故而建议使用 goproxy.cn 作为替代。

如果没有配置 gobin
image.png
image.png

  1. go env -w GOBIN=D:\Env\golang\Go\bin

go env 命令:
功能:输出 go 环境有关的信息
go env -w GO111MODULE=on

go env -w xxx // 设置 env

环境设置:
linux 下:

  1. export GO111MODULE=on
  2. export GOPROXY=https://goproxy.io // 设置代理
  3. 但是不好地方在于, 如果换一个 shell终端或者重新开机, 配置就掉了
  4. 推荐写入配置文件 $HOME/.bashrc

windows 下: