安装go
Go官网下载地址:https://golang.org/dl/
中国区官方镜像站(推荐):https://golang.google.cn/dl/
windows安装
下载好后选择安装路径即可,安装完成后,win+r 输入cmd调出命令行窗口,输入
go version
# 回车后看到版本信息,就算安装成功了
go version go1.24.4 windows/amd64
Linux安装
如果要在linux系统下开发,就需要配置linux的开发环境。如果只需要在linux上运行打包好的程序的话,是不需要安装go环境的。因为,编译后的go,是可以跨平台运行的。
在版本选择页面选择并下载好go1.24.4.linux-amd64.tar.gz
文件:
wget https://dl.google.com/go/go1.24.4.linux-amd64.tar.gz
文件解压到/usr/local目录下:
tar -zxvf go1.24.4.linux-amd64.tar.gz -C /usr/local # 解压
需要加上sudo以root用户的身份再运行。执行完就可以在/usr/local/下看到go目录了。
配置环境变量: Linux下有两个文件可以配置环境变量,其中/etc/profile
是对所有用户生效的;$HOME/.profile
是对当前用户生效的,根据自己的情况自行选择一个文件打开,添加如下两行代码,保存退出。
export GOROOT=/usr/local/go
export PATH=$PATH:$GOROOT/bin
修改/etc/profile
后要重启生效,修改$HOME/.profile
后使用source
命令加载HOME/.profile
文件即可生效。 检查:
~ go version
go version go1.23.1 linux/amd64
Mac下安装
mac下安装go,与windows基本相同,不再赘述了。
安装过程执行完毕后,可以打开终端窗口,输入go version命令,查看安装的Go版本。
环境变量
GOROOT和GOPATH都是环境变量,其中GOROOT是我们安装go开发包的路径
从Go 1.8版本开始,Go开发包在安装完成后,会为GOPATH设置一个默认目录,并且在Go1.14及之后的版本中启用了Go Module模式之后,不一定非要将代码写到GOPATH目录下,所以也就不需要我们再自己配置GOPATH了,使用默认的即可。
# 查看gopath
go env
代理
默认配置是:
GOPROXY=https://proxy.golang.org,direct
国内是访问不到的,我们要修改代理配置->https://goproxy.io
可以执行下面的命令修改GOPROXY:
go env -w GOPROXY=https://goproxy.cn,direct
go env -w GOPROXY=https://mirrors.cloud.tencent.com/go/,direct
开发工具
vscode
下载地址:https://code.visualstudio.com/Download
安装go插件
插件商店搜索Rich Go language support for Visual Studio Code
Windows平台按下Ctrl+Shift+P
,Mac平台按Command+Shift+P
,这个时候VS Code界面会弹出一个输入框,输入以下指令
Go:Install/Update Tools
勾选全部工具,确定即可
编译运行
如果之前没有go项目文件夹,就新建文件夹==>vscode打开
将终端调到cmd终端,不使用powerShell终端,为什么不使用powerShell终端,是因为运行时的麻烦问题,例如,如果我们使用powerShell终端,运行 exe文件时要这么写:.\gohello.exe
通过go mod init 项目名
命令对项目进行初始化,该命令会在项目根目录下生成go.mod文件。例如,我们使用gohello作为我们第一个Go项目的名称,执行如下命令。
go mod init gohello
创建一个main.go
文件:
如果此时VS Code右下角弹出提示让你安装插件,务必点 install all 进行安装。
- 在 Go 语言中,只有首字母为大写的标识符才是导出的(Exported),才能对包外的代码可见;
- 如果首字母是小写的,那么就说明这个标识符仅限于在声明它的包内可见。
- 另外,在 Go 语言中,main 包是不可以像标准库 fmt 包那样被导入(Import)的,如果导入 main 包,在代码编译阶段你会收到一个 Go 编译器错误:import “xx/main” is a program, not an importable package。
package main // 声明 main 包,表明当前是一个可执行程序,Go 语言中使用包来组织代码。一般一个文件夹即一个包
import "fmt" // 导入内置 fmt 包,类似java中的system包
func main(){ // main函数,是程序执行的入口
fmt.Println("Go Hello !") // 在终端打印 Go Hello ! (所见即所得)
}
运行项目
go run main.go
gobuild
go build main.go
命令行输入以下命令,同样输出Go Hello !
main.exe
默认我们go build的可执行文件都是当前操作系统可执行的文件,Go语言支持跨平台编译——在当前平台(例如Windows)下编译其他平台(例如Linux)的可执行文件
Windows编译Linux可执行文件
在Windows下编译一个Linux下可执行文件,那需要怎么做呢?只需要在编译时指定目标操作系统的平台和处理器架构即可。
如果你的Windows使用的是cmd,那么按如下方式指定环境变量。
SET CGO_ENABLED=0 // 禁用CGO
SET GOOS=linux // 目标平台是linux
SET GOARCH=amd64 // 目标处理器架构是amd64
如果你的Windows使用的是PowerShell终端,那么设置环境变量的语法为
$ENV:CGO_ENABLED=0
$ENV:GOOS="linux"
$ENV:GOARCH="amd64"
在你的Windows终端下执行完上述命令后,再执行下面的命令,得到的就是能够在Linux平台运行的可执行文件了。
go build
依赖管理
添加依赖
当你运行go get来添加新的依赖或者更新现有依赖时,这些依赖会被自动添加到go.mod
文件中。例如
go get github.com/tools/godep
更新依赖
运行以下命令来更新所有依赖到最新的兼容版本:
go get ./...
管理依赖
go.mod
文件会记录项目的依赖和对应的版本。你可以手动编辑go.mod文件来指定依赖版本,但不推荐这样做,除非你需要引入特定的bug修复版本或者必须使用某个未发布的版本。
下载依赖
比如当你从代码仓库下载某一个新项目时,运行以下命令来下载项目所有依赖到本地缓存:
go mod download
查看当前项目的依赖
go list -m all
处理依赖冲突
Go Modules会自动处理依赖版本冲突。如果发生冲突,它会选择一个最新的兼容版本。如果需要,你可以手动解决冲突。
Web服务实战
Go 应用的前 4 个领域中,有两个都是 Web 服务相关的。一个是排在第一位的 API/RPC
服务,另一个是排在第四位的 Web
服务(返回 html 页面)。考虑到后续把 Go 应用于 Web 服务领域的机会比较大,所以,这里我们就选择一个 Web 服务项目作为实战小项目。
HTTP服务
先来给你演示一下在 Go 中创建一个基于 HTTP 协议的 Web 服务是多么的简单。
首先按下面步骤建立一个 simple-http-server
Go Module
go mod init simple-http-server
创建一个 main.go 源文件
package main
import "net/http"
func main() {
//注册一个处理函数,当用户访问网站根路径 / 时触发
//(响应对象w:用于向客户端发送响应数据,请求对象r:包含客户端发来的所有请求信息(如 URL、Header、Body 等))
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request){
w.Write([]byte("hello, world"))
})
//启动 HTTP 服务器,监听本机的 8080 端口。
http.ListenAndServe(":8080", nil) //使用默认的多路复用器
}
当你运行这个程序后,在浏览器地址栏输入:http://localhost:8080/
你将会看到页面上显示:hello, world
图书管理 API 服务
首先,我们先来明确一下我们的业务逻辑。
在这个实战小项目中,我们模拟的是真实世界的一个书店的图书管理后端服务。这个服务为平台前端以及其他客户端,提供针对图书的 CRUD(创建、检索、更新与删除)的基于 HTTP 协议的 API。API 采用典型的 RESTful 风格设计,这个服务提供的 API 集合如下:
布局设计
我们按照下面步骤创建一个名为 bookstore 的 Go 项目并创建对应的 Go Module:
go mod init bookstore
通过上面的业务逻辑说明,可以把这个服务大体拆分为两大部分
- 一部分是 HTTP 服务器,用来对外提供 API 服务;
- 另一部分是图书数据的存储模块,所有的图书数据均存储在这里。
我们参考 Go 项目布局标准中的项目布局,把这个项目的结构布局设计成这样:
├── cmd/
│ └── bookstore/ // 放置bookstore main包源码
│ └── main.go
├── go.mod // module bookstore的go.mod
├── go.sum
├── internal/ // 存放项目内部包的目录
│ └── store/
│ └── memstore.go
├── server/ // HTTP服务器模块
│ ├── middleware/
│ │ └── middleware.go
│ └── server.go
└── store/ // 图书数据存储模块
├── factory/
│ └── factory.go
└── store.go
main
main 包不仅包含了整个程序的入口,它还是整个程序中主要模块初始化与组装的场所。
package main
import (
_ "bookstore/internal/store" //只运行这个包的 init() 函数,不直接使用它的导出内容
"bookstore/server"
"bookstore/store/factory"
"context"
"log"
"os" //处理系统信号,比如 Ctrl+C
"os/signal"
"syscall"
"time" //用于设置超时时间
)
func main() {
s, err := factory.New("mem") // 创建图书数据存储模块实例
if err != nil {
panic(err) //如果失败,则 panic 终止程序
}
srv := server.NewBookStoreServer(":8080", s) // 创建http服务实例
errChan, err := srv.ListenAndServe() // 运行http服务
if err != nil {
log.Println("web server start failed:", err)
return
}
log.Println("web server start ok")
//创建一个通道 c,用于接收操作系统发送的中断信号(例如 Ctrl+C 或 kill 命令)
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGINT, syscall.SIGTERM) //监听信号
//使用 select 同时监听errChan以及c两个事件,实现优雅退出
select {
case err = <-errChan:
log.Println("web server run failed:", err)
return
case <-c:
log.Println("bookstore program is exiting...")
ctx, cf := context.WithTimeout(context.Background(), time.Second)
defer cf()
err = srv.Shutdown(ctx) // 优雅关闭http服务实例
}
if err != nil {
log.Println("bookstore program exit error:", err)
return
}
log.Println("bookstore program exit ok")
}
store
存储整个 bookstore 的图书数据。图书数据存储有很多种实现方式,最简单的方式莫过于在内存中创建一个 map,以图书 id 作为 key,来保存图书信息,我们在这一讲中也会采用这种方式。但如果我们要考虑上生产环境,数据要进行持久化,那么最实际的方式就是通过 Nosql 数据库甚至是关系型数据库,实现对图书数据的存储与管理。
package store
type Book struct {
Id string `json:"id"` // 图书ISBN ID
Name string `json:"name"` // 图书名称
Authors []string `json:"authors"` // 图书作者
Press string `json:"press"` // 出版社
}
type Store interface {
Create(*Book) error // 创建一个新图书条目
Update(*Book) error // 更新某图书条目
Get(string) (Book, error) // 获取某图书信息
GetAll() ([]Book, error) // 获取所有图书信息
Delete(string) error // 删除某图书条目
}
对于想要进行图书数据操作的一方来说,他只需要得到一个满足 Store 接口的实例,就可以实现对图书数据的存储操作了,不用再关心图书数据究竟采用了何种存储方式。这就实现了图书存储操作与底层图书数据存储方式的解耦。
store/factory
实现满足 Store 接口实例的创建。
package factory
// store/factory/factory.go
var (
providersMu sync.RWMutex
providers = make(map[string]store.Store)
)
func Register(name string, p store.Store) {
providersMu.Lock()
defer providersMu.Unlock()
if p == nil {
panic("store: Register provider is nil")
}
if _, dup := providers[name]; dup {
panic("store: Register called twice for provider " + name)
}
providers[name] = p
}
func New(providerName string) (store.Store, error) {
providersMu.RLock()
p, ok := providers[providerName]
providersMu.RUnlock()
if !ok {
return nil, fmt.Errorf("store: unknown provider %s", providerName)
}
return p, nil
}