2. Go标准库

2.1 核心知识点

fmt包(格式化输入输出)

fmt.Println("Hello, World!")
fmt.Printf("Name: %s, Age: %d\n", "Alice", 25)
fmt.Sprintf("Formatted string: %d", 42)
fmt.Scan(&name, &age)

strings包(字符串操作)

strings.Contains("hello world", "world")
strings.HasPrefix("hello", "he")
strings.HasSuffix("hello", "lo")
strings.Index("hello", "l")
strings.Split("a,b,c", ",")
strings.Join([]string{"a", "b", "c"}, ",")
strings.Replace("hello", "l", "x", -1)
strings.ToLower("HELLO")
strings.ToUpper("hello")
strings.TrimSpace("  hello  ")

regexp包(正则表达式)

matched, _ := regexp.MatchString("a.*b", "axxxb")
re := regexp.MustCompile(`\d+`)
matches := re.FindAllString("abc123def456", -1)

time包(时间处理)

now := time.Now()
fmt.Println(now.Format("2006-01-02 15:04:05"))
fmt.Println(now.Unix())
 
parsed, _ := time.Parse("2006-01-02", "2024-01-01")
duration := time.Since(parsed)
 
later := now.Add(24 * time.Hour)

io包和bufio包(输入输出)

file, _ := os.Open("file.txt")
defer file.Close()
 
reader := bufio.NewReader(file)
line, _ := reader.ReadString('\n')
 
writer := bufio.NewWriter(file)
writer.WriteString("Hello")
writer.Flush()

net包和http包(网络编程)

http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello, %s!", r.URL.Path[1:])
})
http.ListenAndServe(":8080", nil)
 
resp, _ := http.Get("https://api.example.com")
defer resp.Body.Close()
body, _ := ioutil.ReadAll(resp.Body)

sync包(并发控制)

var mu sync.Mutex
mu.Lock()
mu.Unlock()
 
var wg sync.WaitGroup
wg.Add(1)
go func() {
    defer wg.Done()
}()
wg.Wait()
 
var once sync.Once
once.Do(func() {
    fmt.Println("Execute once")
})

context包(上下文管理)

ctx := context.Background()
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
 
select {
case <-ctx.Done():
    fmt.Println("Context canceled")
case <-time.After(10 * time.Second):
    fmt.Println("Operation completed")
}

2.2 常见面试题

Q1: 如何使用strings包进行字符串操作?

解题思路

  1. 列举strings包中常用的函数
  2. 演示字符串查找、替换、分割、连接等操作
  3. 说明字符串大小写转换和空格处理
  4. 展示实际应用场景

代码实现

package main
 
import (
    "fmt"
    "strings"
)
 
func main() {
    str := "Hello, World! Hello, Go!"
    
    contains := strings.Contains(str, "World")
    fmt.Printf("Contains 'World': %v\n", contains)
    
    hasPrefix := strings.HasPrefix(str, "Hello")
    fmt.Printf("Has prefix 'Hello': %v\n", hasPrefix)
    
    hasSuffix := strings.HasSuffix(str, "Go!")
    fmt.Printf("Has suffix 'Go!': %v\n", hasSuffix)
    
    index := strings.Index(str, "World")
    fmt.Printf("Index of 'World': %d\n", index)
    
    parts := strings.Split(str, ", ")
    fmt.Printf("Split by ', ': %v\n", parts)
    
    joined := strings.Join(parts, " | ")
    fmt.Printf("Joined: %s\n", joined)
    
    replaced := strings.Replace(str, "Hello", "Hi", 1)
    fmt.Printf("Replace first 'Hello' with 'Hi': %s\n", replaced)
    
    replacedAll := strings.ReplaceAll(str, "Hello", "Hi")
    fmt.Printf("Replace all 'Hello' with 'Hi': %s\n", replacedAll)
    
    lower := strings.ToLower(str)
    fmt.Printf("Lower case: %s\n", lower)
    
    upper := strings.ToUpper(str)
    fmt.Printf("Upper case: %s\n", upper)
    
    trimmed := strings.TrimSpace("  Hello, World!  ")
    fmt.Printf("Trimmed: '%s'\n", trimmed)
    
    repeated := strings.Repeat("Go", 3)
    fmt.Printf("Repeated: %s\n", repeated)
}

常见错误分析

错误1:混淆Contains和HasPrefix/HasSuffix的用途

问题说明

  • Contains(s, substr):检查字符串s是否包含子串substr(任意位置)
  • HasPrefix(s, prefix):检查字符串s是否以prefix开头
  • HasSuffix(s, suffix):检查字符串s是否以suffix结尾

错误示例

str := "hello.txt"
 
// 错误:用Contains检查文件扩展名
if strings.Contains(str, ".txt") {
    fmt.Println("是txt文件")
}
 
// 错误:用HasPrefix检查包含关系
if strings.HasPrefix(str, "hello") {
    fmt.Println("包含hello")
}

正确做法

str := "hello.txt"
 
// 检查文件扩展名 - 使用HasSuffix
if strings.HasSuffix(str, ".txt") {
    fmt.Println("是txt文件")
}
 
// 检查是否包含某个子串 - 使用Contains
if strings.Contains(str, "hello") {
    fmt.Println("包含hello")
}
 
// 检查URL协议 - 使用HasPrefix
url := "https://example.com"
if strings.HasPrefix(url, "https://") {
    fmt.Println("是HTTPS协议")
}

错误2:不理解Replace的第三个参数(替换次数)

问题说明strings.Replace(s, old, new, n) 的第三个参数n控制替换次数:

  • n > 0:最多替换n次
  • n = 0:不替换
  • n < 0(通常用-1):替换所有匹配项

错误示例

str := "hello hello hello"
 
// 错误:以为第三个参数是"是否替换所有"的布尔值
result := strings.Replace(str, "hello", "hi", true)
 
// 错误:不知道-1表示替换所有
result := strings.Replace(str, "hello", "hi", 100)

正确做法

str := "hello hello hello"
 
// 替换第一个
result1 := strings.Replace(str, "hello", "hi", 1)
fmt.Println(result1)  // "hi hello hello"
 
// 替换前两个
result2 := strings.Replace(str, "hello", "hi", 2)
fmt.Println(result2)  // "hi hi hello"
 
// 替换所有(使用-1)
resultAll := strings.Replace(str, "hello", "hi", -1)
fmt.Println(resultAll)  // "hi hi hi"
 
// 或者使用ReplaceAll(更简洁)
resultAll2 := strings.ReplaceAll(str, "hello", "hi")
fmt.Println(resultAll2)  // "hi hi hi"

错误3:字符串操作后忘记赋值给新变量(Go字符串不可变)

问题说明: Go中的字符串是不可变的(immutable),所有字符串操作函数都返回新字符串,不会修改原字符串。

错误示例

str := "  hello  "
 
// 错误:以为会修改原字符串
strings.TrimSpace(str)
fmt.Println(str)  // 仍然是 "  hello  "
 
// 错误:链式调用时忘记赋值
strings.ToLower(strings.TrimSpace(str))
 
// 错误:多次操作时没有保存中间结果
strings.Replace(str, "hello", "hi", -1)
strings.ToUpper(str)

正确做法

str := "  hello  "
 
// 正确:赋值给新变量
trimmed := strings.TrimSpace(str)
fmt.Println(trimmed)  // "hello"
 
// 正确:链式调用并赋值
processed := strings.ToUpper(strings.TrimSpace(str))
fmt.Println(processed)  // "HELLO"
 
// 正确:分步处理并赋值
str = "  hello  "
str = strings.TrimSpace(str)
str = strings.ToUpper(str)
fmt.Println(str)  // "HELLO"
 
// 正确:复杂处理流程
func processString(s string) string {
    s = strings.TrimSpace(s)
    s = strings.ToLower(s)
    s = strings.ReplaceAll(s, " ", "_")
    return s
}
 
original := "  Hello World  "
result := processString(original)
fmt.Println(result)  // "hello_world"
fmt.Println(original)  // "  Hello World  "(原字符串不变)

性能提示

// 低效:多次创建新字符串
str := "  Hello World  "
str = strings.TrimSpace(str)
str = strings.ToLower(str)
str = strings.ReplaceAll(str, " ", "_")
 
// 高效:使用strings.Builder处理复杂字符串操作
var builder strings.Builder
str := "  Hello World  "
trimmed := strings.TrimSpace(str)
builder.WriteString(trimmed)
builder.WriteString("_processed")
result := builder.String()

Q2: 如何使用time包处理时间?

解题思路

  1. 演示获取当前时间
  2. 说明时间格式化的特殊规则(2006-01-02 15:04:05)
  3. 演示时间解析和计算
  4. 展示时区处理

代码实现

package main
 
import (
    "fmt"
    "time"
)
 
func main() {
    now := time.Now()
    fmt.Println("Current time:", now)
    fmt.Println("Formatted:", now.Format("2006-01-02 15:04:05"))
    fmt.Println("Unix timestamp:", now.Unix())
    fmt.Println("Unix nano:", now.UnixNano())
    
    year, month, day := now.Date()
    fmt.Printf("Year: %d, Month: %d, Day: %d\n", year, month, day)
    
    hour, min, sec := now.Clock()
    fmt.Printf("Hour: %d, Minute: %d, Second: %d\n", hour, min, sec)
    
    parsed, err := time.Parse("2006-01-02", "2024-01-01")
    if err != nil {
        fmt.Println("Parse error:", err)
        return
    }
    fmt.Println("Parsed time:", parsed)
    
    duration := time.Since(parsed)
    fmt.Printf("Duration since parsed time: %v\n", duration)
    fmt.Printf("Duration in hours: %.2f\n", duration.Hours())
    
    future := now.Add(24 * time.Hour)
    fmt.Println("Future time:", future.Format("2006-01-02 15:04:05"))
    
    past := now.Add(-24 * time.Hour)
    fmt.Println("Past time:", past.Format("2006-01-02 15:04:05"))
    
    loc, _ := time.LoadLocation("America/New_York")
    nyTime := now.In(loc)
    fmt.Println("New York time:", nyTime.Format("2006-01-02 15:04:05 MST"))
    
    start := time.Now()
    time.Sleep(100 * time.Millisecond)
    elapsed := time.Since(start)
    fmt.Printf("Elapsed time: %v\n", elapsed)
}

常见错误分析

错误1:时间格式化字符串错误(必须使用2006-01-02 15:04:05)

问题说明: Go的时间格式化使用特殊的参考时间:2006-01-02 15:04:05,这是Go语言设计的一个独特记忆点。这个时间点不是随意的,而是按照时间顺序排列的:

  • 2006:年份(4位)
  • 01:月份(2位)
  • 02:日期(2位)
  • 15:小时(24小时制,2位)
  • 04:分钟(2位)
  • 05:秒(2位)

记忆口诀01/02 03:04:05PM '06 -0700(对应:月/日 时:分:秒PM 年 时区)

错误示例

now := time.Now()
 
// 错误1:使用传统的YYYY-MM-DD格式
fmt.Println(now.Format("YYYY-MM-DD"))  // 输出:YYYY-MM-DD(错误!)
 
// 错误2:使用dd/mm/yyyy格式
fmt.Println(now.Format("dd/mm/yyyy"))  // 输出:dd/mm/yyyy(错误!)
 
// 错误3:混淆大小写
fmt.Println(now.Format("2006-01-02 03:04:05"))  // 12小时制,缺少PM
fmt.Println(now.Format("2006-01-02 15:04:05 PM"))  // PM位置错误

正确做法

now := time.Now()
 
// 标准日期时间格式
fmt.Println(now.Format("2006-01-02 15:04:05"))  // 2024-01-15 14:30:45
fmt.Println(now.Format("2006/01/02 15:04:05"))  // 2024/01/15 14:30:45
 
// 日期格式
fmt.Println(now.Format("2006-01-02"))           // 2024-01-15
fmt.Println(now.Format("2006年01月02日"))        // 2024年01月15日
 
// 时间格式
fmt.Println(now.Format("15:04:05"))              // 14:30:45
fmt.Println(now.Format("15:04"))                 // 14:30
 
// 12小时制
fmt.Println(now.Format("2006-01-02 03:04:05 PM"))  // 2024-01-15 02:30:45 PM
 
// 带星期
fmt.Println(now.Format("2006-01-02 Monday"))       // 2024-01-15 Monday
fmt.Println(now.Format("2006-01-02 Mon"))          // 2024-01-15 Mon
 
// 带时区
fmt.Println(now.Format("2006-01-02 15:04:05 MST"))  // 2024-01-15 14:30:45 CST
fmt.Println(now.Format("2006-01-02 15:04:05 -0700")) // 2024-01-15 14:30:45 +0800
 
// 自定义格式
fmt.Println(now.Format("20060102"))                 // 20240115
fmt.Println(now.Format("2006_01_02"))               // 2024_01_15
fmt.Println(now.Format("01/02/2006"))               // 01/15/2024

常用格式化对照表

格式字符串示例输出说明
200620244位年份
06242位年份
0101月份(01-12)
11月份(1-12)
JanJan月份缩写
JanuaryJanuary月份全称
0215日期(01-31)
215日期(1-31)
1514小时(00-23)
0302小时(01-12)
0430分钟(00-59)
0545秒(00-59)
.000.000毫秒
.000000.000000微秒
.000000000.000000000纳秒
PMPMAM/PM标记
MSTCST时区缩写
-0700+0800时区偏移
MondayMonday星期全称
MonMon星期缩写

实际应用场景

now := time.Now()
 
// 场景1:日志文件名
logFileName := now.Format("2006-01-02_15-04-05.log")
fmt.Println(logFileName)  // 2024-01-15_14-30-45.log
 
// 场景2:数据库存储格式
dbTime := now.Format("2006-01-02 15:04:05")
fmt.Println(dbTime)  // 2024-01-15 14:30:45
 
// 场景3:API响应时间格式
apiTime := now.Format("2006-01-02T15:04:05Z07:00")
fmt.Println(apiTime)  // 2024-01-15T14:30:45+08:00
 
// 场景4:用户友好的日期显示
userDate := now.Format("2006年01月02日 星期一")
fmt.Println(userDate)  // 2024年01月15日 星期一
 
// 场景5:相对时间显示
duration := time.Since(now)
fmt.Printf("时间差: %v\n", duration)  // 自动选择合适单位

时间解析(Parse)

// 解析字符串为时间
timeStr := "2024-01-15 14:30:45"
parsed, err := time.Parse("2006-01-02 15:04:05", timeStr)
if err != nil {
    fmt.Println("解析错误:", err)
}
fmt.Println(parsed)
 
// 解析不同格式
timeStr2 := "2024/01/15"
parsed2, _ := time.Parse("2006/01/02", timeStr2)
fmt.Println(parsed2)
 
// 解析带时区的时间
timeStr3 := "2024-01-15T14:30:45+08:00"
parsed3, _ := time.Parse(time.RFC3339, timeStr3)
fmt.Println(parsed3)

预定义常量格式

now := time.Now()
 
// Go提供了一些预定义的时间格式常量
fmt.Println(now.Format(time.RFC3339))      // 2024-01-15T14:30:45+08:00
fmt.Println(now.Format(time.RFC1123))      // Mon, 15 Jan 2024 14:30:45 CST
fmt.Println(now.Format(time.RFC822))       // 15 Jan 24 14:30 CST
fmt.Println(now.Format(time.Kitchen))      // 2:30PM
fmt.Println(now.Format(time.ANSIC))        // Mon Jan 15 14:30:45 2024
fmt.Println(now.Format(time.UnixDate))    // Mon Jan 15 14:30:45 CST 2024
fmt.Println(now.Format(time.RubyDate))    // Mon Jan 15 14:30:45 +0800 2024

错误2:混淆Unix()和UnixNano()

问题说明

  • Unix():返回从1970年1月1日UTC到现在的秒数(int64)
  • UnixNano():返回从1970年1月1日UTC到现在的纳秒数(int64)

错误示例

now := time.Now()
 
// 错误1:以为Unix()返回毫秒
timestamp := now.Unix()
fmt.Println(timestamp)  // 1705303845(秒)
 
// 错误2:混淆精度
nano := now.UnixNano()
fmt.Println(nano)  // 1705303845123456789(纳秒)

正确做法

now := time.Now()
 
// 获取秒级时间戳
unixSeconds := now.Unix()
fmt.Println(unixSeconds)  // 1705303845
 
// 获取毫秒级时间戳
unixMillis := now.UnixMilli()
fmt.Println(unixMillis)  // 1705303845123
 
// 获取微秒级时间戳
unixMicros := now.UnixMicro()
fmt.Println(unixMicros)  // 1705303845123456
 
// 获取纳秒级时间戳
unixNanos := now.UnixNano()
fmt.Println(unixNanos)  // 1705303845123456789
 
// 时间戳转时间
t1 := time.Unix(1705303845, 0)
fmt.Println(t1)  // 2024-01-15 14:30:45 +0800 CST
 
t2 := time.Unix(0, 1705303845123456789)
fmt.Println(t2)  // 2024-01-15 14:30:45.123456789 +0800 CST

错误3:时区处理不当

问题说明: Go的time.Time对象包含时区信息,如果不正确处理时区,可能导致时间显示和计算错误。

错误示例

now := time.Now()
 
// 错误1:解析时没有指定时区
parsed, _ := time.Parse("2006-01-02 15:04:05", "2024-01-15 14:30:45")
fmt.Println(parsed)  // 时区为UTC,不是本地时区
 
// 错误2:时区转换错误
loc, _ := time.LoadLocation("America/New_York")
nyTime := parsed.In(loc)
fmt.Println(nyTime)  // 可能显示错误的时间

正确做法

now := time.Now()
 
// 解析时指定时区
loc, _ := time.LoadLocation("Asia/Shanghai")
parsed, _ := time.ParseInLocation("2006-01-02 15:04:05", "2024-01-15 14:30:45", loc)
fmt.Println(parsed)  // 时区为Asia/Shanghai
 
// 转换到不同时区
nyLoc, _ := time.LoadLocation("America/New_York")
nyTime := parsed.In(nyLoc)
fmt.Println(nyTime)  // 2024-01-15 01:30:45 EST
 
// 使用UTC
utcTime := parsed.UTC()
fmt.Println(utcTime)  // 2024-01-15 06:30:45 UTC
 
// 使用本地时区
localTime := parsed.Local()
fmt.Println(localTime)  // 2024-01-15 14:30:45 CST
 
// 常用时区
loc1, _ := time.LoadLocation("UTC")
loc2, _ := time.LoadLocation("Asia/Shanghai")
loc3, _ := time.LoadLocation("America/New_York")
loc4, _ := time.LoadLocation("Europe/London")
loc5, _ := time.LoadLocation("Asia/Tokyo")
 
fmt.Println("UTC:", now.In(loc1))
fmt.Println("Shanghai:", now.In(loc2))
fmt.Println("New York:", now.In(loc3))
fmt.Println("London:", now.In(loc4))
fmt.Println("Tokyo:", now.In(loc5))

Q3: 如何使用http包创建HTTP服务器?

解题思路

  1. 演示创建简单的HTTP服务器
  2. 说明路由处理和参数获取
  3. 展示中间件的使用
  4. 演示静态文件服务

代码实现

package main
 
import (
    "encoding/json"
    "fmt"
    "log"
    "net/http"
    "time"
)
 
type User struct {
    Name  string `json:"name"`
    Email string `json:"email"`
}
 
func loggingMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        log.Printf("Started %s %s", r.Method, r.URL.Path)
        next.ServeHTTP(w, r)
        log.Printf("Completed %s %s in %v", r.Method, r.URL.Path, time.Since(start))
    })
}
 
func homeHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Welcome to the home page!")
}
 
func userHandler(w http.ResponseWriter, r *http.Request) {
    user := User{
        Name:  "Alice",
        Email: "alice@example.com",
    }
    
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(user)
}
 
func queryHandler(w http.ResponseWriter, r *http.Request) {
    name := r.URL.Query().Get("name")
    age := r.URL.Query().Get("age")
    
    fmt.Fprintf(w, "Name: %s, Age: %s", name, age)
}
 
func main() {
    mux := http.NewServeMux()
    
    mux.HandleFunc("/", homeHandler)
    mux.HandleFunc("/user", userHandler)
    mux.HandleFunc("/query", queryHandler)
    
    handler := loggingMiddleware(mux)
    
    server := &http.Server{
        Addr:         ":8080",
        Handler:      handler,
        ReadTimeout:  10 * time.Second,
        WriteTimeout: 10 * time.Second,
    }
    
    log.Println("Server starting on :8080")
    log.Fatal(server.ListenAndServe())
}

常见错误分析

错误1:忘记设置Content-Type头

问题说明: 当返回JSON、HTML、XML等非纯文本内容时,必须设置正确的Content-Type响应头,否则客户端无法正确解析响应内容。

错误示例

func userHandler(w http.ResponseWriter, r *http.Request) {
    user := User{
        Name:  "Alice",
        Email: "alice@example.com",
    }
    
    // 错误:没有设置Content-Type
    json.NewEncoder(w).Encode(user)
    // 客户端收到的Content-Type是text/plain,无法正确解析JSON
}
 
func htmlHandler(w http.ResponseWriter, r *http.Request) {
    // 错误:没有设置Content-Type
    fmt.Fprintf(w, "<html><body>Hello</body></html>")
    // 客户端会将其当作纯文本处理
}

正确做法

func userHandler(w http.ResponseWriter, r *http.Request) {
    user := User{
        Name:  "Alice",
        Email: "alice@example.com",
    }
    
    // 正确:设置JSON Content-Type
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(user)
}
 
func htmlHandler(w http.ResponseWriter, r *http.Request) {
    // 正确:设置HTML Content-Type
    w.Header().Set("Content-Type", "text/html; charset=utf-8")
    fmt.Fprintf(w, "<html><body>Hello</body></html>")
}
 
func xmlHandler(w http.ResponseWriter, r *http.Request) {
    // 正确:设置XML Content-Type
    w.Header().Set("Content-Type", "application/xml")
    fmt.Fprintf(w, "<?xml version=\"1.0\"?><root>Hello</root>")
}
 
func plainTextHandler(w http.ResponseWriter, r *http.Request) {
    // 正确:设置纯文本Content-Type
    w.Header().Set("Content-Type", "text/plain; charset=utf-8")
    fmt.Fprintf(w, "Hello, World!")
}
 
func downloadHandler(w http.ResponseWriter, r *http.Request) {
    // 正确:设置文件下载Content-Type
    w.Header().Set("Content-Type", "application/octet-stream")
    w.Header().Set("Content-Disposition", "attachment; filename=file.txt")
    fmt.Fprintf(w, "File content")
}

常用Content-Type类型

// JSON
w.Header().Set("Content-Type", "application/json")
 
// JSONP
w.Header().Set("Content-Type", "application/javascript")
 
// HTML
w.Header().Set("Content-Type", "text/html; charset=utf-8")
 
// XML
w.Header().Set("Content-Type", "application/xml")
 
// 纯文本
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
 
// CSS
w.Header().Set("Content-Type", "text/css; charset=utf-8")
 
// JavaScript
w.Header().Set("Content-Type", "application/javascript")
 
// 表单数据
w.Header().Set("Content-Type", "application/x-www-form-urlencoded")
 
// 多部分表单
w.Header().Set("Content-Type", "multipart/form-data")
 
// 文件下载
w.Header().Set("Content-Type", "application/octet-stream")
 
// 图片
w.Header().Set("Content-Type", "image/jpeg")
w.Header().Set("Content-Type", "image/png")
w.Header().Set("Content-Type", "image/gif")
 
// PDF
w.Header().Set("Content-Type", "application/pdf")

错误2:超时设置不当导致资源泄漏

问题说明: HTTP服务器必须设置合理的超时时间,否则慢客户端或恶意请求可能导致连接长时间占用,最终耗尽服务器资源。

错误示例

// 错误1:完全没有设置超时
server := &http.Server{
    Addr:    ":8080",
    Handler: mux,
}
// 慢客户端可以无限期占用连接
 
// 错误2:超时时间设置过长
server := &http.Server{
    Addr:         ":8080",
    Handler:      mux,
    ReadTimeout:  1 * time.Hour,  // 太长
    WriteTimeout: 1 * time.Hour,  // 太长
}
 
// 错误3:只设置了部分超时
server := &http.Server{
    Addr:        ":8080",
    Handler:     mux,
    ReadTimeout: 10 * time.Second,
    // 缺少WriteTimeout、IdleTimeout等
}

正确做法

// 正确:设置完整的超时配置
server := &http.Server{
    Addr:         ":8080",
    Handler:      mux,
    
    // 读取整个请求(包括请求体)的超时时间
    ReadTimeout:  5 * time.Second,
    
    // 写入响应的超时时间
    WriteTimeout: 10 * time.Second,
    
    // 空闲连接的超时时间
    IdleTimeout:  120 * time.Second,
    
    // 读取请求头的超时时间
    ReadHeaderTimeout: 2 * time.Second,
    
    // 最大请求体大小(防止大文件攻击)
    MaxHeaderBytes: 1 << 20, // 1MB
}
 
log.Println("Server starting on :8080")
log.Fatal(server.ListenAndServe())

超时参数详解

server := &http.Server{
    Addr: ":8080",
    
    // ReadTimeout: 从连接建立到读取完整个请求体的时间
    // 包括读取请求头和请求体
    // 如果客户端发送数据太慢,超过这个时间连接会被关闭
    ReadTimeout: 5 * time.Second,
    
    // WriteTimeout: 从开始写响应到写完整个响应的时间
    // 如果客户端接收数据太慢,超过这个时间连接会被关闭
    WriteTimeout: 10 * time.Second,
    
    // IdleTimeout: 空闲连接的超时时间
    // 连接建立后,如果在这个时间内没有新的请求,连接会被关闭
    // 适用于HTTP/1.1的keep-alive连接
    IdleTimeout: 120 * time.Second,
    
    // ReadHeaderTimeout: 读取请求头的超时时间
    // 如果客户端发送请求头太慢,超过这个时间连接会被关闭
    // 这个超时应该在ReadTimeout之前触发
    ReadHeaderTimeout: 2 * time.Second,
    
    // MaxHeaderBytes: 请求头的最大字节数
    // 防止恶意客户端发送超大的请求头
    MaxHeaderBytes: 1 << 20, // 1MB
}

实际应用场景

// 场景1:API服务器(快速响应)
apiServer := &http.Server{
    Addr:              ":8080",
    Handler:           apiHandler,
    ReadTimeout:       5 * time.Second,
    WriteTimeout:      10 * time.Second,
    IdleTimeout:       60 * time.Second,
    ReadHeaderTimeout: 2 * time.Second,
}
 
// 场景2:文件上传服务器(需要更长的超时)
uploadServer := &http.Server{
    Addr:              ":8081",
    Handler:           uploadHandler,
    ReadTimeout:       30 * time.Second,  // 允许上传大文件
    WriteTimeout:      30 * time.Second,
    IdleTimeout:       60 * time.Second,
    ReadHeaderTimeout: 5 * time.Second,
    MaxHeaderBytes:    1 << 20,
}
 
// 场景3:WebSocket服务器(需要特殊处理)
wsServer := &http.Server{
    Addr:         ":8082",
    Handler:      wsHandler,
    ReadTimeout:  0,  // WebSocket需要长连接,不设置超时
    WriteTimeout: 0,  // WebSocket需要长连接,不设置超时
    IdleTimeout:  0,  // WebSocket需要长连接,不设置超时
}
 
// 场景4:反向代理服务器
proxyServer := &http.Server{
    Addr:              ":8083",
    Handler:           proxyHandler,
    ReadTimeout:       30 * time.Second,
    WriteTimeout:      30 * time.Second,
    IdleTimeout:       300 * time.Second,  // 允许更长的空闲时间
    ReadHeaderTimeout: 10 * time.Second,
}

优雅关闭

func main() {
    server := &http.Server{
        Addr:         ":8080",
        Handler:      mux,
        ReadTimeout:  5 * time.Second,
        WriteTimeout: 10 * time.Second,
        IdleTimeout:  120 * time.Second,
    }
    
    // 启动服务器
    go func() {
        log.Println("Server starting on :8080")
        if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
            log.Fatalf("Server error: %v", err)
        }
    }()
    
    // 等待中断信号
    quit := make(chan os.Signal, 1)
    signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
    <-quit
    
    log.Println("Shutting down server...")
    
    // 优雅关闭,等待现有请求完成(最多30秒)
    ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
    defer cancel()
    
    if err := server.Shutdown(ctx); err != nil {
        log.Printf("Server forced to shutdown: %v", err)
    }
    
    log.Println("Server exited")
}

错误3:路由冲突

问题说明: 在HTTP服务器中,路由的匹配顺序很重要。如果路由定义不当,可能导致请求被错误的路由处理,或者某些路由永远无法被访问。

错误示例

mux := http.NewServeMux()
 
// 错误1:通用路由在前面
mux.HandleFunc("/", homeHandler)  // 这个会匹配所有请求
mux.HandleFunc("/user", userHandler)  // 永远不会被访问
mux.HandleFunc("/user/profile", profileHandler)  // 永远不会被访问
 
// 错误2:路由重叠
mux.HandleFunc("/api/", apiHandler)  // 匹配 /api/ 开头的所有请求
mux.HandleFunc("/api/user", userHandler)  // 可能被上面的路由捕获
 
// 错误3:使用ServeMux时忽略路由匹配规则
mux.HandleFunc("/user", userHandler)
mux.HandleFunc("/user/", userDetailHandler)  // 容易混淆

正确做法

// 正确:使用http.ServeMux的路由规则
mux := http.NewServeMux()
 
// ServeMux的路由匹配规则:
// 1. 最长匹配优先
// 2. 精确匹配优先于模式匹配
// 3. 模式必须以 / 结尾才能匹配子路径
 
// 正确的路由定义
mux.HandleFunc("/", homeHandler)  // 默认路由,放在最后
mux.HandleFunc("/user", userHandler)  // 精确匹配 /user
mux.HandleFunc("/user/", userDetailHandler)  // 匹配 /user/ 开头的所有请求
mux.HandleFunc("/api/", apiHandler)  // 匹配 /api/ 开头的所有请求
 
// 正确:使用第三方路由库(推荐)
// 使用gorilla/mux
router := mux.NewRouter()
 
// 路由分组
api := router.PathPrefix("/api").Subrouter()
api.HandleFunc("/user", userHandler).Methods("GET")
api.HandleFunc("/user", createUserHandler).Methods("POST")
api.HandleFunc("/user/{id}", getUserHandler).Methods("GET")
api.HandleFunc("/user/{id}", updateUserHandler).Methods("PUT")
api.HandleFunc("/user/{id}", deleteUserHandler).Methods("DELETE")
 
// 中间件
router.Use(loggingMiddleware)
router.Use(authMiddleware)
 
// 静态文件
router.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))
 
// 使用chi路由库
r := chi.NewRouter()
 
// 中间件
r.Use(middleware.Logger)
r.Use(middleware.Recoverer)
 
// 路由
r.Get("/", homeHandler)
r.Route("/api", func(r chi.Router) {
    r.Get("/user", userHandler)
    r.Post("/user", createUserHandler)
    r.Get("/user/{id}", getUserHandler)
    r.Put("/user/{id}", updateUserHandler)
    r.Delete("/user/{id}", deleteUserHandler)
})
 
// 静态文件
r.FileServer("/static", http.Dir("static"))

路由匹配规则详解

// http.ServeMux的路由匹配规则
 
mux := http.NewServeMux()
 
// 规则1:精确匹配优先
mux.HandleFunc("/user", func(w http.ResponseWriter, r *http.Request) {
    fmt.Fprint(w, "Exact match: /user")
})
 
mux.HandleFunc("/user/", func(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Pattern match: %s", r.URL.Path)
})
 
// 请求 /user -> 精确匹配,输出 "Exact match: /user"
// 请求 /user/ -> 模式匹配,输出 "Pattern match: /user/"
// 请求 /user/profile -> 模式匹配,输出 "Pattern match: /user/profile"
 
// 规则2:最长匹配优先
mux.HandleFunc("/api", func(w http.ResponseWriter, r *http.Request) {
    fmt.Fprint(w, "Short match: /api")
})
 
mux.HandleFunc("/api/user", func(w http.ResponseWriter, r *http.Request) {
    fmt.Fprint(w, "Long match: /api/user")
})
 
// 请求 /api -> 匹配 /api
// 请求 /api/user -> 匹配 /api/user(最长匹配)
 
// 规则3:模式必须以 / 结尾才能匹配子路径
mux.HandleFunc("/static", func(w http.ResponseWriter, r *http.Request) {
    fmt.Fprint(w, "No slash: /static")
})
 
mux.HandleFunc("/static/", func(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "With slash: %s", r.URL.Path)
})
 
// 请求 /static -> 精确匹配 /static
// 请求 /static/ -> 匹配 /static/
// 请求 /static/file.txt -> 匹配 /static/
// 请求 /staticfile -> 精确匹配 /static(如果存在)或404

RESTful API路由设计

// 使用gorilla/mux设计RESTful API
router := mux.NewRouter()
 
// 用户资源
users := router.PathPrefix("/api/users").Subrouter()
users.HandleFunc("", listUsersHandler).Methods("GET")       // GET /api/users
users.HandleFunc("", createUserHandler).Methods("POST")     // POST /api/users
users.HandleFunc("/{id}", getUserHandler).Methods("GET")    // GET /api/users/123
users.HandleFunc("/{id}", updateUserHandler).Methods("PUT") // PUT /api/users/123
users.HandleFunc("/{id}", deleteUserHandler).Methods("DELETE") // DELETE /api/users/123
 
// 文章资源
articles := router.PathPrefix("/api/articles").Subrouter()
articles.HandleFunc("", listArticlesHandler).Methods("GET")
articles.HandleFunc("", createArticleHandler).Methods("POST")
articles.HandleFunc("/{id}", getArticleHandler).Methods("GET")
articles.HandleFunc("/{id}", updateArticleHandler).Methods("PUT")
articles.HandleFunc("/{id}", deleteArticleHandler).Methods("DELETE")
articles.HandleFunc("/{id}/comments", listCommentsHandler).Methods("GET") // GET /api/articles/123/comments
articles.HandleFunc("/{id}/comments", createCommentHandler).Methods("POST") // POST /api/articles/123/comments
 
// 路径参数获取
func getUserHandler(w http.ResponseWriter, r *http.Request) {
    vars := mux.Vars(r)
    userID := vars["id"]
    fmt.Fprintf(w, "User ID: %s", userID)
}
 
// 查询参数获取
func listUsersHandler(w http.ResponseWriter, r *http.Request) {
    page := r.URL.Query().Get("page")
    limit := r.URL.Query().Get("limit")
    fmt.Fprintf(w, "Page: %s, Limit: %s", page, limit)
}

Q4: 如何使用context包管理请求上下文?

解题思路

  1. 解释context的基本概念和用途
  2. 演示创建不同类型的context(Background, TODO, WithCancel, WithTimeout, WithDeadline, WithValue)
  3. 展示context在HTTP请求中的应用
  4. 说明context的传播机制

代码实现

package main
 
import (
    "context"
    "fmt"
    "time"
)
 
func worker(ctx context.Context, id int) {
    for {
        select {
        case <-ctx.Done():
            fmt.Printf("Worker %d: %v\n", id, ctx.Err())
            return
        default:
            fmt.Printf("Worker %d: working...\n", id)
            time.Sleep(500 * time.Millisecond)
        }
    }
}
 
func main() {
    ctx, cancel := context.WithCancel(context.Background())
    
    go worker(ctx, 1)
    go worker(ctx, 2)
    
    time.Sleep(2 * time.Second)
    cancel()
    time.Sleep(1 * time.Second)
    
    ctx, cancel = context.WithTimeout(context.Background(), 2*time.Second)
    defer cancel()
    
    select {
    case <-time.After(3 * time.Second):
        fmt.Println("Operation completed")
    case <-ctx.Done():
        fmt.Println("Timeout:", ctx.Err())
    }
    
    ctx = context.WithValue(context.Background(), "userID", 12345)
    userID := ctx.Value("userID")
    fmt.Println("User ID:", userID)
    
    deadline := time.Now().Add(2 * time.Second)
    ctx, cancel = context.WithDeadline(context.Background(), deadline)
    defer cancel()
    
    select {
    case <-time.After(3 * time.Second):
        fmt.Println("Operation completed")
    case <-ctx.Done():
        fmt.Println("Deadline exceeded:", ctx.Err())
    }
}

常见错误分析

错误1:忘记调用cancel()函数

问题说明: 当创建带有取消功能的context时(如WithCancel、WithTimeout、WithDeadline),必须调用返回的cancel函数来释放资源。如果不调用,可能导致资源泄漏。

错误示例

// 错误1:创建WithCancel但不调用cancel
func processData() {
    ctx, cancel := context.WithCancel(context.Background())
    // 忘记调用cancel!
    
    go func() {
        select {
        case <-ctx.Done():
            return
        case <-time.After(5 * time.Second):
            fmt.Println("Done")
        }
    }()
    // goroutine永远不会退出,资源泄漏
}
 
// 错误2:创建WithTimeout但不调用cancel
func fetchWithTimeout() {
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    // 忘记defer cancel()
    
    resp, err := http.GetWithContext(ctx, "https://api.example.com")
    if err != nil {
        return
    }
    defer resp.Body.Close()
    // 即使超时,cancel也不会被调用
}
 
// 错误3:在循环中创建context但不调用cancel
func processItems(items []string) {
    for _, item := range items {
        ctx, cancel := context.WithTimeout(context.Background(), time.Second)
        // 忘记调用cancel
        processItem(ctx, item)
    }
}

正确做法

// 正确1:使用defer确保cancel被调用
func processData() {
    ctx, cancel := context.WithCancel(context.Background())
    defer cancel()  // 确保资源被释放
    
    go func() {
        select {
        case <-ctx.Done():
            return
        case <-time.After(5 * time.Second):
            fmt.Println("Done")
        }
    }()
}
 
// 正确2:WithTimeout使用defer cancel
func fetchWithTimeout() error {
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()  // 确保资源被释放
    
    resp, err := http.GetWithContext(ctx, "https://api.example.com")
    if err != nil {
        return err
    }
    defer resp.Body.Close()
    
    return nil
}
 
// 正确3:在循环中正确处理cancel
func processItems(items []string) error {
    for _, item := range items {
        ctx, cancel := context.WithTimeout(context.Background(), time.Second)
        defer cancel()  // 每次循环都会调用
        
        if err := processItem(ctx, item); err != nil {
            return err
        }
    }
    return nil
}
 
// 正确4:提前取消context
func processDataWithEarlyCancel() {
    ctx, cancel := context.WithCancel(context.Background())
    
    go func() {
        select {
        case <-ctx.Done():
            return
        case <-time.After(5 * time.Second):
            fmt.Println("Done")
        }
    }()
    
    // 某个条件满足时提前取消
    if someCondition {
        cancel()  // 提前取消
        return
    }
    
    cancel()  // 正常取消
}

cancel函数的作用

// cancel函数的主要作用:
// 1. 释放context相关的资源
// 2. 向所有监听该context的goroutine发送取消信号
// 3. 防止goroutine泄漏
 
// 示例:展示cancel的重要性
func demonstrateCancel() {
    // 不调用cancel的情况
    ctx1, _ := context.WithCancel(context.Background())
    go func() {
        <-ctx1.Done()
        fmt.Println("Goroutine 1 stopped")
    }()
    // goroutine 1永远不会停止,资源泄漏
    
    // 调用cancel的情况
    ctx2, cancel2 := context.WithCancel(context.Background())
    go func() {
        <-ctx2.Done()
        fmt.Println("Goroutine 2 stopped")
    }()
    cancel2()  // goroutine 2会停止
}

错误2:在context中存储敏感信息

问题说明: context.Value()虽然可以存储任意值,但不应该用于存储敏感信息(如密码、token、私钥等),因为context会在整个调用链中传递,容易泄露。

错误示例

// 错误1:在context中存储密码
func loginHandler(w http.ResponseWriter, r *http.Request) {
    username := r.FormValue("username")
    password := r.FormValue("password")
    
    // 错误:将密码存储在context中
    ctx := context.WithValue(r.Context(), "password", password)
    
    // context会在日志、错误处理等地方被打印
    log.Printf("Login request: %+v", ctx)  // 密码可能被泄露
}
 
// 错误2:在context中存储token
func authMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        token := r.Header.Get("Authorization")
        
        // 错误:将token存储在context中
        ctx := context.WithValue(r.Context(), "token", token)
        next.ServeHTTP(w, r.WithContext(ctx))
    })
}
 
// 错误3:在context中存储私钥
func signDataHandler(w http.ResponseWriter, r *http.Request) {
    privateKey := getPrivateKey()
    
    // 错误:将私钥存储在context中
    ctx := context.WithValue(r.Context(), "privateKey", privateKey)
    
    signature := signData(ctx, r.FormValue("data"))
    fmt.Fprintf(w, "Signature: %s", signature)
}

正确做法

// 正确1:使用专门的类型存储用户信息
type UserContextKey string
 
const (
    UserIDKey   UserContextKey = "userID"
    UsernameKey UserContextKey = "username"
)
 
func loginHandler(w http.ResponseWriter, r *http.Request) {
    username := r.FormValue("username")
    password := r.FormValue("password")
    
    // 验证密码
    user, err := authenticate(username, password)
    if err != nil {
        http.Error(w, "Invalid credentials", http.StatusUnauthorized)
        return
    }
    
    // 只存储用户ID,不存储密码
    ctx := context.WithValue(r.Context(), UserIDKey, user.ID)
    ctx = context.WithValue(ctx, UsernameKey, user.Username)
    
    // 继续处理请求
    processRequest(w, r.WithContext(ctx))
}
 
// 正确2:使用结构体存储认证信息
type AuthInfo struct {
    UserID    int64
    Username  string
    Roles     []string
    ExpiresAt time.Time
}
 
type AuthInfoKey struct{}
 
func authMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        token := r.Header.Get("Authorization")
        
        // 验证token并获取用户信息
        authInfo, err := validateToken(token)
        if err != nil {
            http.Error(w, "Invalid token", http.StatusUnauthorized)
            return
        }
        
        // 存储认证信息,不存储原始token
        ctx := context.WithValue(r.Context(), AuthInfoKey{}, authInfo)
        next.ServeHTTP(w, r.WithContext(ctx))
    })
}
 
// 正确3:敏感信息使用函数参数传递
func signDataHandler(w http.ResponseWriter, r *http.Request) {
    data := r.FormValue("data")
    
    // 直接传递私钥,不通过context
    privateKey := getPrivateKey()
    signature := signData(privateKey, data)
    
    fmt.Fprintf(w, "Signature: %s", signature)
}
 
// 正确4:使用环境变量或配置文件存储敏感信息
type Config struct {
    DatabaseURL string
    APIKey      string
    SecretKey   string
}
 
var config Config
 
func init() {
    config.DatabaseURL = os.Getenv("DATABASE_URL")
    config.APIKey = os.Getenv("API_KEY")
    config.SecretKey = os.Getenv("SECRET_KEY")
}

context.Value的使用原则

// 原则1:只存储请求范围的元数据
// ✅ 正确
ctx := context.WithValue(ctx, "requestID", "12345")
ctx := context.WithValue(ctx, "userID", 123)
ctx := context.WithValue(ctx, "traceID", "abc-123")
 
// ❌ 错误
ctx := context.WithValue(ctx, "password", "secret123")
ctx := context.WithValue(ctx, "token", "eyJhbGciOiJIUzI1NiIs...")
ctx := context.WithValue(ctx, "privateKey", "-----BEGIN PRIVATE KEY-----")
 
// 原则2:使用自定义类型作为key,避免冲突
type ContextKey string
 
const (
    RequestIDKey ContextKey = "requestID"
    UserIDKey    ContextKey = "userID"
)
 
// ✅ 正确
ctx := context.WithValue(ctx, RequestIDKey, "12345")
 
// ❌ 错误(容易与其他包冲突)
ctx := context.WithValue(ctx, "requestID", "12345")
 
// 原则3:context.Value应该是可选的
func processRequest(ctx context.Context) {
    // ✅ 正确:检查值是否存在
    if userID, ok := ctx.Value(UserIDKey).(int64); ok {
        fmt.Printf("User ID: %d\n", userID)
    }
    
    // ❌ 错误:假设值一定存在
    userID := ctx.Value(UserIDKey).(int64)
    fmt.Printf("User ID: %d\n", userID)
}

错误3:过度使用context.Value

问题说明: context.Value虽然方便,但不应该作为函数参数的替代品。过度使用context.Value会导致代码难以理解和维护,应该优先使用函数参数传递数据。

错误示例

// 错误1:将所有参数都放在context中
func processData(ctx context.Context) {
    userID := ctx.Value("userID").(int64)
    data := ctx.Value("data").([]byte)
    config := ctx.Value("config").(*Config)
    
    // 函数签名不清晰,不知道需要什么参数
    fmt.Printf("Processing data for user %d\n", userID)
}
 
func main() {
    ctx := context.Background()
    ctx = context.WithValue(ctx, "userID", 123)
    ctx = context.WithValue(ctx, "data", []byte("hello"))
    ctx = context.WithValue(ctx, "config", &Config{})
    
    processData(ctx)
}
 
// 错误2:使用context传递业务数据
func createUser(ctx context.Context) error {
    username := ctx.Value("username").(string)
    email := ctx.Value("email").(string)
    password := ctx.Value("password").(string)
    
    // 业务逻辑数据应该通过参数传递
    return db.CreateUser(username, email, password)
}
 
// 错误3:context.Value链式调用过多
func process(ctx context.Context) {
    ctx = context.WithValue(ctx, "step1", "done")
    ctx = context.WithValue(ctx, "step2", "done")
    ctx = context.WithValue(ctx, "step3", "done")
    ctx = context.WithValue(ctx, "step4", "done")
    ctx = context.WithValue(ctx, "step5", "done")
    
    // context变得臃肿,难以维护
    nextStep(ctx)
}

正确做法

// 正确1:使用函数参数传递业务数据
type UserData struct {
    Username string
    Email    string
    Password string
}
 
func createUser(data UserData) error {
    // 函数签名清晰,知道需要什么参数
    return db.CreateUser(data.Username, data.Email, data.Password)
}
 
func main() {
    data := UserData{
        Username: "alice",
        Email:    "alice@example.com",
        Password: "secret",
    }
    
    if err := createUser(data); err != nil {
        log.Fatal(err)
    }
}
 
// 正确2:context只用于传递请求范围的元数据
type RequestIDKey struct{}
type UserIDKey struct{}
 
func processRequest(ctx context.Context, data []byte, config *Config) error {
    requestID := ctx.Value(RequestIDKey{}).(string)
    userID := ctx.Value(UserIDKey{}).(int64)
    
    // 业务数据通过参数传递
    log.Printf("Processing request %s for user %d\n", requestID, userID)
    
    return process(data, config)
}
 
// 正确3:使用结构体组织相关数据
type RequestContext struct {
    RequestID string
    UserID    int64
    TraceID   string
    StartTime time.Time
}
 
func processRequest(ctx context.Context, reqCtx RequestContext, data []byte) error {
    log.Printf("Processing request %s for user %d\n", reqCtx.RequestID, reqCtx.UserID)
    
    return process(data)
}
 
// 正确4:context.Value的使用场景
// 场景1:传递请求ID(用于日志追踪)
type RequestIDKey struct{}
 
func loggingMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        requestID := generateRequestID()
        ctx := context.WithValue(r.Context(), RequestIDKey{}, requestID)
        
        log.Printf("Request %s: %s %s", requestID, r.Method, r.URL.Path)
        next.ServeHTTP(w, r.WithContext(ctx))
    })
}
 
// 场景2:传递用户信息(用于认证)
type UserIDKey struct{}
 
func authMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        userID := authenticate(r)
        if userID == 0 {
            http.Error(w, "Unauthorized", http.StatusUnauthorized)
            return
        }
        
        ctx := context.WithValue(r.Context(), UserIDKey{}, userID)
        next.ServeHTTP(w, r.WithContext(ctx))
    })
}
 
// 场景3:传递追踪信息(用于分布式追踪)
type TraceIDKey struct{}
 
func tracingMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        traceID := r.Header.Get("X-Trace-ID")
        if traceID == "" {
            traceID = generateTraceID()
        }
        
        ctx := context.WithValue(r.Context(), TraceIDKey{}, traceID)
        next.ServeHTTP(w, r.WithContext(ctx))
    })
}
 
// 场景4:传递取消信号(用于超时控制)
func fetchData(ctx context.Context) error {
    ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
    defer cancel()
    
    // 使用context控制超时
    resp, err := http.GetWithContext(ctx, "https://api.example.com")
    if err != nil {
        return err
    }
    defer resp.Body.Close()
    
    return nil
}

context.Value vs 函数参数对比

// ❌ 错误:过度使用context.Value
func process(ctx context.Context) {
    userID := ctx.Value("userID").(int64)
    username := ctx.Value("username").(string)
    email := ctx.Value("email").(string)
    data := ctx.Value("data").([]byte)
    config := ctx.Value("config").(*Config)
    
    // 函数签名不清晰,不知道需要什么参数
    // 类型不安全,容易panic
    // 难以测试,需要构造复杂的context
}
 
// ✅ 正确:使用函数参数
type User struct {
    ID       int64
    Username string
    Email    string
}
 
type ProcessConfig struct {
    Timeout time.Duration
    Retry   int
}
 
func process(user User, data []byte, config ProcessConfig) error {
    // 函数签名清晰,知道需要什么参数
    // 类型安全,编译时检查
    // 容易测试,直接传递参数
    
    return nil
}
 
// ✅ 正确:context只用于传递元数据
func process(ctx context.Context, user User, data []byte, config ProcessConfig) error {
    requestID := ctx.Value(RequestIDKey{}).(string)
    
    log.Printf("Processing request %s for user %d\n", requestID, user.ID)
    
    return nil
}

总结:context的最佳实践

// 1. context应该作为函数的第一个参数
func process(ctx context.Context, data []byte) error {
    // ✅ 正确
    return nil
}
 
// 2. 不要将context存储在结构体中
type Processor struct {
    ctx context.Context  // ❌ 错误
}
 
// 3. context应该是可选的,函数应该能在没有context的情况下工作
func process(data []byte) error {
    return processWithContext(context.Background(), data)
}
 
func processWithContext(ctx context.Context, data []byte) error {
    // 使用context控制超时、取消等
    return nil
}
 
// 4. context.Value只用于传递请求范围的元数据
// ✅ 正确:requestID, userID, traceID
// ❌ 错误:password, token, privateKey, 业务数据
 
// 5. 使用自定义类型作为context key
type ContextKey string
 
const (
    RequestIDKey ContextKey = "requestID"
    UserIDKey    ContextKey = "userID"
)
 
// 6. 检查context.Value是否存在
if userID, ok := ctx.Value(UserIDKey).(int64); ok {
    fmt.Printf("User ID: %d\n", userID)
}
 
// 7. 总是调用cancel函数
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
 
// 8. 不要过度使用context.Value
// 优先使用函数参数传递数据