mkdir helloworld
cd helloworld
使用go mod init 模块名来管理依赖,模块名随便起一个就行,目录下就会有一个go.mod文件
写了一个 hello.go文件后
go run .运行程序
go run会自动寻找程序入口,即 标记为package main的 go 文件的 main 函数入口
可以在 pkg.go.dev/找开源的包
当在程序中导入一个不是标准库的包时,使用go get或者go mod tidy下载到本地
go get单纯的下载到本地,go mod tidy将下载的依赖添加到go.mod文件
导入包的学习:
.
├── greetings
│ ├── go.mod
│ └── greetings.go
└── hello
├── go.mod
└── hello.go
目录如上,greetings.go 是一个模块的函数,注意 package greetings不是 main,只是模块用于被其他代码调用
package greetings
import"fmt"
// Hello returns a greeting for the named person.
func Hello(name string)string {
// Return a greeting that embeds the name in a message.
message:= fmt.Sprintf("Hi, %v. Welcome!",name)
return message
}
创建 hello 目录,并创建 hello.go,package main以及 main 函数,并调用greetings模块
package main
import(
"fmt"
"hndr01d/greetings"
)
func main() {
// Get a greeting message and print it.
message:= greetings.Hello("Gladys")
fmt.Println(message)
}
greetings 目录下的 go.mod 如下:
module hndr01d/greetings
go 1.23.3
hello 目录下的 go.mod如下
module hndr01d/hello
go 1.23.3
准备工作就绪,在 hello 目录下执行 go run .
发现报错: hello.go:6:5:package hndr01d/greetings is not in std(/usr/local/go/src/hndr01d/greetings)
因为 gopath 找不到 hndr01d/greetings模块
为此调用 go mod edit命令将 go 工具的寻找模块路径定位到 greetings目录,此命令实际上编辑了 go.mod 文件
go mod edit -replace hndr01d/greetings=../greetings
这条命令指定了应该将hndr01d/greetings替换为本地的../greetings ,执行完毕后可以看到hello目录下的go.mod文件中多出了一行
module hndr01d/hello
go 1.23.3
replace hndr01d/greetings => ../greetings
还没完,还要运行 go mod tidy命令以同步 hndr01d/hello模块的依赖关系,添加代码所需但尚未在模块中跟踪的依赖关系。
go mod tidy
该命令在greetings目录中找到了本地代码,然后添加了一个 require 指令,指定hndr01d/hello需要hndr01d/greetings。也就是创建了依赖关系。
执行完毕后发现 hello 的 go.mod又多出一行
module hndr01d/hello
go 1.23.3
replace hndr01d/greetings => ../greetings
require hndr01d/greetings v0.0.0-00010101000000-000000000000
指令后的 v0.0.0 ...是伪版本号,因为当前的 hndr01d/greetings模块没有设置任何版本
现在就可以调用 go run .来正确的运行程序了!
上面的**replace**只有在调用本地包时需要,在调用已发布的模块时,**go.mod****通常只会包含 ****require**指令来添加依赖关系(可以通过**go mod tidy**来自动添加)
greetings.go:
加入了错误处理、rand、slice、map、多参数的传入和返回
当前一个版本只有 hello 时,现在要更新版本,可以输出多个人的 greeting,若是直接在原函数上改,那么使用该包的其他开发者的程序会出错,做好的办法就是向下兼容,定义一个新的hellos函数,保留原本的 hello函数
package greetings
import(
"errors"
"fmt"
"math/rand"
)
// Hello returns a greeting for the named person.
func Hello(name string) (string,error) {
// If no name was given,return an error with a message.
if name == "" {
return name,errors.New("empty name")
}
// Create a message using a random format.
message:= fmt.Sprintf(randomFormat(),name)
return message,nil
}
// Hellos returns a map that associates each of the named people
// with a greeting message.
func Hellos(names []string) (map[string]string,error) {
// A map to associate names with messages.
messages:= make(map[string]string)
// Loop through the received slice of names,calling
// the Hello function to get a message for each name.
for _,name := range names {
message,err := Hello(name)
if err!= nil {
return nil,err
}
// In the map,associate the retrieved message with
// the name.
messages[name] = message
}
return messages,nil
}
// randomFormat returns one of a set of greeting messages. The returned
// message is selected at random.
func randomFormat()string {
// A slice of message formats.
formats:= []string{
"Hi, %v. Welcome!",
"Great to see you, %v!",
"Hail, %v!Well met!",
}
// Return one of the message formats selected at random.
return formats[rand.Intn(len(formats))]
}
hello.go:
下面的代码使用了 log 包,并设置错误输出前缀为 greetings:
package main
import(
"fmt"
"log"
"hndr01d/greetings"
)
func main() {
// Set properties of the predefined Logger,including
// the log entry prefix and a flag to disable printing
// the time,source file,and line number.
log.SetPrefix("greetings: ")
log.SetFlags(0)
// A slice of names.
names:= []string{"Gladys", "", "Darrin"}
// Request greeting messages for the names.
messages,err := greetings.Hellos(names)
if err!= nil {
log.Fatal(err)
}
// If no error was returned,print the returned map of
// messages to the console.
fmt.Println(messages)
}
log.Fatal会直接 exit,停止程序运行
go 内置了 go 单元测试
提供了testing包、go test命令
命名约定:文件名以_test.go 结尾,以此告诉 go 该文件包含测试函数
函数名以Test 开头,并且传入参数为t *testing.T ,即参数类型为*testing.T ,没有返回值
在函数中,发生错误err,使用t.Fatalf打印错误并结束程序
greetings_test.go文件
package greetings
import(
"testing"
"regexp"
)
// TestHelloName calls greetings.Hello with a name,checking
// for a valid return value.
func TestHelloName(t *testing.T) {
name:= "Gladys"
want:= regexp.MustCompile(`\b `+name+`\b `)
msg,err := Hello("Gladys")
if!want.MatchString(msg) || err!= nil {
t.Fatalf(` Hello("Gladys") = %q, %v,want match for %#q,nil `,msg,err,want)
}
}
// TestHelloEmpty calls greetings.Hello with an empty string,
// checking for an error.
func TestHelloEmpty(t *testing.T) {
msg,err := Hello("")
if msg!= "" || err == nil {
t.Fatalf(` Hello("") = %q, %v,want "",error `,msg,err)
}
}
使用 go test命令进行测试,参数-v 会返回详细信息
hndr01d@PC-202410191650:~/gopractice/greetings$ go test
PASS
ok hndr01d/greetings 0.001s
hndr01d@PC-202410191650:~/gopractice/greetings$ go test -v
=== RUN TestHelloName
--- PASS:TestHelloName (0.00s)
=== RUN TestHelloEmpty
--- PASS:TestHelloEmpty (0.00s)
PASS
ok hndr01d/greetings 0.002s
编译和下载:
go run用于方便的编译和执行 go 文件,但是并不会生成可执行文件
go build用于编译文件以及其依赖,并生成可执行文件,但并不会主动下载依赖包
go install编译并下载依赖包
go build并运行./hello
结果: map[Darrin:Hail,Darrin!Well met!Gladys:Hi,Gladys. Welcome!Samantha:Hi,Samantha. Welcome!]
然后只能在 hello 所在目录下运行该可执行文件
要在全局运行,可以加一个环境变量
首先查询 go install 的目录
go list -f'{{.Target}}'
显示结果:/home/hndr01d/go/bin/hello
可以将该路径加入到环境变量下
export PATH=$PATH:/home/hndr01d/go/bin
一旦添加了环境变量,那么使用go install就会对当前目录下的文件(package main,以及 main 函数)进行编译,并将可执行文件下载到设置的环境变量目录下。
然后便可以全局执行。
需要创建的 API 接口:
/albums :
GET:获得相册列表,以 JSON 返回POST:以 JSON 格式发送请求来添加一个新相册/albums/:id :
GET:通过相册 ID 来返回指定相册的 JSON创建web-service-gin目录并进入
创建依赖管理模块:go mod init hndr01d/web-service-gin
准备数据以及数据格式
//json 标签表示序列化为 json 后的字段名,不加标签,会使用结构体字段名本身
//一般 json 都是小写格式
type album struct {
ID string ` json:"id"`
Title string ` json:"title"`
Artist string ` json:"artist"`
Price float64 ` json:"price"`
}
// 定义数据
var albums = []album{
{ID: "1",Title: "Blue Train",Artist: "John Coltrane",Price:56.99},
{ID: "2",Title: "Jeru",Artist: "Gerry Mulligan",Price:17.99},
{ID: "3",Title: "Sarah Vaughan and Clifford Brown",Artist: "Sarah Vaughan",Price:39.99},
}
/albums需要编写:
getAlbums 函数从相册结构的切片中创建 JSON,并将 JSON 写入响应中。
func getAlbums(c *gin.Context) {
c.IndentedJSON(http.StatusOK,albums)
}
gin.Context类型是 Gin 中最重要的部分,携带了请求详细信息,以及用于验证和序列化 JSON 等
这里的 context 和 go 内置的 context 包不同
Context.IndentedJSON函数将结构体序列化为 JSON 并加入到响应
http.StatusOK表示状态码为 200 OK
处理路由:
func main() {
router:= gin.Default()
router.GET("/albums",getAlbums)
router.Run("localhost:8080")
}
对 /albums 路径的 GET 请求将交给 getAlbums 函数来处理
将上述代码都写在 main.go 文件中,并导入相应的包
package main
import(
"net/http"
"github.com/gin-gonic/gin"
)
处理对应的依赖:go mod tidy或 go get .
go run .启动服务器
打开另一个终端,使用 curl 来访问:curl http://localhost:8080/albums

/albums POST 逻辑处理函数:
从请求体中的 JSON 数据中,添加新的相册到列表
func postAlbums(c *gin.Context) {
var newAlbum album
// Call BindJSON to bind the received JSON to
// newAlbum.
if err:= c.BindJSON(&newAlbum);err != nil {
return
}
// 添加相册到列表
albums = append(albums,newAlbum)
c.IndentedJSON(http.StatusCreated,newAlbum)
}
Context.BindJSON方法将请求正文绑定到 newAlbum。
http.StatusCreated:在响应中添加 201 状态码,以及表示您添加的相册的 JSON。
在 main 函数中添加 POST 路由
当有 POST 请求/albums 时,由 postAlbums 函数来处理
func main() {
router:= gin.Default()
router.GET("/albums",getAlbums)
router.POST("/albums",postAlbums)
router.Run("localhost:8080")
}
保存并重新运行服务器
curl http://localhost:8080/albums \
--include \
--header"Content-Type:application/json" \
--request"POST" \
--data'{"id": "4","title": "The Modern Sound of Betty Carter","artist": "Betty Carter","price":49.99}'

/albums 的指定 id逻辑处理函数
func getAlbumByID(c *gin.Context) {
id:= c.Param("id")
// 查找是否有对应 id 的相册
for _,a := range albums {
if a.ID == id {
c.IndentedJSON(http.StatusOK,a)
return
}
}
c.IndentedJSON(http.StatusNotFound,gin.H{"message": "album not found"})
}
Context.Param用于从 URL 检索 id 路径参数。当您将此处理程序映射到路径时,将在路径中包含该参数的占位符。
找到了返回数据以及 200 ok 状态码
http.StatusNotFound没找到则返回 404 Not Found
添加路由
func main() {
router:= gin.Default()
router.GET("/albums",getAlbums)
router.GET("/albums/:id",getAlbumByID)
router.POST("/albums",postAlbums)
router.Run("localhost:8080")
}
将 /albums/:id 路径与 getAlbumByID 函数相关联。在 Gin 中,路径中项目前的冒号表示该项目是路径参数。
curl 请求:
curl http://localhost:8080/albums/2

package main
import(
"net/http"
"github.com/gin-gonic/gin"
)
// json 标签表示序列化为 json 后的字段名,不加标签,会使用结构体字段名本身
// 一般 json 都是小写格式
type album struct {
ID string ` json:"id"`
Title string ` json:"title"`
Artist string ` json:"artist"`
Price float64 ` json:"price"`
}
// 定义数据
var albums = []album{
{ID: "1",Title: "Blue Train",Artist: "John Coltrane",Price:56.99},
{ID: "2",Title: "Jeru",Artist: "Gerry Mulligan",Price:17.99},
{ID: "3",Title: "Sarah Vaughan and Clifford Brown",Artist: "Sarah Vaughan",Price:39.99},
}
func main() {
router:= gin.Default()
router.GET("/albums",getAlbums)
router.POST("/albums",postAlbums)
router.GET("/albums/:id",getAlbumByID)
router.Run("localhost:8080")
}
// 返回响应
func getAlbums(c *gin.Context) {
c.IndentedJSON(http.StatusOK,albums)
}
func postAlbums(c *gin.Context) {
var newAlbum album
// 将请求中的 JSON 数据绑定到 newAlbum.
if err:= c.BindJSON(&newAlbum);err != nil {
return
}
// 添加相册到列表
albums = append(albums,newAlbum)
c.IndentedJSON(http.StatusCreated,newAlbum)
}
func getAlbumByID(c *gin.Context) {
id:= c.Param("id")
// 查找是否有对应 id 的相册
for _,a := range albums {
if a.ID == id {
c.IndentedJSON(http.StatusOK,a)
return
}
}
c.IndentedJSON(http.StatusNotFound,gin.H{"message": "album not found"})
}