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包进行字符串操作?
解题思路:
- 列举strings包中常用的函数
- 演示字符串查找、替换、分割、连接等操作
- 说明字符串大小写转换和空格处理
- 展示实际应用场景
代码实现:
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包处理时间?
解题思路:
- 演示获取当前时间
- 说明时间格式化的特殊规则(2006-01-02 15:04:05)
- 演示时间解析和计算
- 展示时区处理
代码实现:
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常用格式化对照表:
| 格式字符串 | 示例输出 | 说明 |
|---|---|---|
2006 | 2024 | 4位年份 |
06 | 24 | 2位年份 |
01 | 01 | 月份(01-12) |
1 | 1 | 月份(1-12) |
Jan | Jan | 月份缩写 |
January | January | 月份全称 |
02 | 15 | 日期(01-31) |
2 | 15 | 日期(1-31) |
15 | 14 | 小时(00-23) |
03 | 02 | 小时(01-12) |
04 | 30 | 分钟(00-59) |
05 | 45 | 秒(00-59) |
.000 | .000 | 毫秒 |
.000000 | .000000 | 微秒 |
.000000000 | .000000000 | 纳秒 |
PM | PM | AM/PM标记 |
MST | CST | 时区缩写 |
-0700 | +0800 | 时区偏移 |
Monday | Monday | 星期全称 |
Mon | Mon | 星期缩写 |
实际应用场景:
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服务器?
解题思路:
- 演示创建简单的HTTP服务器
- 说明路由处理和参数获取
- 展示中间件的使用
- 演示静态文件服务
代码实现:
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(如果存在)或404RESTful 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包管理请求上下文?
解题思路:
- 解释context的基本概念和用途
- 演示创建不同类型的context(Background, TODO, WithCancel, WithTimeout, WithDeadline, WithValue)
- 展示context在HTTP请求中的应用
- 说明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
// 优先使用函数参数传递数据