没有理想的人不伤心

Golang - 官方文档学习 - web接口

2025/04/24
1
0

Getting Started

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.gopackage 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 函数)进行编译,并将可执行文件下载到设置的环境变量目录下。

然后便可以全局执行。

Developing a RESTful API with Go and Gin

需要创建的 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},
}

GET 请求 /albums

需要编写:

  1. 响应的逻辑
  2. 请求的路由

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

1732605071209-12863a19-6bd9-443d-8352-bb349e041531.png

POST 请求/albums

  • 将新的 JSON 相册数据添加到相册列表
  • POST 请求路由处理

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}'

1732605828451-d67e3806-cb38-40c0-8e96-5a11758a3d14.png

GET 请求 /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

1732606681656-e17d6295-42cb-4a4c-a283-cdc4bc4f4bd1.png

完整代码

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"})
}