组织代码
项目结构:
默认情况下 go 的项目源代码存放在 GOPATH/src 目录下
推荐的项目组织:
个人开发
团队协作开发:
企业开发:
5、函数
定义:组织好的、可重复使用的、用于执行指定任务的 代码块。
go 中使用func
关键字定义函数
func 函数名(参数) 返回值{
函数体
}
1、包管理
- 为了组织复杂的库 和 系统代码, 引入包的知识
go 语言中,包名遵循 go 项目的目录结构
demo
建立了一个购物系统, 可以使用 “shopping” 包名作为一个开始
然后把所有的源代码文件放到 $GOPATH/src/shopping/
目录中
但是由于代码众多, 可能不想把所有东西都放在这个文件夹下 ~
- 可能想单独把 数据库逻辑放在一个单独的目录
为了实现:
创建了一个子目录 $GOPATH/src/shopping/db/
,
- 子目录中文件的包名就是 db
- 但是为了从另一个包访问它, 包括 shopping 包,需要导入
shopping/db
// 目录结构
$GOPATH/src/shopping/
/db/
db.go
pricecheck.go
/main/
main.go 程序入口
// db.go
package db
type Item struct{
Price float64
}
func LoadItem(id int) *Item {
return &Item{
Price: 9.10,
}
}
// 包名 和 文件夹名是一样的,package name 就是所在的文件夹的名字
// pricecheck.go
package shopping
import(
"shoppping/db"
)
func PriceCheck(itemId int)(float64, bool) {
item := db.LoadItem( itemId )
if item == nil{
return 0,false
}
return item.Price, true
}
// main.go
package main
import (
"shopping"
"fmt"
)
func main() {
fmt.Println(shopping.PriceCheck(4343))
}
然后在 shopping/ 目录
go run main/mian.go
[ 循环导入 ]
编写更复杂系统时,会遇到 循环导入
demo:
A 包导入 B 包
B 包又导入 A 包 ( 间接或直接导入 ) 编译器所不允许
demo//
目录结构:
复现 循环导入包 的错误代码:
$GOPATH/src/shopping/
/db/
db.go
pricecheck.go
/main/
main.go 程序入口
//-----------------------------------------------------------------------------------//
// shopping/pricecheck.go
package shopping
import(
"shoppping/db"
)
type Item struct {
Price float64
}
func PriceCheck(itemId int)(float64, bool) {
item := db.LoadItem( itemId )
if item == nil{
return 0,false
}
return item.Price, true
}
//-----------------------------------------------------------------------------------//
// shopping/db/db.go
package db
import "shopping" //因为当前的 Item 定义在 shopping/pricecheck.go
func LoadItem(id int) *shopping.Item {
return &shopping.Item{
Price: 9.10,
}
}
//-----------------------------------------------------------------------------------//
// shopping/main/main.go
package main
import "shopping"
func main(){
shopping.PriceCheck(344)
}
原因是什么?
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 , 所以循环导入报错 //
如何 修正 ?
引入另外一个 包含共享结构体的 包来解决问题:
models/item.go 的内容:
package models
type Item struct{
Price float64
}
[ 关于可见性 ]
go 中对于可见性的简单规则:
- 类型 或者函数名称 以一个大写字母开始, 就具有包外可见性
- 以小写字母开始, 就不具有
- 也可以应用到结构体字段。如果一个字段名以一个小写字母开始,只有包内的代码可以访问他们。
[ 包管理 ]
go 除了 build、run 还有一个go get
命令,用于获取第三方库
demo:
从 Github 中获取一个库
go get github.com/mattn/go-sqlite3
go get 获取远端的文件并把它们存储在工作区$GOPATH/src/
目录下, 会多一个 github.com
目录
导入
import(
"github.com/mattn/go-sqlite3"
)
// 这看起来像一个 URL,
// 实际上,它只是希望导入在 $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函数。下图详细地解释了整个执行过程:
main 函数和 main 包:
每个可执行的应用程序必须包含一个主函数。这个函数是执行的入口点。主函数应该存在main包中。
// $GOPATH/src/test/test.go
package main //指定这个 go文件属于 main 包
import "fmt" // 用于导入现有的一个包名,当前导入的是包含Println方法的 fmt 包
func main(){ // 主函数, 作为程序执行的入口
fmt.Println("the first step")
}
go 的 import 还支持以下两种方式来加载 自己写的模块:
- 1、相对路径
import "./model"
// 当前文件同一目录的model
目录,但是不建议这种方式 import
- 2、绝对路径
import "test/model"
//加载 $GOPATH/src/test/model
模块
特殊的 import 方式:
1、点操作
import (
. "fmt"
)
这个点操作的含义就是:
这个包导入之后在你调用这个包的函数时, 可以省略前缀的包名,
也就是前面的 fmt.Println("hello world") 可以省略为 Println("hello world")
2、别名操作
别名操作顾名思义我们可以把包命名成另一个我们用起来容易记忆的名字
import (
f "fmt"
)
f.Println("hello world")
3、_ 操作
import (
"database/sql"
_ "github.com/ziutek/mysql/godrv"
)
_ 操作实际上是 引入该包, 而不直接使用包里面的函数, 而是引入时候调用包里面的 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、初始化项目
随便找一个目录创建项目
yufu@LAPTOP-MMCN9FEB MINGW64 /d/Env/golang/GOPATH
$ pwd
/d/Env/golang/GOPATH
yufu@LAPTOP-MMCN9FEB MINGW64 /d/Env/golang/GOPATH
$ ls
bin/ pkg/ src/
yufu@LAPTOP-MMCN9FEB MINGW64 /d/Env/golang/GOPATH
$ cd src/ && mkdir zhang01 && cd zhang01
yufu@LAPTOP-MMCN9FEB MINGW64 /d/Env/golang/GOPATH/src/zhang01
$ go mod init zhang01
go: creating new go.mod: module zhang01
yufu@LAPTOP-MMCN9FEB MINGW64 /d/Env/golang/GOPATH/src/zhang01
$ cat go.mod
module zhang01
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 文件
package main
import(
"github.com/gin-gonic/gin" // 引入了github的 一个相关包, 需要 go get 下载
)
func main(){
r := gin.Default()
r.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong",
})
})
r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")
}
yufu@LAPTOP-MMCN9FEB MINGW64 /d/Env/golang/GOPATH/src/zhang01
$ go run main.go
main.go:5:5: no required module provides package github.com/gin-gonic/gin; to add it:
go get github.com/gin-gonic/gin
yufu@LAPTOP-MMCN9FEB MINGW64 /d/Env/golang/GOPATH/src/zhang01
$ go get github.com/gin-gonic/gin
go: downloading github.com/gin-gonic/gin v1.7.2
---
yufu@LAPTOP-MMCN9FEB MINGW64 /d/Env/golang/GOPATH/src/zhang01
$ go run main.go
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
[GIN-debug] GET /ping --> main.main.func1 (3 handlers)
[GIN-debug] Environment variable PORT is undefined. Using port :8080 by default
[GIN-debug] Listening and serving HTTP on :8080
yufu@LAPTOP-MMCN9FEB MINGW64 /d/Env/golang/GOPATH/src/zhang01
$ cat go.mod
module zhang01
go 1.16
require github.com/gin-gonic/gin v1.7.2 // indirect
yufu@LAPTOP-MMCN9FEB MINGW64 /d/Env/golang/GOPATH/src/zhang01
$
说明:
go module 安装 package 的原則是先拉最新的 release tag,若无tag则拉最新的commit
go 会自动生成一个 go.sum 文件来记录 dependency tree (依赖关系树 )
go list -m -u all
命令检查可以升级的 packagego get -u
升级所有依赖
缓存包在 pkg/ 目录下
使用 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上对应的库,比如:
replace (
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a => github.com/golang/crypto v0.0.0-20190313024323-a1f597ede03a
)
创建模块:
设置好 go mod 之后可以在任何目录下随便创建:
所提供的环境变量
go modules 中有如下环境变量
$ go env
// 命令查看
yufu@LAPTOP-MMCN9FEB MINGW64 ~/Desktop
$ go env
set GO111MODULE=
set GOARCH=amd64
set GOBIN=D:\Env\golang\Go\bin
set GOCACHE=C:\Users\34348\AppData\Local\go-build
set GOENV=C:\Users\34348\AppData\Roaming\go\env
set GOEXE=.exe
set GOFLAGS=
set GOHOSTARCH=amd64
set GOHOSTOS=windows
set GOINSECURE=
set GOMODCACHE=D:\Env\golang\GOPATH\pkg\mod
set GONOPROXY=
set GONOSUMDB=
set GOOS=windows
set GOPATH=D:\Env\golang\GOPATH
set GOPRIVATE=
set GOPROXY=https://proxy.golang.org,direct
set GOROOT=D:\Env\golang\Go
set GOSUMDB=sum.golang.org
set GOTMPDIR=
set GOTOOLDIR=D:\Env\golang\Go\pkg\tool\windows_amd64
set GOVCS=
set GOVERSION=go1.16.5
set GCCGO=gccgo
set AR=ar
set CC=gcc
set CXX=g++
set CGO_ENABLED=1
set GOMOD=NUL
set CGO_CFLAGS=-g -O2
set CGO_CPPFLAGS=
set CGO_CXXFLAGS=-g -O2
set CGO_FFLAGS=-g -O2
set CGO_LDFLAGS=-g -O2
set PKG_CONFIG=pkg-config
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 下:
GOPROXY:
该环境变量主要设置 go 模块代理
- 用于 go 可以直接通过镜像站点来快速拉取
GOPROXY 的默认值:https://proxy.golang.org,direct
国内无法访问
所以在开启 go modules 时同时设置国内的 go 模块代理
yufu@LAPTOP-MMCN9FEB MINGW64 ~/Desktop
$ go env -w GOPROXY=https://goproxy.cn,direct
go env: reading go env config: read C:\Users\34348\AppData\Roaming\go\env: The handle is invalid.
// 不可用, windows 下还是通过环境变量设置吧
GOPROXY的值是一个以英文逗号 “,” 分割的 Go 模块代理列表,允许设置多个模块代理
yufu@LAPTOP-MMCN9FEB MINGW64 ~/Desktop
$ go env
set GO111MODULE=on
set GOARCH=amd64
set GOBIN=D:\Env\golang\Go\bin
set GOCACHE=C:\Users\34348\AppData\Local\go-build
set GOENV=C:\Users\34348\AppData\Roaming\go\env
set GOEXE=.exe
set GOHOSTARCH=amd64
set GOHOSTOS=windows
set GOMODCACHE=D:\Env\golang\GOPATH\pkg\mod
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 下通过环境变量设置
假设私有仓库是 git.example.com,github.com/eddycjy/mquote
则所有模块路径为 example.com 的子域名
演示:
初始化项目
创建一个示例项目演示:
yufu@LAPTOP-MMCN9FEB MINGW64 /d/Env/golang
$ mkdir -p modules/test01
yufu@LAPTOP-MMCN9FEB MINGW64 /d/Env/golang
$ cd modules/test01/
// 然后进行 go modules 的 初始化
yufu@LAPTOP-MMCN9FEB MINGW64 /d/Env/golang/modules/test01
$ go mod init github.com/laoyufu1/module1
go: creating new go.mod: module github.com/laoyufu1/module1
yufu@LAPTOP-MMCN9FEB MINGW64 /d/Env/golang/modules/test01
$ ls
go.mod
yufu@LAPTOP-MMCN9FEB MINGW64 /d/Env/golang/modules/test01
$ cat go.mod
module github.com/laoyufu1/module1
go 1.16
yufu@LAPTOP-MMCN9FEB MINGW64 /d/Env/golang/modules/test01
$
go mod init 时指定了模块的导入路径为 github.com/laoyufu1/module1
2、接口
接口是 定义了但是并没有实现的类型
type Logger interface{
Log(message string)
}
作用:
接口有助于 将代码与特定的实现进行分离
例如,我们可能有各种类型的日志记录器
type SqlLogger struct{ ... }
type ConsoleLogger struct { ... }
type FileLogger struct { ... }
针对接口而不是具体实现的编程会使我们很轻松的修改(或者测试)任何代码都不会产生影响。
比如:
type Server struct{
logger Logger
}
go mod:
说明:go modules 是 golang 1.11 新加的特性
模块是 相关go包的集合
modules 是源代码交换和 版本控制的单元
go 命令支持 直接使用 modules, 包括记录和解析对其他模块的依赖性
使用 go mod:
必须使得 go 升级到 >=1.11
查看版本信息:
yufu@LAPTOP-MMCN9FEB MINGW64 ~/Desktop
$ go version
go version go1.16.5 windows/amd64
设置 go mod 和 go proxy
yufu@LAPTOP-MMCN9FEB MINGW64 ~/Desktop
$ go env
set GO111MODULE=
set GOARCH=amd64
set GOBIN=D:\Env\golang\Go\bin
set GOCACHE=C:\Users\34348\AppData\Local\go-build
set GOENV=C:\Users\34348\AppData\Roaming\go\env
配置环境变量 GOENV 修改为
测试:
yufu@LAPTOP-MMCN9FEB MINGW64 ~/Desktop
$ go env
set GO111MODULE=
set GOARCH=amd64
set GOBIN=D:\Env\golang\Go\bin
set GOCACHE=C:\Users\34348\AppData\Local\go-build
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
go env -w GOBIN=D:\Env\golang\Go\bin
go env 命令:
功能:输出 go 环境有关的信息go env -w GO111MODULE=on
go env -w xxx // 设置 env
环境设置:
linux 下:
export GO111MODULE=on
export GOPROXY=https://goproxy.io // 设置代理
但是不好地方在于, 如果换一个 shell终端或者重新开机, 配置就掉了
推荐写入配置文件 $HOME/.bashrc
windows 下: