Go 学习笔记(六):标准库与工程实践

写在前面

本文是 Go 学习笔记系列的第六篇,介绍 Go 标准库的常用功能,以及工程实践中需要掌握的技能:文件操作、HTTP 服务、数据库、命令行和单元测试。前置知识:并发编程(第五篇)。


一、文件操作

1.1 读取文件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import "os"

// 方式1:一次性读取整个文件(小文件适用)
data, err := os.ReadFile("config.yaml")
if err != nil {
    log.Fatal(err)
}
fmt.Println(string(data))

// 方式2:逐行读取(大文件适用)
file, err := os.Open("access.log")
if err != nil {
    log.Fatal(err)
}
defer file.Close()

scanner := bufio.NewScanner(file)
for scanner.Scan() {
    line := scanner.Text()
    fmt.Println(line)
}
if err := scanner.Err(); err != nil {
    log.Fatal(err)
}

1.2 写入文件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 方式1:一次性写入
err := os.WriteFile("output.txt", []byte("Hello Go"), 0644)

// 方式2:创建并写入(覆盖)
file, err := os.Create("output.txt")
if err != nil {
    log.Fatal(err)
}
defer file.Close()
file.WriteString("Hello Go\n")

// 方式3:追加写入
file, err := os.OpenFile("output.txt", os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644)
if err != nil {
    log.Fatal(err)
}
defer file.Close()
file.WriteString("追加的内容\n")

// 方式4:带缓冲写入
writer := bufio.NewWriter(file)
writer.WriteString("缓冲写入\n")
writer.Flush()   // 别忘了 Flush

1.3 文件和目录操作

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 创建目录
os.Mkdir("mydir", 0755)           // 创建一层
os.MkdirAll("a/b/c", 0755)        // 递归创建多层

// 删除
os.Remove("file.txt")              // 删除文件
os.RemoveAll("mydir")              // 递归删除目录

// 重命名/移动
os.Rename("old.txt", "new.txt")

// 获取文件信息
info, err := os.Stat("file.txt")
fmt.Println(info.Size())           // 文件大小
fmt.Println(info.IsDir())          // 是否目录
fmt.Println(info.ModTime())        // 修改时间

// 判断文件是否存在
_, err := os.Stat("file.txt")
if os.IsNotExist(err) {
    fmt.Println("文件不存在")
}

1.4 io 工具函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import "io"

// 复制(从 Reader 复制到 Writer)
io.Copy(dst, src)

// 读取全部内容
data, err := io.ReadAll(reader)

// 字符串当 Reader 用
reader := strings.NewReader("hello")

// 多个 Reader 拼接
reader := io.MultiReader(r1, r2, r3)

// 写入多个 Writer
writer := io.MultiWriter(w1, w2)

二、HTTP 服务

2.1 基础 HTTP 服务

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import "net/http"

func main() {
    // 注册路由
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("Hello, Go!"))
    })

    http.HandleFunc("/api/user", handleUser)

    // 启动服务
    fmt.Println("服务启动在 :8080")
    log.Fatal(http.ListenAndServe(":8080", nil))
}

func handleUser(w http.ResponseWriter, r *http.Request) {
    // 根据 Method 区分操作
    switch r.Method {
    case http.MethodGet:
        w.Header().Set("Content-Type", "application/json")
        w.Write([]byte(`{"name": "张三", "age": 25}`))
    case http.MethodPost:
        // 读取请求体
        body, _ := io.ReadAll(r.Body)
        defer r.Body.Close()
        fmt.Println("收到:", string(body))
        w.WriteHeader(http.StatusCreated)
    default:
        w.WriteHeader(http.StatusMethodNotAllowed)
    }
}

2.2 处理请求参数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
func handler(w http.ResponseWriter, r *http.Request) {
    // 获取 Query 参数
    name := r.URL.Query().Get("name")
    page := r.URL.Query().Get("page")

    // 获取路径参数(需要自定义路由或用框架)
    // 标准库不直接支持,推荐用 Gin 等框架

    // 获取 Header
    auth := r.Header.Get("Authorization")

    // 获取 Content-Type
    contentType := r.Header.Get("Content-Type")

    // 读取请求体
    body, err := io.ReadAll(r.Body)
    defer r.Body.Close()
}

2.3 发送 HTTP 请求

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import "net/http"

// GET 请求
resp, err := http.Get("https://api.example.com/users")
if err != nil {
    log.Fatal(err)
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))

// POST 请求(JSON)
data := strings.NewReader(`{"name": "张三"}`)
resp, err := http.Post("https://api.example.com/users", "application/json", data)
defer resp.Body.Close()

// 自定义请求(设置 Header、超时等)
req, _ := http.NewRequest("GET", "https://api.example.com/users", nil)
req.Header.Set("Authorization", "Bearer xxx")

client := &http.Client{Timeout: 10 * time.Second}
resp, err := client.Do(req)
defer resp.Body.Close()

2.4 JSON 响应

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
func jsonHandler(w http.ResponseWriter, r *http.Request) {
    user := User{Name: "张三", Age: 25}

    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(user)
}

// 接收 JSON 请求
func createUser(w http.ResponseWriter, r *http.Request) {
    var user User
    if err := json.NewDecoder(r.Body).Decode(&user); err != nil {
        http.Error(w, "无效的 JSON", http.StatusBadRequest)
        return
    }
    fmt.Println(user.Name)
}

三、数据库操作

3.1 基础连接

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
import "database/sql"
import _ "github.com/go-sql-driver/mysql"  // MySQL 驱动

func main() {
    // 连接数据库
    db, err := sql.Open("mysql", "user:password@tcp(127.0.0.1:3306)/mydb?charset=utf8mb4&parseTime=true")
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()

    // 验证连接
    if err := db.Ping(); err != nil {
        log.Fatal(err)
    }

    // 设置连接池
    db.SetMaxOpenConns(25)          // 最大连接数
    db.SetMaxIdleConns(5)           // 最大空闲连接
    db.SetConnMaxLifetime(5 * time.Minute)
}

3.2 查询操作

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 查询单行
var name string
var age int
err := db.QueryRow("SELECT name, age FROM users WHERE id = ?", 1).Scan(&name, &age)
if err == sql.ErrNoRows {
    fmt.Println("未找到")
}

// 查询多行
rows, err := db.Query("SELECT id, name, age FROM users WHERE age > ?", 18)
if err != nil {
    log.Fatal(err)
}
defer rows.Close()

for rows.Next() {
    var id, age int
    var name string
    if err := rows.Scan(&id, &name, &age); err != nil {
        log.Fatal(err)
    }
    fmt.Println(id, name, age)
}

3.3 增删改

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// 插入
result, err := db.Exec("INSERT INTO users (name, age) VALUES (?, ?)", "张三", 25)
if err != nil {
    log.Fatal(err)
}
id, _ := result.LastInsertId()   // 获取自增 ID

// 更新
result, err := db.Exec("UPDATE users SET age = ? WHERE id = ?", 26, 1)
affected, _ := result.RowsAffected()   // 影响行数

// 删除
result, err := db.Exec("DELETE FROM users WHERE id = ?", 1)

3.4 事务

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
tx, err := db.Begin()
if err != nil {
    log.Fatal(err)
}

// 执行事务中的操作
_, err = tx.Exec("UPDATE accounts SET balance = balance - 100 WHERE id = ?", 1)
if err != nil {
    tx.Rollback()
    log.Fatal(err)
}

_, err = tx.Exec("UPDATE accounts SET balance = balance + 100 WHERE id = ?", 2)
if err != nil {
    tx.Rollback()
    log.Fatal(err)
}

// 提交
if err := tx.Commit(); err != nil {
    log.Fatal(err)
}

实际项目中推荐用 sqlx 或 GORM 等库简化数据库操作。


四、命令行参数

4.1 os.Args

1
2
3
4
5
6
7
8
9
func main() {
    // os.Args[0] 是程序名,之后是参数
    if len(os.Args) < 2 {
        fmt.Println("用法: myapp <name>")
        os.Exit(1)
    }
    name := os.Args[1]
    fmt.Println("Hello,", name)
}

4.2 flag 包

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
import "flag"

func main() {
    // 定义命令行参数
    host := flag.String("host", "localhost", "服务器地址")
    port := flag.Int("port", 8080, "端口号")
    debug := flag.Bool("debug", false, "调试模式")

    // 也可以绑定到变量
    var config string
    flag.StringVar(&config, "config", "app.yaml", "配置文件路径")

    // 解析参数
    flag.Parse()

    fmt.Printf("host: %s, port: %d, debug: %v, config: %s\n", *host, *port, *debug, config)
}

// 使用:
// go run main.go -host 0.0.0.0 -port 3000 -debug -config prod.yaml

4.3 获取环境变量

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// 获取环境变量
dbHost := os.Getenv("DB_HOST")
dbPort := os.Getenv("DB_PORT")

// 带默认值
func getEnv(key, defaultValue string) string {
    if value := os.Getenv(key); value != "" {
        return value
    }
    return defaultValue
}

host := getEnv("DB_HOST", "localhost")

五、单元测试

5.1 基本测试

Go 的测试文件以 _test.go 结尾,测试函数以 Test 开头:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// math.go
package math

func Add(a, b int) int {
    return a + b
}

// math_test.go(同包下)
package math

import "testing"

func TestAdd(t *testing.T) {
    result := Add(1, 2)
    if result != 3 {
        t.Errorf("Add(1, 2) = %d, want 3", result)
    }
}

运行测试:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 运行当前包的测试
go test

# 详细输出
go test -v

# 运行指定测试函数
go test -run TestAdd

# 运行所有测试
go test ./...

5.2 表驱动测试(推荐)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
func TestAdd(t *testing.T) {
    tests := []struct {
        name     string
        a, b     int
        expected int
    }{
        {"正数相加", 1, 2, 3},
        {"负数相加", -1, -2, -3},
        {"零值相加", 0, 0, 0},
        {"正负混合", 5, -3, 2},
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            result := Add(tt.a, tt.b)
            if result != tt.expected {
                t.Errorf("Add(%d, %d) = %d, want %d", tt.a, tt.b, result, tt.expected)
            }
        })
    }
}

5.3 基准测试

1
2
3
4
5
func BenchmarkAdd(b *testing.B) {
    for i := 0; i < b.N; i++ {
        Add(1, 2)
    }
}
1
2
3
# 运行基准测试
go test -bench=.
go test -bench=. -benchmem   # 显示内存分配情况

5.4 测试 HTTP Handler

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import "net/http/httptest"

func TestHandler(t *testing.T) {
    req := httptest.NewRequest("GET", "/api/user?name=张三", nil)
    w := httptest.NewRecorder()

    handleUser(w, req)

    if w.Code != http.StatusOK {
        t.Errorf("状态码 = %d, want %d", w.Code, http.StatusOK)
    }

    body := w.Body.String()
    fmt.Println(body)
}

六、日志

6.1 标准库 log

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import "log"

log.Println("普通日志")
log.Printf("用户 %s 登录", name)
log.Fatal("致命错误,程序退出")   // 输出后调用 os.Exit(1)
log.Panic("严重错误")            // 输出后调用 panic

// 自定义日志格式
logger := log.New(os.Stdout, "[APP] ", log.LstdFlags|log.Lshortfile)
logger.Println("自定义格式日志")

6.2 log/slog(Go 1.21+,结构化日志)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
import "log/slog"

// 基本使用
slog.Info("用户登录", "user", "张三", "ip", "192.168.1.1")
slog.Error("操作失败", "err", err, "path", "/api/users")

// 结构化日志输出(JSON 格式)
handler := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelDebug})
logger := slog.New(handler)
slog.SetDefault(logger)

slog.Info("请求处理完成",
    "method", "GET",
    "path", "/api/users",
    "status", 200,
    "duration", time.Since(start),
)
// {"time":"...","level":"INFO","msg":"请求处理完成","method":"GET","path":"/api/users","status":200,"duration":"1.2ms"}

七、项目结构规范

Go 项目没有强制的目录结构,但社区有推荐的规范:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
myproject/
├── cmd/                // 可执行程序入口
│   └── server/
│       └── main.go
├── internal/           // 私有代码(不能被外部导入)
│   ├── handler/        // HTTP 处理器
│   ├── service/        // 业务逻辑
│   └── repository/     // 数据访问
├── pkg/                // 可被外部导入的公共包
│   └── utils/
├── config/             // 配置文件
│   └── app.yaml
├── go.mod
├── go.sum
└── Makefile

internal 目录是 Go 编译器强制约束的:其他模块无法导入 internal 下的包。


八、小结

本文学习了 Go 的标准库和工程实践:

  • 文件操作(os、io、bufio)
  • HTTP 服务与客户端(net/http)
  • 数据库操作(database/sql)
  • 命令行参数与环境变量
  • 单元测试(testing、表驱动测试、httptest)
  • 日志(log、slog)
  • 项目结构规范

下一篇将学习 Go 的进阶特性:泛型、反射、性能分析。