commit 7f4527d501a7381ca19bad8c3b780904f6ed67be Author: liumangmang Date: Fri Dec 26 17:56:02 2025 +0800 初始化Go学习项目 diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..35410ca --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# 默认忽略的文件 +/shelf/ +/workspace.xml +# 基于编辑器的 HTTP 客户端请求 +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/learn-golang.iml b/.idea/learn-golang.iml new file mode 100644 index 0000000..5e764c4 --- /dev/null +++ b/.idea/learn-golang.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..5d5319e --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/go-atomic-cpu/atomic_cas.go b/go-atomic-cpu/atomic_cas.go new file mode 100644 index 0000000..8c34237 --- /dev/null +++ b/go-atomic-cpu/atomic_cas.go @@ -0,0 +1,16 @@ +package main + +import ( + "fmt" + "sync/atomic" +) + +func main() { + var value int64 = 0 + + success := atomic.CompareAndSwapInt64(&value, 0, 42) + fmt.Println("第一次 CAS 是否成功:", success, "当前值:", value) + + success = atomic.CompareAndSwapInt64(&value, 0, 100) + fmt.Println("第二次 CAS 是否成功:", success, "当前值:", value) +} diff --git a/go-atomic-cpu/atomic_counter.go b/go-atomic-cpu/atomic_counter.go new file mode 100644 index 0000000..5d33a59 --- /dev/null +++ b/go-atomic-cpu/atomic_counter.go @@ -0,0 +1,26 @@ +package main + +import ( + "fmt" + "sync" + "sync/atomic" +) + +func main() { + var wg sync.WaitGroup + var counter int64 // 注意必须是 int64/uint64 等特定类型 + + for i := 0; i < 1000; i++ { + wg.Add(1) + go func() { + defer wg.Done() + for j := 0; j < 1000; j++ { + atomic.AddInt64(&counter, 1) + } + }() + } + + wg.Wait() + fmt.Println("期待的结果:", 1000*1000) + fmt.Println("实际结果:", counter) +} diff --git a/go-atomic-cpu/cpu_busy_loop.go b/go-atomic-cpu/cpu_busy_loop.go new file mode 100644 index 0000000..589c482 --- /dev/null +++ b/go-atomic-cpu/cpu_busy_loop.go @@ -0,0 +1,24 @@ +package main + +import ( + "fmt" + "sync/atomic" + "time" +) + +func main() { + var stop int32 = 0 + + go func() { + for atomic.LoadInt32(&stop) == 0 { + // 忙等:什么也不干,不让出 CPU + } + fmt.Println("worker 退出") + }() + + time.Sleep(2 * time.Second) + fmt.Println("main: 设置 stop=1") + atomic.StoreInt32(&stop, 1) + + time.Sleep(1 * time.Second) +} diff --git a/go-atomic-cpu/cpu_channel_wait.go b/go-atomic-cpu/cpu_channel_wait.go new file mode 100644 index 0000000..238d2a1 --- /dev/null +++ b/go-atomic-cpu/cpu_channel_wait.go @@ -0,0 +1,23 @@ +package main + +import ( + "fmt" + "time" +) + +func main() { + stop := make(chan struct{}) + + go func() { + select { + case <-stop: + fmt.Println("worker 收到停止信号,退出") + } + }() + + time.Sleep(2 * time.Second) + fmt.Println("main: 关闭 stop channel") + close(stop) + + time.Sleep(1 * time.Second) +} diff --git a/go-atomic-cpu/go.mod b/go-atomic-cpu/go.mod new file mode 100644 index 0000000..4071b3d --- /dev/null +++ b/go-atomic-cpu/go.mod @@ -0,0 +1,3 @@ +module go-atomic-cpu + +go 1.22.2 diff --git a/go-basics/go.mod b/go-basics/go.mod new file mode 100644 index 0000000..bfa36ea --- /dev/null +++ b/go-basics/go.mod @@ -0,0 +1,3 @@ +module go-basics + +go 1.22.2 diff --git a/go-basics/main.go b/go-basics/main.go new file mode 100644 index 0000000..744208b --- /dev/null +++ b/go-basics/main.go @@ -0,0 +1,82 @@ +package main + +import "fmt" + +func main() { + // ========== 1. 基本类型 ========== + var a int = 42 + var b float64 = 3.14 + var c bool = true + var d string = "Hello, Go!" + + fmt.Println("基本类型:") + fmt.Printf("int: %d\n", a) + fmt.Printf("float64: %.2f\n", b) + fmt.Printf("bool: %t\n", c) + fmt.Printf("string: %s\n", d) + + // ========== 2. 变量声明方式 ========== + // 方式1:var 声明(可带或不带初始值) + var x int + x = 100 + var y = 200 // 类型自动推导 + + // 方式2:批量声明 + var ( + name = "Liu" + age = 30 + height = 175.5 + isAdmin = true + ) + + fmt.Println("\n变量声明:") + fmt.Println("x =", x, ", y =", y) + fmt.Println("name:", name, "age:", age, "height:", height, "isAdmin:", isAdmin) + + // ========== 3. := 简短声明(仅在函数内部可用)========== + z := 999 // 自动推导为 int + message := "Use := !" // 自动推导为 string + fmt.Println("\n简短声明 (:=):") + fmt.Println("z =", z, ", message =", message) + + // 注意::= 必须至少声明一个新变量 + // 下面这行会报错:no new variables on left side of := + //z := 888 // ❌ 错误! + + // 但可以这样混合新旧变量: + w, z := "new", 888 // w 是新的,z 是重新赋值(shadowing) + fmt.Println("w =", w, ", z =", z) + + // ========== 4. 指针 ========== + p := &a // p 是指向 a 的指针(*int 类型) + fmt.Println("\n指针:") + fmt.Printf("a 的值: %d\n", a) + fmt.Printf("a 的地址: %p\n", &a) + fmt.Printf("p 的值(即 a 的地址): %p\n", p) + fmt.Printf("*p(解引用): %d\n", *p) + + *p = 1000 // 通过指针修改 a 的值 + fmt.Println("修改后 a =", a) + + // ========== 5. new vs make ========== + // new(T) → 分配零值内存,返回 *T + ptrInt := new(int) // *int,值为 0 + *ptrInt = 42 + fmt.Println("\nnew(int):", *ptrInt) + + // make 仅用于 slice, map, chan —— 返回初始化后的 T(不是指针!) + s := make([]int, 3) // 长度为3的 slice,元素为 [0 0 0] + m := make(map[string]int) // 空 map + ch := make(chan int, 2) // 缓冲通道 + + s[0] = 10 + m["count"] = 1 + ch <- 100 + + fmt.Println("make(slice):", s) + fmt.Println("make(map):", m) + fmt.Println("make(chan) 示例(略去接收)") + + // ⚠️ 不能对 slice/map/chan 使用 new! + // bad := new([]int) // 这是一个 *[]int,但底层数组未初始化,不能直接用! +} diff --git a/go-channel-practice/buffered.go b/go-channel-practice/buffered.go new file mode 100644 index 0000000..12e77f0 --- /dev/null +++ b/go-channel-practice/buffered.go @@ -0,0 +1,35 @@ +package main + +import ( + "fmt" + "time" +) + +func main() { + ch := make(chan int, 3) // 容量为 3 的有缓冲 channel + + fmt.Println("[Main] 开始发送 3 个元素...") + ch <- 1 + fmt.Println("[Main] 已发送 1") + ch <- 2 + fmt.Println("[Main] 已发送 2") + ch <- 3 + fmt.Println("[Main] 已发送 3 (已满)") + + // 再发送一个会怎样? + go func() { + fmt.Println("[Sender] 尝试发送第 4 个元素(会阻塞,直到有接收者)...") + ch <- 4 + fmt.Println("[Sender] 第 4 个元素发送成功") + }() + + time.Sleep(1 * time.Second) + + fmt.Println("[Main] 开始接收...") + for i := 0; i < 4; i++ { + v := <-ch + fmt.Println("[Main] 收到:", v) + } + + fmt.Println("[Main] 程序结束") +} diff --git a/go-channel-practice/directional.go b/go-channel-practice/directional.go new file mode 100644 index 0000000..6c8b86a --- /dev/null +++ b/go-channel-practice/directional.go @@ -0,0 +1,28 @@ +package main + +import "fmt" + +// 只负责发送数据 +func producer(out chan<- int) { + for i := 1; i <= 5; i++ { + fmt.Println("[Producer] 发送:", i) + out <- i + } + fmt.Println("[Producer] 关闭 channel") + close(out) // 只有发送方才能关闭 +} + +// 只负责接收数据 +func consumer(in <-chan int) { + for v := range in { // 直到 channel 被关闭 + fmt.Println("[Consumer] 接收:", v) + } + fmt.Println("[Consumer] channel 已关闭,接收结束") +} + +func main() { + ch := make(chan int) + + go producer(ch) // ch 在这里被当作 只发送 channel 使用 + consumer(ch) // ch 在这里被当作 只接收 channel 使用 +} diff --git a/go-channel-practice/go.mod b/go-channel-practice/go.mod new file mode 100644 index 0000000..5607a6e --- /dev/null +++ b/go-channel-practice/go.mod @@ -0,0 +1,3 @@ +module go-channel-practice + +go 1.22.2 diff --git a/go-channel-practice/pipeline.go b/go-channel-practice/pipeline.go new file mode 100644 index 0000000..8a700a5 --- /dev/null +++ b/go-channel-practice/pipeline.go @@ -0,0 +1,37 @@ +package main + +import "fmt" + +// 生产者:产生 1~5 +func producer1(out chan<- int) { + for i := 1; i <= 5; i++ { + fmt.Println("[Producer] 发送:", i) + out <- i + } + close(out) +} + +// 处理者:把数字放大 10 倍 +func multiplier(in <-chan int, out chan<- int) { + for v := range in { + fmt.Println("[Multiplier] 接收:", v) + out <- v * 10 + } + close(out) +} + +// 消费者:打印结果 +func consumer1(in <-chan int) { + for v := range in { + fmt.Println("[Consumer] 最终结果:", v) + } +} + +func main() { + ch1 := make(chan int, 2) // 有缓冲,减轻 producer 阻塞 + ch2 := make(chan int, 2) + + go producer1(ch1) + go multiplier(ch1, ch2) + consumer1(ch2) +} diff --git a/go-channel-practice/unbuffered.go b/go-channel-practice/unbuffered.go new file mode 100644 index 0000000..a8d0f37 --- /dev/null +++ b/go-channel-practice/unbuffered.go @@ -0,0 +1,24 @@ +package main + +import ( + "fmt" + "time" +) + +func main() { + ch := make(chan string) // 无缓冲 channel + + go func() { + fmt.Println("[Sender] 准备发送数据...") + ch <- "hello from goroutine" // 这里会阻塞,直到有人接收 + fmt.Println("[Sender] 数据发送完毕") + }() + + time.Sleep(1 * time.Second) + fmt.Println("[Main] 1 秒后开始接收数据...") + + msg := <-ch // 接收数据,同时解除发送方阻塞 + fmt.Println("[Main] 收到:", msg) + + fmt.Println("[Main] 程序结束") +} diff --git a/go-context-practice/cancel_basic.go b/go-context-practice/cancel_basic.go new file mode 100644 index 0000000..add555f --- /dev/null +++ b/go-context-practice/cancel_basic.go @@ -0,0 +1,35 @@ +package main + +import ( + "context" + "fmt" + "time" +) + +// 模拟一个可被取消的循环任务 +func worker(ctx context.Context, name string) { + for { + select { + case <-ctx.Done(): + fmt.Println(name, "收到取消信号:", ctx.Err()) + return + default: + fmt.Println(name, "还在干活...") + time.Sleep(500 * time.Millisecond) + } + } +} + +func main() { + ctx, cancel := context.WithCancel(context.Background()) + + go worker(ctx, "worker-1") + go worker(ctx, "worker-2") + + time.Sleep(2 * time.Second) + fmt.Println("main: 决定取消所有 worker") + cancel() // 发出取消信号 + + time.Sleep(1 * time.Second) + fmt.Println("main 结束") +} diff --git a/go-context-practice/derived_context.go b/go-context-practice/derived_context.go new file mode 100644 index 0000000..d0dda83 --- /dev/null +++ b/go-context-practice/derived_context.go @@ -0,0 +1,42 @@ +package main + +import ( + "context" + "fmt" + "time" +) + +func subTask(ctx context.Context, name string, d time.Duration) { + select { + case <-ctx.Done(): + fmt.Println(name, "提前被取消:", ctx.Err()) + case <-time.After(d): + fmt.Println(name, "完成,用时", d) + } +} + +func mainTask(ctx context.Context) { + // 从上游 ctx 派生两个子 context + ctx1, cancel1 := context.WithCancel(ctx) + defer cancel1() + + ctx2, cancel2 := context.WithTimeout(ctx, 2*time.Second) + defer cancel2() + + go subTask(ctx1, "subTask-1", 5*time.Second) + go subTask(ctx2, "subTask-2", 5*time.Second) + + time.Sleep(1 * time.Second) + fmt.Println("mainTask: 主动取消 subTask-1 的 ctx1") + cancel1() + + // 等待一会儿,看 subTask-2 是否因超时被取消 + time.Sleep(3 * time.Second) +} + +func main() { + root := context.Background() + fmt.Println("开始 mainTask...") + mainTask(root) + fmt.Println("main 结束") +} diff --git a/go-context-practice/go.mod b/go-context-practice/go.mod new file mode 100644 index 0000000..73a8e73 --- /dev/null +++ b/go-context-practice/go.mod @@ -0,0 +1,3 @@ +module go-context-practice + +go 1.22.2 diff --git a/go-context-practice/timeout_with_context.go b/go-context-practice/timeout_with_context.go new file mode 100644 index 0000000..b9c45a2 --- /dev/null +++ b/go-context-practice/timeout_with_context.go @@ -0,0 +1,37 @@ +package main + +import ( + "context" + "fmt" + "time" +) + +// 模拟一个可能很慢的操作 +func slowJob(ctx context.Context) error { + for i := 1; i <= 5; i++ { + select { + case <-ctx.Done(): + fmt.Println("slowJob 被取消:", ctx.Err()) + return ctx.Err() + default: + fmt.Println("slowJob 进行中 step", i) + time.Sleep(1 * time.Second) + } + } + fmt.Println("slowJob 正常完成") + return nil +} + +func main() { + // 最多给 slowJob 3 秒时间 + ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) + defer cancel() + + fmt.Println("开始执行 slowJob,超时时间 3 秒...") + if err := slowJob(ctx); err != nil { + fmt.Println("结束,原因:", err) + return + } + + fmt.Println("结束:正常完成") +} diff --git a/go-crawler/crawler_basic.go b/go-crawler/crawler_basic.go new file mode 100644 index 0000000..4e1877a --- /dev/null +++ b/go-crawler/crawler_basic.go @@ -0,0 +1,81 @@ +package main + +import ( + "fmt" + "io" + "net/http" + "regexp" + "sync" +) + +func main() { + urls := []string{ + "https://golang.org", + "https://go.dev", + "https://www.baidu.com", + "https://www.bing.com", + } + + jobs := make(chan string) + results := make(chan string) + + var wg sync.WaitGroup + workerCount := 3 + + //启动 worker + for i := 0; i < workerCount; i++ { + wg.Add(1) + go worker(i, jobs, results, &wg) + } + + //发送任务 + go func() { + for _, url := range urls { + jobs <- url + } + close(jobs) // 关闭 channel + }() + + //单独goroutine 负责所有 worker结束后 关闭 results + go func() { + wg.Wait() + close(results) + }() + + for res := range results { + fmt.Println(res) + } +} + +// worker 从 jobs 读取 URL,写结果到 results +func worker(id int, jobs <-chan string, results chan<- string, wg *sync.WaitGroup) { + defer wg.Done() + for url := range jobs { + title, err := fetchTitle(url) + if err != nil { + results <- fmt.Sprintf("[worker-%d] %s ERROR: %v", id, url, err) + continue + } + results <- fmt.Sprintf("[worker-%d] %s => %s", id, url, title) + } +} + +func fetchTitle(url string) (string, error) { + resp, err := http.Get(url) + if err != nil { + return "", err + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + return "", err + } + + re := regexp.MustCompile(`(.+)`) + matches := re.FindSubmatch(body) + if len(matches) >= 2 { + return string(matches[1]), nil + } + return "(no title)", nil +} diff --git a/go-crawler/go.mod b/go-crawler/go.mod new file mode 100644 index 0000000..298dc9b --- /dev/null +++ b/go-crawler/go.mod @@ -0,0 +1,3 @@ +module go-crawler + +go 1.22.2 diff --git a/go-error-handling/go.mod b/go-error-handling/go.mod new file mode 100644 index 0000000..569f4a5 --- /dev/null +++ b/go-error-handling/go.mod @@ -0,0 +1,3 @@ +module go-error-handling + +go 1.22.2 diff --git a/go-error-handling/main.go b/go-error-handling/main.go new file mode 100644 index 0000000..29d2d3e --- /dev/null +++ b/go-error-handling/main.go @@ -0,0 +1,122 @@ +package main + +import ( + "errors" + "fmt" + "os" +) + +// ========== 1. 自定义错误 + fmt.Errorf 封装 ========== +func divide(a, b float64) (float64, error) { + if b == 0 { + // 使用 fmt.Errorf 包装原始错误,添加上下文 + return 0, fmt.Errorf("divide by zero: cannot divide %.2f by %.2f", a, b) + } + return a / b, nil +} + +// 更复杂的错误链(Go 1.13+ 支持 %w) +var ErrNegativeInput = errors.New("input must be non-negative") + +func sqrt(x float64) (float64, error) { + if x < 0 { + // 使用 %w 包装错误,支持 errors.Is 和 errors.As + return 0, fmt.Errorf("invalid input for sqrt: %w", ErrNegativeInput) + } + return x * x, nil // 注意:这里故意写成平方,方便测试 +} + +// ========== 2. defer 的典型用途 ========== +func readFile(filename string) error { + fmt.Println("尝试打开文件:", filename) + file, err := os.Open(filename) + if err != nil { + return fmt.Errorf("无法打开文件 %s: %w", filename, err) + } + // defer 确保文件在函数退出前关闭(即使 panic) + defer func() { + fmt.Println("defer: 关闭文件") + file.Close() + }() + + // 模拟读取 + fmt.Println("文件已打开,正在读取...") + return nil +} + +// ========== 3. panic 与 recover ========== +func riskyFunction(n int) { + defer func() { + if r := recover(); r != nil { + fmt.Printf("recover 捕获到 panic: %v\n", r) + } + }() + if n < 0 { + panic("n 不能为负数!") + } + fmt.Println("riskyFunction 正常执行,n =", n) +} + +// recover 只在 defer 中有效! +func noRecover() { + // ❌ 这样写无法捕获 panic + recover() + panic("不会被捕获") +} + +// ========== 4. 综合示例:安全计算 ========== +func safeCompute(a, b float64) { + defer fmt.Println("safeCompute 结束\n---") + + fmt.Printf("计算 %.2f / %.2f\n", a, b) + result, err := divide(a, b) + if err != nil { + fmt.Println("错误:", err) + return + } + fmt.Printf("结果: %.2f\n", result) + + fmt.Println("尝试对结果开方(实际是平方)") + sq, err := sqrt(result) + if err != nil { + if errors.Is(err, ErrNegativeInput) { + fmt.Println("检测到特定错误:输入为负") + } + fmt.Println("sqrt 错误:", err) + return + } + fmt.Printf("平方结果: %.2f\n", sq) +} + +// ========== 主函数 ========== +func main() { + fmt.Println("=== 1. 基本 error 处理 ===") + safeCompute(10, 2) + safeCompute(10, 0) // 触发除零错误 + + fmt.Println("\n=== 2. defer 文件操作 ===") + fileErr := readFile("/nonexistent/file.txt") // 文件不存在,触发错误 + if fileErr != nil { + fmt.Println("文件操作错误:", fileErr) + } + fileErr2 := readFile("/etc/hostname") // 存在的文件(Linux 系统通常有) + if fileErr2 != nil { + fmt.Println("文件操作错误:", fileErr2) + } + fmt.Println("\n=== 3. panic 与 recover ===") + riskyFunction(5) + riskyFunction(-1) // 触发 panic,但被 recover 捕获 + + fmt.Println("\n=== 4. 错误封装与识别 ===") + _, err := sqrt(-4) + if err != nil { + fmt.Println("原始错误信息:", err) + // 使用 errors.Is 判断是否包含特定错误 + if errors.Is(err, ErrNegativeInput) { + fmt.Println("✅ 成功识别自定义错误 ErrNegativeInput") + } + } + + // ⚠️ 取消注释下面这行会 crash(recover 不在 defer 中无效) + //noRecover() +} diff --git a/go-gin-demo/context_demo.go b/go-gin-demo/context_demo.go new file mode 100644 index 0000000..9bde61e --- /dev/null +++ b/go-gin-demo/context_demo.go @@ -0,0 +1,37 @@ +package main + +import ( + "fmt" + + "github.com/gin-gonic/gin" +) + +func main() { + r := gin.Default() + + r.GET("/user", func(c *gin.Context) { + // 获取请求信息 + method := c.Request.Method // GET + path := c.Request.URL.Path // /user + + //打印 + fmt.Printf("Method: %s, Path: %s", method, path) + + // 响应数据 + c.JSON(200, gin.H{"name": "Alice"}) + }) + + // 支持多种响应格式 + r.GET("/text", func(c *gin.Context) { + c.String(200, "Hello, Gin!") // 纯文本 + }) + + r.GET("/yaml", func(c *gin.Context) { + c.YAML(200, gin.H{ + "name": "Bob", + "age": 30, + }) // YAML 格式 + }) + + r.Run(":9999") +} diff --git a/go-gin-demo/go.mod b/go-gin-demo/go.mod new file mode 100644 index 0000000..77a8edf --- /dev/null +++ b/go-gin-demo/go.mod @@ -0,0 +1,31 @@ +module go-gin-demo + +go 1.22.2 + +require ( + github.com/bytedance/sonic v1.9.1 // indirect + github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect + github.com/gabriel-vasile/mimetype v1.4.2 // indirect + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/gin-gonic/gin v1.9.1 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.14.0 // indirect + github.com/goccy/go-json v0.10.2 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.2.4 // indirect + github.com/leodido/go-urn v1.2.4 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pelletier/go-toml/v2 v2.0.8 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.2.11 // indirect + golang.org/x/arch v0.3.0 // indirect + golang.org/x/crypto v0.9.0 // indirect + golang.org/x/net v0.10.0 // indirect + golang.org/x/sys v0.8.0 // indirect + golang.org/x/text v0.9.0 // indirect + google.golang.org/protobuf v1.30.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go-gin-demo/go.sum b/go-gin-demo/go.sum new file mode 100644 index 0000000..e968ca4 --- /dev/null +++ b/go-gin-demo/go.sum @@ -0,0 +1,78 @@ +github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= +github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s= +github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= +github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= +github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= +github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= +github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= +github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js= +github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= +github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= +github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= +github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= +github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= +github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= +github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= +golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= +golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= +golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/go-gin-demo/handler_demo.go b/go-gin-demo/handler_demo.go new file mode 100644 index 0000000..c3939d3 --- /dev/null +++ b/go-gin-demo/handler_demo.go @@ -0,0 +1,51 @@ +package main + +import ( + "github.com/gin-gonic/gin" +) + +// 方式 2:独立函数(推荐,便于单元测试) +func getProduct(c *gin.Context) { + productID := c.Param("id") + c.JSON(200, gin.H{ + "id": productID, + "name": "Product", + }) +} + +// 方式 3:结构体方法(便于依赖注入) +type ProductService struct { + name string +} + +type ProductHandler struct { + service *ProductService +} + +func (h *ProductHandler) Get(c *gin.Context) { + id := c.Param("id") + // 使用 h.service 调用业务逻辑 + c.JSON(200, gin.H{ + "id": id, + "service_name": h.service.name, + }) +} + +func main() { + r := gin.Default() + + // 方式 1:直接定义 + r.GET("/method1", func(c *gin.Context) { + c.JSON(200, gin.H{"method": "inline"}) + }) + + // 方式 2:使用独立函数 + r.GET("/products/:id", getProduct) + + // 方式 3:使用结构体方法 + service := &ProductService{name: "ProductService"} + handler := &ProductHandler{service: service} + r.GET("/products-struct/:id", handler.Get) + + r.Run(":9999") +} diff --git a/go-gin-demo/main.go b/go-gin-demo/main.go new file mode 100644 index 0000000..5d2483a --- /dev/null +++ b/go-gin-demo/main.go @@ -0,0 +1,20 @@ +package main + +import ( + "github.com/gin-gonic/gin" +) + +func main() { + // 创建 Gin 引擎(类似 Spring 的 ApplicationContext) + r := gin.Default() + + // 注册路由和处理器 + r.GET("/hello", func(c *gin.Context) { + c.JSON(200, gin.H{ + "message": "Hello, Gin!", + }) + }) + + // 启动服务器,监听 8080 端口 + r.Run(":8080") +} diff --git a/go-gin-demo/params_form_demo.go b/go-gin-demo/params_form_demo.go new file mode 100644 index 0000000..aea3c8c --- /dev/null +++ b/go-gin-demo/params_form_demo.go @@ -0,0 +1,27 @@ +package main + +import ( + "github.com/gin-gonic/gin" +) + +func main() { + r := gin.Default() + + r.POST("/login", func(c *gin.Context) { + username := c.PostForm("username") // 获取表单参数 + password := c.PostForm("password") + + // 简单验证 + if username == "" || password == "" { + c.JSON(400, gin.H{"error": "username and password required"}) + return + } + + c.JSON(200, gin.H{ + "message": "Login successful", + "username": username, + }) + }) + + r.Run(":8080") +} diff --git a/go-gin-demo/params_json_demo.go b/go-gin-demo/params_json_demo.go new file mode 100644 index 0000000..481ff90 --- /dev/null +++ b/go-gin-demo/params_json_demo.go @@ -0,0 +1,34 @@ +package main + +import ( + "github.com/gin-gonic/gin" +) + +// 定义数据结构体 +type UserJSON struct { + Name string `json:"name"` + Email string `json:"email"` + Age int `json:"age"` +} + +func main() { + r := gin.Default() + + r.POST("/users", func(c *gin.Context) { + var user UserJSON + + // 方式 1:ShouldBindJSON(推荐,错误时不会中断) + if err := c.ShouldBindJSON(&user); err != nil { + c.JSON(400, gin.H{"error": err.Error()}) + return + } + + // 业务逻辑 + c.JSON(201, gin.H{ + "message": "User created", + "data": user, + }) + }) + + r.Run(":9999") +} diff --git a/go-gin-demo/params_path_demo.go b/go-gin-demo/params_path_demo.go new file mode 100644 index 0000000..e2d58c2 --- /dev/null +++ b/go-gin-demo/params_path_demo.go @@ -0,0 +1,24 @@ +package main + +import ( + "github.com/gin-gonic/gin" +) + +func main() { + r := gin.Default() + + // 单个参数 + r.GET("/users/:id", func(c *gin.Context) { + id := c.Param("id") // 获取参数 + c.JSON(200, gin.H{"user_id": id}) + }) + + // 多个参数 + r.GET("/users/:uid/posts/:pid", func(c *gin.Context) { + uid := c.Param("uid") + pid := c.Param("pid") + c.JSON(200, gin.H{"uid": uid, "pid": pid}) + }) + + r.Run(":8080") +} diff --git a/go-gin-demo/params_query_demo.go b/go-gin-demo/params_query_demo.go new file mode 100644 index 0000000..1c0b64c --- /dev/null +++ b/go-gin-demo/params_query_demo.go @@ -0,0 +1,20 @@ +package main + +import ( + "github.com/gin-gonic/gin" +) + +func main() { + r := gin.Default() + + r.GET("/search", func(c *gin.Context) { + keyword := c.Query("q") // 获取查询参数 + limit := c.DefaultQuery("limit", "10") // 带默认值 + c.JSON(200, gin.H{ + "keyword": keyword, + "limit": limit, + }) + }) + + r.Run(":8080") +} diff --git a/go-gin-demo/routes_demo.go b/go-gin-demo/routes_demo.go new file mode 100644 index 0000000..f689aab --- /dev/null +++ b/go-gin-demo/routes_demo.go @@ -0,0 +1,56 @@ +package main + +import ( + "github.com/gin-gonic/gin" +) + +func main() { + r := gin.Default() + + // 基础 CRUD 路由 + r.GET("/products", func(c *gin.Context) { + c.JSON(200, gin.H{"message": "Get all products"}) + }) + + r.POST("/products", func(c *gin.Context) { + c.JSON(201, gin.H{"message": "Product created"}) + }) + + r.PUT("/products/:id", func(c *gin.Context) { + id := c.Param("id") + c.JSON(200, gin.H{"message": "Product updated", "id": id}) + }) + + r.DELETE("/products/:id", func(c *gin.Context) { + id := c.Param("id") + c.JSON(200, gin.H{"message": "Product deleted", "id": id}) + }) + + // 路由分组(推荐) + api := r.Group("/api/v1") + { + api.GET("/users", func(c *gin.Context) { + c.JSON(200, gin.H{"message": "Get all users"}) + }) + + api.POST("/users", func(c *gin.Context) { + c.JSON(201, gin.H{"message": "User created"}) + }) + + // 嵌套分组 + users := api.Group("/users") + { + users.GET("/:id", func(c *gin.Context) { + id := c.Param("id") + c.JSON(200, gin.H{"message": "Get user", "id": id}) + }) + + users.PUT("/:id", func(c *gin.Context) { + id := c.Param("id") + c.JSON(200, gin.H{"message": "User updated", "id": id}) + }) + } + } + + r.Run(":9999") +} diff --git a/go-gin-demo/user_manager.go b/go-gin-demo/user_manager.go new file mode 100644 index 0000000..6bbec0f --- /dev/null +++ b/go-gin-demo/user_manager.go @@ -0,0 +1,114 @@ +package main + +import ( + "fmt" + "github.com/gin-gonic/gin" + "net/http" +) + +// 用户结构体 +type User struct { + ID int `json:"id"` + Name string `json:"name" binding:"required"` + Email string `json:"email" binding:"required,email"` +} + +// 内存存储(实战应使用数据库) +var users = []User{ + {ID: 1, Name: "Alice", Email: "alice@example.com"}, + {ID: 2, Name: "Bob", Email: "bob@example.com"}, +} + +var nextID = 3 + +// 获取所有用户 +func listUsers(c *gin.Context) { + c.JSON(http.StatusOK, users) +} + +// 获取单个用户 +func getUser(c *gin.Context) { + id := c.Param("id") + for _, user := range users { + if user.ID == intParam(id) { + c.JSON(http.StatusOK, user) + return + } + } + c.JSON(http.StatusNotFound, gin.H{"error": "User not found"}) +} + +// 创建用户 +func createUser(c *gin.Context) { + var user User + if err := c.ShouldBindJSON(&user); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + user.ID = nextID + nextID++ + users = append(users, user) + c.JSON(http.StatusCreated, user) +} + +// 更新用户 +func updateUser(c *gin.Context) { + id := c.Param("id") + var user User + + if err := c.ShouldBindJSON(&user); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + for i, u := range users { + if u.ID == intParam(id) { + user.ID = u.ID + users[i] = user + c.JSON(http.StatusOK, user) + return + } + } + c.JSON(http.StatusNotFound, gin.H{"error": "User not found"}) +} + +// 删除用户 +func deleteUser(c *gin.Context) { + id := c.Param("id") + for i, user := range users { + if user.ID == intParam(id) { + users = append(users[:i], users[i+1:]...) + c.JSON(http.StatusNoContent, nil) + return + } + } + c.JSON(http.StatusNotFound, gin.H{"error": "User not found"}) +} + +// 辅助函数 +func intParam(s string) int { + var i int + fmt.Sscanf(s, "%d", &i) + return i +} + +func main() { + gin.SetMode(gin.ReleaseMode) + r := gin.Default() + + // API 分组 + api := r.Group("/api") + { + users := api.Group("/users") + { + users.GET("", listUsers) + users.POST("", createUser) + users.GET("/:id", getUser) + users.PUT("/:id", updateUser) + users.DELETE("/:id", deleteUser) + } + } + + r.Run(":9999") +} diff --git a/go-gin-middleware/go.mod b/go-gin-middleware/go.mod new file mode 100644 index 0000000..cd48242 --- /dev/null +++ b/go-gin-middleware/go.mod @@ -0,0 +1,32 @@ +module go-gin-middleware + +go 1.22.2 + +require ( + github.com/bytedance/sonic v1.9.1 // indirect + github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect + github.com/gabriel-vasile/mimetype v1.4.2 // indirect + github.com/gin-contrib/cors v1.4.0 // indirect + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/gin-gonic/gin v1.9.1 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.14.0 // indirect + github.com/goccy/go-json v0.10.2 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.2.4 // indirect + github.com/leodido/go-urn v1.2.4 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pelletier/go-toml/v2 v2.0.8 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.2.11 // indirect + golang.org/x/arch v0.3.0 // indirect + golang.org/x/crypto v0.9.0 // indirect + golang.org/x/net v0.10.0 // indirect + golang.org/x/sys v0.8.0 // indirect + golang.org/x/text v0.9.0 // indirect + google.golang.org/protobuf v1.30.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go-gin-middleware/go.sum b/go-gin-middleware/go.sum new file mode 100644 index 0000000..a02536a --- /dev/null +++ b/go-gin-middleware/go.sum @@ -0,0 +1,118 @@ +github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= +github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s= +github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= +github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= +github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= +github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= +github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= +github.com/gin-contrib/cors v1.4.0 h1:oJ6gwtUl3lqV0WEIwM/LxPF1QZ5qe2lGWdY2+bz7y0g= +github.com/gin-contrib/cors v1.4.0/go.mod h1:bs9pNM0x/UsmHPBWT2xZz9ROh8xYjYkiURUfmBoMlcs= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk= +github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= +github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= +github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= +github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js= +github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= +github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= +github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= +github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= +github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo= +github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= +github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= +github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= +github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= +github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= +golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= +golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/go-gin-middleware/middleware_abort.go b/go-gin-middleware/middleware_abort.go new file mode 100644 index 0000000..bac2fa2 --- /dev/null +++ b/go-gin-middleware/middleware_abort.go @@ -0,0 +1,31 @@ +package main + +import ( + "github.com/gin-gonic/gin" +) + +func TokenAuthMiddleware() gin.HandlerFunc { + return func(c *gin.Context) { + // 检查 token + if c.Query("token") == "" { + c.JSON(401, gin.H{"error": "Unauthorized"}) + c.Abort() // 中止后续处理 + return + } + + c.Next() + } +} + +func main() { + r := gin.Default() + + // 使用认证中间件 + r.Use(TokenAuthMiddleware()) + + r.GET("/protected", func(c *gin.Context) { + c.JSON(200, gin.H{"message": "Access granted"}) + }) + + r.Run(":9999") +} diff --git a/go-gin-middleware/middleware_auth.go b/go-gin-middleware/middleware_auth.go new file mode 100644 index 0000000..cee9a8f --- /dev/null +++ b/go-gin-middleware/middleware_auth.go @@ -0,0 +1,39 @@ +package main + +import ( + "github.com/gin-gonic/gin" +) + +// 基础认证中间件 +func BasicAuth() gin.HandlerFunc { + return func(c *gin.Context) { + username, password, ok := c.Request.BasicAuth() + + if !ok || username != "admin" || password != "password123" { + c.JSON(401, gin.H{"error": "Unauthorized"}) + c.Abort() + return + } + + // 认证成功,继续 + c.Set("username", username) // 将用户信息存储在 context 中 + c.Next() + } +} + +func main() { + r := gin.Default() + + // 公开路由 + r.GET("/", func(c *gin.Context) { + c.JSON(200, gin.H{"message": "Public endpoint"}) + }) + + // 需要认证的路由 + r.GET("/protected", BasicAuth(), func(c *gin.Context) { + username := c.GetString("username") + c.JSON(200, gin.H{"message": "Hello " + username}) + }) + + r.Run(":9999") +} diff --git a/go-gin-middleware/middleware_basic.go b/go-gin-middleware/middleware_basic.go new file mode 100644 index 0000000..d629974 --- /dev/null +++ b/go-gin-middleware/middleware_basic.go @@ -0,0 +1,35 @@ +package main + +import ( + "fmt" + + "github.com/gin-gonic/gin" +) + +// 最小化中间件 +func MyMiddleware() gin.HandlerFunc { + return func(c *gin.Context) { + // 请求前处理 + fmt.Println("Before request") + + // 继续下一个中间件/Handler + c.Next() + + // 响应后处理 + fmt.Println("After request2") + fmt.Println("After request1") + } +} + +func main() { + r := gin.Default() + + // 使用中间件 + r.Use(MyMiddleware()) + + r.GET("/", func(c *gin.Context) { + c.JSON(200, gin.H{"message": "Hello"}) + }) + + r.Run(":9999") +} diff --git a/go-gin-middleware/middleware_complete.go b/go-gin-middleware/middleware_complete.go new file mode 100644 index 0000000..5ab0c3f --- /dev/null +++ b/go-gin-middleware/middleware_complete.go @@ -0,0 +1,73 @@ +package main + +import ( + "fmt" + "time" + + "github.com/gin-gonic/gin" +) + +// 日志中间件 +func Logger() gin.HandlerFunc { + return func(c *gin.Context) { + start := time.Now() + c.Next() + duration := time.Since(start) + fmt.Printf("[%s] %s %d (%v)\n", + c.Request.Method, c.Request.URL.Path, c.Writer.Status(), duration) + } +} + +// 认证中间件 +func Auth() gin.HandlerFunc { + return func(c *gin.Context) { + token := c.GetHeader("Authorization") + if token != "Bearer secret" { + c.JSON(401, gin.H{"error": "Unauthorized"}) + c.Abort() + return + } + c.Next() + } +} + +// Recovery 中间件 +func Recovery() gin.HandlerFunc { + return func(c *gin.Context) { + defer func() { + if err := recover(); err != nil { + c.JSON(500, gin.H{"error": "Internal Server Error"}) + c.Abort() + } + }() + c.Next() + } +} + +func main() { + r := gin.New() + + // 全局中间件 + r.Use(Logger()) + r.Use(Recovery()) + + // 公开路由 + r.GET("/", func(c *gin.Context) { + c.JSON(200, gin.H{"message": "Public"}) + }) + + // 需要认证的路由组 + auth := r.Group("/api") + auth.Use(Auth()) + { + auth.GET("/users", func(c *gin.Context) { + c.JSON(200, gin.H{"users": []string{"Alice", "Bob"}}) + }) + + auth.POST("/users", func(c *gin.Context) { + c.JSON(201, gin.H{"message": "User created"}) + }) + } + + r.Run(":9999") +} diff --git a/go-gin-middleware/middleware_cors.go b/go-gin-middleware/middleware_cors.go new file mode 100644 index 0000000..1a39333 --- /dev/null +++ b/go-gin-middleware/middleware_cors.go @@ -0,0 +1,28 @@ +package main + +import ( + "time" + + "github.com/gin-contrib/cors" + "github.com/gin-gonic/gin" +) + +func main() { + r := gin.Default() + + // 配置 CORS + r.Use(cors.New(cors.Config{ + AllowOrigins: []string{"http://localhost:3000"}, + AllowMethods: []string{"GET", "POST", "PUT", "DELETE"}, + AllowHeaders: []string{"Origin", "Content-Type", "Authorization"}, + ExposeHeaders: []string{"Content-Length"}, + AllowCredentials: true, + MaxAge: 12 * time.Hour, + })) + + r.GET("/api/data", func(c *gin.Context) { + c.JSON(200, gin.H{"message": "CORS enabled"}) + }) + + r.Run(":9999") +} diff --git a/go-gin-middleware/middleware_cors_custom.go b/go-gin-middleware/middleware_cors_custom.go new file mode 100644 index 0000000..7b51542 --- /dev/null +++ b/go-gin-middleware/middleware_cors_custom.go @@ -0,0 +1,33 @@ +package main + +import ( + "github.com/gin-gonic/gin" +) + +func CORSMiddleware() gin.HandlerFunc { + return func(c *gin.Context) { + c.Writer.Header().Set("Access-Control-Allow-Origin", "*") + c.Writer.Header().Set("Access-Control-Allow-Credentials", "true") + c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization") + c.Writer.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE") + + if c.Request.Method == "OPTIONS" { + c.AbortWithStatus(204) + return + } + + c.Next() + } +} + +func main() { + r := gin.Default() + + r.Use(CORSMiddleware()) + + r.GET("/api/data", func(c *gin.Context) { + c.JSON(200, gin.H{"message": "Custom CORS enabled"}) + }) + + r.Run(":9999") +} diff --git a/go-gin-middleware/middleware_logger.go b/go-gin-middleware/middleware_logger.go new file mode 100644 index 0000000..32fa6ec --- /dev/null +++ b/go-gin-middleware/middleware_logger.go @@ -0,0 +1,46 @@ +package main + +import ( + "fmt" + "time" + + "github.com/gin-gonic/gin" +) + +// 日志中间件 +func LoggerMiddleware() gin.HandlerFunc { + return func(c *gin.Context) { + // 记录请求信息 + startTime := time.Now() + method := c.Request.Method + url := c.Request.URL.Path + + // 继续处理 + c.Next() + + // 记录响应信息 + statusCode := c.Writer.Status() + duration := time.Since(startTime) + + fmt.Printf("[%s] %s %d ( %v)\n", method, url, statusCode, duration) + } +} + +func main() { + r := gin.New() // 不使用默认中间件 + + // 使用自定义日志中间件 + r.Use(LoggerMiddleware()) + r.Use(gin.Recovery()) + + r.GET("/", func(c *gin.Context) { + c.JSON(200, gin.H{"message": "Hello"}) + }) + + r.GET("/slow", func(c *gin.Context) { + time.Sleep(100 * time.Millisecond) + c.JSON(200, gin.H{"message": "Slow endpoint"}) + }) + + r.Run(":9999") +} diff --git a/go-gin-middleware/middleware_recovery.go b/go-gin-middleware/middleware_recovery.go new file mode 100644 index 0000000..2e1c2e4 --- /dev/null +++ b/go-gin-middleware/middleware_recovery.go @@ -0,0 +1,50 @@ +package main + +import ( + "fmt" + + "github.com/gin-gonic/gin" +) + +// 自定义 Recovery 中间件 +func CustomRecovery() gin.HandlerFunc { + return func(c *gin.Context) { + defer func() { + if err := recover(); err != nil { + // 记录错误 + fmt.Printf("Panic: %v\n", err) + + // 返回友好的错误响应 + c.JSON(500, gin.H{ + "error": "Internal Server Error", + "message": fmt.Sprintf("%v", err), + }) + + // 中止处理链 + c.Abort() + } + }() + + c.Next() + } +} + +func main() { + r := gin.New() + + // 使用自定义 Recovery + r.Use(CustomRecovery()) + r.Use(gin.Logger()) + + // 正常路由 + r.GET("/", func(c *gin.Context) { + c.JSON(200, gin.H{"message": "OK"}) + }) + + // 故意触发 panic + r.GET("/panic", func(c *gin.Context) { + panic("Something went wrong!") + }) + + r.Run(":9999") +} diff --git a/go-gin-middleware/middleware_token.go b/go-gin-middleware/middleware_token.go new file mode 100644 index 0000000..b74bcdb --- /dev/null +++ b/go-gin-middleware/middleware_token.go @@ -0,0 +1,69 @@ +package main + +import ( + "strings" + + "github.com/gin-gonic/gin" +) + +// Token 认证中间件 +func TokenAuth() gin.HandlerFunc { + return func(c *gin.Context) { + // 从请求头获取 token + authHeader := c.GetHeader("Authorization") + + if authHeader == "" { + c.JSON(401, gin.H{"error": "Missing Authorization header"}) + c.Abort() + return + } + + // 验证 token 格式(Bearer xxx) + parts := strings.SplitN(authHeader, " ", 2) + if len(parts) != 2 || parts[0] != "Bearer" { + c.JSON(401, gin.H{"error": "Invalid Authorization header"}) + c.Abort() + return + } + + token := parts[1] + + // 验证 token 有效性(简化版,实际应使用 JWT) + if !isValidToken(token) { + c.JSON(401, gin.H{"error": "Invalid token"}) + c.Abort() + return + } + + // 提取用户信息(从 token) + userID := extractUserID(token) + c.Set("user_id", userID) + + c.Next() + } +} + +// 验证 token(示例) +func isValidToken(token string) bool { + return token == "valid_token_123" +} + +// 提取用户ID(示例) +func extractUserID(token string) string { + return "user123" +} + +func main() { + r := gin.Default() + + // 需要 token 的路由 + r.GET("/profile", TokenAuth(), func(c *gin.Context) { + userID := c.GetString("user_id") + c.JSON(200, gin.H{ + "user_id": userID, + "profile": "User profile data", + }) + }) + + r.Run(":9999") +} diff --git a/go-gorm-demo/association_methods.go b/go-gorm-demo/association_methods.go new file mode 100644 index 0000000..b87b0de --- /dev/null +++ b/go-gorm-demo/association_methods.go @@ -0,0 +1,71 @@ +package main + +import ( + "fmt" + + "gorm.io/driver/sqlite" + "gorm.io/gorm" +) + +//type User struct { +// ID uint `gorm:"primaryKey"` +// Name string `gorm:"size:100"` +// Orders []Order +//} +// +//type Order struct { +// ID uint `gorm:"primaryKey"` +// UserID uint +// Product string `gorm:"size:100"` +// Amount int +//} + +func main() { + db, _ := gorm.Open(sqlite.Open("test.db"), &gorm.Config{}) + db.AutoMigrate(&User{}, &Order{}) + + //user := User{Name: "Alice"} + //db.Create(&user) + + //查询用户根据名称 + var user User + db.Where("name = ?", "Alice").First(&user) + + //order1 := Order{Product: "Laptop"} + //order2 := Order{Product: "Mouse"} + //order3 := Order{Product: "Keyboard"} + + // 1. Append - 添加关联 + //db.Model(&user).Association("Orders").Append(&order1, &order2) + //fmt.Println("Orders appended") + + // 2. Count - 统计关联数量 + //count := db.Model(&user).Association("Orders").Count() + //fmt.Printf("Order count: %d\n", count) + + // 4. Replace - 替换所有关联 + //db.Model(&user).Association("Orders").Replace(&order3) + + //查询订单 Keyboard + var order3 Order + db.Where("product = ?", "Keyboard").First(&order3) + + // 5. Delete - 删除关联(不删除记录本身) + //db.Model(&user).Association("Orders").Delete(&order3) + //fmt.Println("Association deleted") + + //fmt.Println("Orders replaced with Keyboard") + + // 6. Clear - 清空所有关联 + //db.Model(&user).Association("Orders").Append(&order3) + db.Model(&user).Association("Orders").Clear() + fmt.Println("All associations cleared") + + // 3. Find - 查找关联 + var orders []Order + db.Model(&user).Association("Orders").Find(&orders) + fmt.Println("Orders found:") + for _, o := range orders { + fmt.Printf(" - %s\n", o.Product) + } +} diff --git a/go-gorm-demo/crud_create.go b/go-gorm-demo/crud_create.go new file mode 100644 index 0000000..061864f --- /dev/null +++ b/go-gorm-demo/crud_create.go @@ -0,0 +1,51 @@ +package main + +import ( + "fmt" + + "gorm.io/driver/sqlite" + "gorm.io/gorm" +) + +// type User struct { +// ID uint `gorm:"primaryKey"` +// Name string `gorm:"size:100"` +// Email string `gorm:"size:100;unique"` +// Age int +// CreatedAt time.Time +// } +func main() { + db, _ := gorm.Open(sqlite.Open("test.db"), &gorm.Config{}) + db.AutoMigrate(&User{}) + + // 1. 单条插入 + user := User{ + Name: "Alice", + Email: "alice@example.com", + Age: 30, + } + result := db.Create(&user) + + if result.Error != nil { + panic(result.Error) + } + + fmt.Printf("New user ID: %d\n", user.ID) + fmt.Printf("Rows affected: %d\n", result.RowsAffected) + + // 2. 批量插入 + users := []User{ + {Name: "Bob", Email: "bob@example.com", Age: 25}, + {Name: "Charlie", Email: "charlie@example.com", Age: 35}, + {Name: "David", Email: "david@example.com", Age: 28}, + } + + db.Create(users) + fmt.Println("Batch insert completed!") + + // 3. 指定字段插入 + user2 := User{Name: "Eve", Email: "eve@example.com", Age: 22} + db.Select("name", "email").Create(&user2) // 仅插入 name 和 email + + fmt.Println("All users created successfully!") +} diff --git a/go-gorm-demo/crud_delete.go b/go-gorm-demo/crud_delete.go new file mode 100644 index 0000000..8817d9d --- /dev/null +++ b/go-gorm-demo/crud_delete.go @@ -0,0 +1,56 @@ +package main + +import ( + "fmt" + + "gorm.io/driver/sqlite" + "gorm.io/gorm" +) + +//type User struct { +// ID uint `gorm:"primaryKey"` +// Name string `gorm:"size:100"` +// Email string `gorm:"size:100;unique"` +// Age int +// DeletedAt gorm.DeletedAt `gorm:"index"` // 软删除字段 +//} + +func main() { + db, _ := gorm.Open(sqlite.Open("test.db"), &gorm.Config{}) + db.AutoMigrate(&User{}) + + //// 插入测试数据 + //users := []User{ + // {Name: "Alice", Email: "alice@example.com", Age: 30}, + // {Name: "Bob", Email: "bob@example.com", Age: 25}, + // {Name: "Charlie", Email: "charlie@example.com", Age: 35}, + //} + //db.Create(users) + + // 1. 软删除(标记 deleted_at) + db.Delete(&User{}, 11) // 删除 ID=1 的用户 + fmt.Println("Soft deleted user ID=1") + + // 2. 查询时默认不包括已删除的 + var activeUsers []User + db.Find(&activeUsers) + fmt.Printf("Active users: %d\n", len(activeUsers)) + + // 3. 查询所有记录(包括已删除的) + var allUsers []User + db.Unscoped().Find(&allUsers) + fmt.Printf("All users (including deleted): %d\n", len(allUsers)) + + // 4. 永久删除(硬删除) + db.Unscoped().Delete(&User{}, 12) + fmt.Println("Permanently deleted user ID=2") + + // 5. 批量删除 + db.Where("age < ?", 30).Delete(&User{}) + fmt.Println("Batch delete completed") + + // 最终统计 + var finalCount int64 + db.Model(&User{}).Count(&finalCount) + fmt.Printf("Final active users: %d\n", finalCount) +} diff --git a/go-gorm-demo/crud_read.go b/go-gorm-demo/crud_read.go new file mode 100644 index 0000000..429f58f --- /dev/null +++ b/go-gorm-demo/crud_read.go @@ -0,0 +1,72 @@ +package main + +import ( + "fmt" + + "gorm.io/driver/sqlite" + "gorm.io/gorm" +) + +//type User struct { +// ID uint `gorm:"primaryKey"` +// Name string `gorm:"size:100"` +// Email string `gorm:"size:100;unique"` +// Age int +// CreatedAt time.Time +//} + +func main() { + db, _ := gorm.Open(sqlite.Open("test.db"), &gorm.Config{}) + + // 先插入测试数据 + db.AutoMigrate(&User{}) + db.Create(&User{Name: "Alice", Email: "alice@example.com", Age: 30}) + db.Create(&User{Name: "Bob", Email: "bob@example.com", Age: 25}) + db.Create(&User{Name: "Charlie", Email: "charlie@example.com", Age: 35}) + + // 1. 根据 ID 查询 + var user User + db.First(&user, 1) // 查询 ID=1 的用户 + fmt.Printf("User ID=1: %s (%s)\n", user.Name, user.Email) + + // 2. 按条件查询单条 + var user2 User + db.Where("email = ?", "bob@example.com").First(&user2) + fmt.Printf("User by email: %s (age=%d)\n", user2.Name, user2.Age) + + // 3. 查询所有 + var users []User + db.Find(&users) + fmt.Printf("Total users: %d\n", len(users)) + + // 4. 按条件查询多条 + var adults []User + db.Where("age >= ?", 30).Find(&adults) + fmt.Printf("Users age >= 30: %d\n", len(adults)) + + // 5. 使用 IN 查询 + var selectedUsers []User + db.Where("id IN ?", []int{1, 2}).Find(&selectedUsers) + fmt.Printf("Selected users: %d\n", len(selectedUsers)) + + // 6. 排序查询 + var sortedUsers []User + db.Order("age DESC").Find(&sortedUsers) + fmt.Println("Users sorted by age (DESC):") + for _, u := range sortedUsers { + fmt.Printf(" - %s (age=%d)\n", u.Name, u.Age) + } + + // 7. 分页查询 + var pageUsers []User + db.Offset(0).Limit(2).Find(&pageUsers) + fmt.Printf("Page 1 (limit 2): %d users\n", len(pageUsers)) + + // 8. 组合查询 + var combinedUsers []User + db.Where("age > ?", 25). + Order("age DESC"). + Limit(2). + Find(&combinedUsers) + fmt.Printf("Combined query: %d users\n", len(combinedUsers)) +} diff --git a/go-gorm-demo/crud_update.go b/go-gorm-demo/crud_update.go new file mode 100644 index 0000000..6af1509 --- /dev/null +++ b/go-gorm-demo/crud_update.go @@ -0,0 +1,56 @@ +package main + +import ( + "fmt" + + "gorm.io/driver/sqlite" + "gorm.io/gorm" +) + +//type User struct { +// ID uint `gorm:"primaryKey"` +// Name string `gorm:"size:100"` +// Email string `gorm:"size:100;unique"` +// Age int +// UpdatedAt time.Time +//} + +func main() { + db, _ := gorm.Open(sqlite.Open("test.db"), &gorm.Config{}) + db.AutoMigrate(&User{}) + + // 插入测试数据 + user := User{Name: "Alice", Email: "alice@example.com", Age: 30} + db.Create(&user) + + // 1. 更新单个字段 + db.Model(&User{}).Where("id = ?", user.ID).Update("name", "Alice Updated") + fmt.Println("Updated single field") + + // 2. 更新多个字段(使用 map) + db.Model(&User{}).Where("id = ?", user.ID).Updates(map[string]interface{}{ + "name": "Alice Smith", + "age": 31, + }) + fmt.Println("Updated multiple fields (map)") + + // 3. 更新多个字段(使用 struct,仅更新非零值) + db.Model(&User{}).Where("id = ?", user.ID).Updates(User{ + Name: "Alice Johnson", + Age: 32, + }) + fmt.Println("Updated multiple fields (struct)") + + // 4. 强制更新零值字段 + db.Model(&User{}).Where("id = ?", user.ID).Update("age", 0) + fmt.Println("Updated age to zero") + + // 5. 批量更新 + db.Model(&User{}).Where("age > ?", 25).Update("age", gorm.Expr("age + ?", 1)) + fmt.Println("Batch update completed") + + // 查询最终结果 + var finalUser User + db.First(&finalUser, user.ID) + fmt.Printf("Final user: %s (age=%d)\n", finalUser.Name, finalUser.Age) +} diff --git a/go-gorm-demo/db_connect.go b/go-gorm-demo/db_connect.go new file mode 100644 index 0000000..c05a281 --- /dev/null +++ b/go-gorm-demo/db_connect.go @@ -0,0 +1,29 @@ +package main + +import ( + "fmt" + "gorm.io/driver/sqlite" + "gorm.io/gorm" +) + +func main() { + // 连接 SQLite(文件数据库) + db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{}) + if err != nil { + panic("failed to connect database") + } + + fmt.Println("Database connected successfully!") + + // 获取底层的 *sql.DB + sqlDB, err := db.DB() + if err != nil { + panic(err) + } + + // 设置连接池 + sqlDB.SetMaxIdleConns(10) + sqlDB.SetMaxOpenConns(100) + + fmt.Println("Connection pool configured") +} diff --git a/go-gorm-demo/go.mod b/go-gorm-demo/go.mod new file mode 100644 index 0000000..e01ad06 --- /dev/null +++ b/go-gorm-demo/go.mod @@ -0,0 +1,11 @@ +module go-gorm-demo + +go 1.22.2 + +require ( + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect + github.com/mattn/go-sqlite3 v1.14.17 // indirect + gorm.io/driver/sqlite v1.5.4 // indirect + gorm.io/gorm v1.25.5 // indirect +) diff --git a/go-gorm-demo/go.sum b/go-gorm-demo/go.sum new file mode 100644 index 0000000..909192d --- /dev/null +++ b/go-gorm-demo/go.sum @@ -0,0 +1,10 @@ +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM= +github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +gorm.io/driver/sqlite v1.5.4 h1:IqXwXi8M/ZlPzH/947tn5uik3aYQslP9BVveoax0nV0= +gorm.io/driver/sqlite v1.5.4/go.mod h1:qxAuCol+2r6PannQDpOP1FP6ag3mKi4esLnB/jHed+4= +gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls= +gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= diff --git a/go-gorm-demo/model_basic.go b/go-gorm-demo/model_basic.go new file mode 100644 index 0000000..dd76c91 --- /dev/null +++ b/go-gorm-demo/model_basic.go @@ -0,0 +1,28 @@ +package main + +import ( + "fmt" + + "gorm.io/driver/sqlite" + "gorm.io/gorm" +) + +// // User 模型 +// +// type User struct { +// ID uint `gorm:"primaryKey"` +// Name string `gorm:"size:100;not null"` +// Email string `gorm:"size:100;unique;not null"` +// Age int `gorm:"default:0"` +// Active bool `gorm:"default:true"` +// CreatedAt time.Time +// UpdatedAt time.Time +// } +func main() { + db, _ := gorm.Open(sqlite.Open("test.db"), &gorm.Config{}) + + // 自动迁移(创建表) + db.AutoMigrate(&User{}) + + fmt.Println("Table created successfully!") +} diff --git a/go-gorm-demo/preload_strategies.go b/go-gorm-demo/preload_strategies.go new file mode 100644 index 0000000..bd5b02b --- /dev/null +++ b/go-gorm-demo/preload_strategies.go @@ -0,0 +1,63 @@ +package main + +import ( + "fmt" + + "gorm.io/driver/sqlite" + "gorm.io/gorm" +) + +type User struct { + ID uint `gorm:"primaryKey"` + Name string `gorm:"size:100"` + Profile Profile + Orders []Order +} + +type Profile struct { + ID uint `gorm:"primaryKey"` + UserID uint + Bio string `gorm:"size:200"` +} + +type Order struct { + ID uint `gorm:"primaryKey"` + UserID uint + Product string `gorm:"size:100"` +} + +func main() { + db, _ := gorm.Open(sqlite.Open("test.db"), &gorm.Config{}) + db.AutoMigrate(&User{}, &Profile{}, &Order{}) + + // 创建测试数据 + //user := User{ + // Name: "Alice", + // Profile: Profile{Bio: "Software Engineer"}, + // Orders: []Order{ + // {Product: "Laptop"}, + // {Product: "Mouse"}, + // }, + //} + //db.Create(&user) + + // 1. 预加载单个关联 + //var user1 User + //db.Preload("Profile").First(&user1, 1) + //fmt.Printf("User: %s, Bio: %s\n", user1.Name, user1.Profile.Bio) + + //// 2. 预加载多个关联 + //var user2 User + //db.Preload("Profile").Preload("Orders").First(&user2, 1) + //fmt.Printf("User: %s, Orders: %d\n", user2.Name, len(user2.Orders)) + + // 3. 预加载所有关联 + //var user3 User + //db.Preload("Profile").Preload("Orders").First(&user3, 1) + //fmt.Printf("Fully loaded user: %s\n", user3.Name) + + //// 4. 条件预加载 + var user4 User + db.Preload("Orders", "product = ?", "Laptop").First(&user4, 1) + fmt.Printf("Filtered orders: %d\n", len(user4.Orders)) +} diff --git a/go-gorm-demo/query_advanced.go b/go-gorm-demo/query_advanced.go new file mode 100644 index 0000000..d67eb7f --- /dev/null +++ b/go-gorm-demo/query_advanced.go @@ -0,0 +1,76 @@ +package main + +import ( + "fmt" + + "gorm.io/driver/sqlite" + "gorm.io/gorm" +) + +//type User struct { +// ID uint `gorm:"primaryKey"` +// Name string `gorm:"size:100"` +// Email string `gorm:"size:100;unique"` +// Age int +// Active bool `gorm:"default:true"` +// CreatedAt time.Time +//} + +func main() { + db, _ := gorm.Open(sqlite.Open("test.db"), &gorm.Config{}) + db.AutoMigrate(&User{}) + + // 插入测试数据 + users := []User{ + {Name: "Alice", Email: "alice@example.com", Age: 30, Active: true}, + {Name: "Bob", Email: "bob@example.com", Age: 25, Active: false}, + {Name: "Charlie", Email: "charlie@example.com", Age: 35, Active: true}, + {Name: "David", Email: "david@example.com", Age: 28, Active: true}, + } + db.Create(users) + + // 1. 多条件查询 + var result1 []User + db.Where("age > ? AND active = ?", 25, true).Find(&result1) + fmt.Printf("Age > 25 AND active: %d users\n", len(result1)) + + // 2. OR 查询 + var result2 []User + db.Where("age < ? OR active = ?", 30, false).Find(&result2) + fmt.Printf("Age < 30 OR inactive: %d users\n", len(result2)) + + // 3. 模糊查询 + var result3 []User + db.Where("name LIKE ?", "%li%").Find(&result3) + fmt.Printf("Name contains 'li': %d users\n", len(result3)) + + // 4. 仅查询指定字段 + var result4 []User + db.Select("name", "email").Find(&result4) + fmt.Printf("Selected fields: %d users\n", len(result4)) + for _, u := range result4 { + fmt.Printf(" - %s (%s)\n", u.Name, u.Email) + } + + // 5. 统计查询 + var count int64 + db.Model(&User{}).Where("active = ?", true).Count(&count) + fmt.Printf("Active users count: %d\n", count) + + // 6. 聚合查询 + var avgAge float64 + db.Model(&User{}).Select("AVG(age)").Row().Scan(&avgAge) + fmt.Printf("Average age: %.2f\n", avgAge) + + // 7. 分组查询 + type Result struct { + Active bool + Count int64 + } + var groupResult []Result + db.Model(&User{}).Select("active, count(*) as count").Group("active").Scan(&groupResult) + fmt.Println("Group by active:") + for _, r := range groupResult { + fmt.Printf(" Active=%v: %d users\n", r.Active, r.Count) + } +} diff --git a/go-gorm-demo/raw_sql.go b/go-gorm-demo/raw_sql.go new file mode 100644 index 0000000..e686140 --- /dev/null +++ b/go-gorm-demo/raw_sql.go @@ -0,0 +1,47 @@ +package main + +import ( + "fmt" + + "gorm.io/driver/sqlite" + "gorm.io/gorm" +) + +//type User struct { +// ID uint `gorm:"primaryKey"` +// Name string `gorm:"size:100"` +// Email string `gorm:"size:100;unique"` +// Age int +// Active bool `gorm:"default:true"` +//} + +func main() { + db, _ := gorm.Open(sqlite.Open("test.db"), &gorm.Config{}) + db.AutoMigrate(&User{}) + + // 插入测试数据 + db.Create(&User{Name: "Alice", Email: "alice@example.com", Age: 30}) + db.Create(&User{Name: "Bob", Email: "bob@example.com", Age: 25}) + + // 1. 原生查询 + var users []User + db.Raw("SELECT * FROM users WHERE age > ?", 20).Scan(&users) + fmt.Printf("Raw query result: %d users\n", len(users)) + + // 2. 原生更新 + db.Exec("UPDATE users SET age = age + 1 WHERE name = ?", "Alice") + fmt.Println("Raw update executed") + + // 3. 原生统计 + var count int64 + db.Raw("SELECT COUNT(*) FROM users").Scan(&count) + fmt.Printf("Total users: %d\n", count) + + // 4. 查询单行 + var result struct { + Name string + Age int + } + db.Raw("SELECT name, age FROM users WHERE id = ?", 1).Scan(&result) + fmt.Printf("User: %s (age=%d)\n", result.Name, result.Age) +} diff --git a/go-gorm-demo/relation_has_many.go b/go-gorm-demo/relation_has_many.go new file mode 100644 index 0000000..60b1638 --- /dev/null +++ b/go-gorm-demo/relation_has_many.go @@ -0,0 +1,61 @@ +package main + +import ( + "fmt" + + "gorm.io/driver/sqlite" + "gorm.io/gorm" +) + +// 用户拥有多个订单 +//type User struct { +// ID uint `gorm:"primaryKey"` +// Name string `gorm:"size:100"` +// Orders []Order // 一对多关联 +//} +// +//type Order struct { +// ID uint `gorm:"primaryKey"` +// UserID uint // 外键 +// Product string `gorm:"size:100"` +// Amount int +//} + +func main() { + db, _ := gorm.Open(sqlite.Open("test.db"), &gorm.Config{}) + db.AutoMigrate(&User{}, &Order{}) + + // 1. 创建用户和订单 + user := User{ + Name: "Alice", + Orders: []Order{ + {Product: "Laptop", Amount: 1}, + {Product: "Mouse", Amount: 2}, + {Product: "Keyboard", Amount: 1}, + }, + } + db.Create(&user) + fmt.Println("User and orders created") + + // 2. 预加载查询 + var result User + db.Preload("Orders").First(&result, user.ID) + fmt.Printf("User: %s, Orders: %d\n", result.Name, len(result.Orders)) + for _, order := range result.Orders { + fmt.Printf(" - %s (x%d)\n", order.Product, order.Amount) + } + + // 3. 添加新订单 + newOrder := Order{UserID: user.ID, Product: "Monitor", Amount: 1} + db.Create(&newOrder) + fmt.Println("New order added") + + // 4. 查询用户的所有订单 + var orders []Order + db.Where("user_id = ?", user.ID).Find(&orders) + fmt.Printf("Total orders: %d\n", len(orders)) + + // 5. 删除某个订单 + db.Delete(&Order{}, orders[0].ID) + fmt.Println("First order deleted") +} diff --git a/go-gorm-demo/relation_has_one.go b/go-gorm-demo/relation_has_one.go new file mode 100644 index 0000000..160ec74 --- /dev/null +++ b/go-gorm-demo/relation_has_one.go @@ -0,0 +1,51 @@ +package main + +import ( + "fmt" + + "gorm.io/driver/sqlite" + "gorm.io/gorm" +) + +// 用户拥有一个账户 +//type User struct { +// ID uint `gorm:"primaryKey"` +// Name string `gorm:"size:100"` +// Account Account // 一对一关联 +// Email string `gorm:"size:100;unique"` +//} +// +//type Account struct { +// ID uint `gorm:"primaryKey"` +// UserID uint // 外键 +// Balance int +//} + +func main() { + db, _ := gorm.Open(sqlite.Open("test.db"), &gorm.Config{}) + db.AutoMigrate(&User{}, &Account{}) + + // 1. 创建用户和账户 + user := User{ + Name: "Alice", + Account: Account{Balance: 1000}, + } + db.Create(&user) + fmt.Println("User and account created") + + // 2. 预加载查询 + var result User + db.Preload("Account").First(&result, user.ID) + fmt.Printf("User: %s, Balance: %d\n", result.Name, result.Account.Balance) + + // 3. 更新关联数据 + db.Model(&result.Account).Update("balance", 2000) + fmt.Println("Account balance updated") + + // 4. 删除关联(不会自动删除 Account) + db.Delete(&result) + + var accountCount int64 + db.Model(&Account{}).Count(&accountCount) + fmt.Printf("Account still exists: %d\n", accountCount) +} diff --git a/go-gorm-demo/relation_many_to_many.go b/go-gorm-demo/relation_many_to_many.go new file mode 100644 index 0000000..6a5a20f --- /dev/null +++ b/go-gorm-demo/relation_many_to_many.go @@ -0,0 +1,96 @@ +package main + +import ( + "fmt" + + "gorm.io/driver/sqlite" + "gorm.io/gorm" +) + +// 学生和课程:多对多关系 +type Student struct { + ID uint `gorm:"primaryKey"` + Name string `gorm:"size:100"` + Courses []Course `gorm:"many2many:student_courses;"` // 多对多 +} + +type Course struct { + ID uint `gorm:"primaryKey"` + Name string `gorm:"size:100"` + Students []Student `gorm:"many2many:student_courses;"` // 多对多 +} + +func main() { + db, _ := gorm.Open(sqlite.Open("test.db"), &gorm.Config{}) + //db.AutoMigrate(&Student{}, &Course{}) + + // 1. 创建课程 + //math := Course{Name: "Math"} + //physics := Course{Name: "Physics"} + //chemistry := Course{Name: "Chemistry"} + //db.Create(&math) + //db.Create(&physics) + //db.Create(&chemistry) + + // 2. 创建学生并关联课程 + //alice := Student{ + // Name: "Alice", + // Courses: []Course{math, physics}, + //} + //bob := Student{ + // Name: "Bob", + // Courses: []Course{physics, chemistry}, + //} + //db.Create(&alice) + //db.Create(&bob) + //fmt.Println("Students and courses created") + + //查询数据库的alice的学生 + var alice Student + db.Where("name = ?", "Alice").First(&alice) + + //查询数据库的Bob学生 + var bob Student + db.Where("name = ?", "Bob").First(&bob) + + //查询课程 + var math Course + db.Where("name = ?", "Math").First(&math) + + //// 5. 添加关联 + //db.Model(&alice).Association("Courses").Append(&chemistry) + //fmt.Println("Alice enrolled in Chemistry") + + //// 7. 替换所有关联 + //db.Model(&alice).Association("Courses").Replace(&math) + //fmt.Println("Alice now only takes Math") + + // 8. 清空关联 + db.Model(&bob).Association("Courses").Clear() + fmt.Println("Bob dropped all courses") + + // 3. 查询学生的课程 + var student1 Student + db.Preload("Courses").First(&student1, 1) + fmt.Printf("%s's courses:\n", student1.Name) + for _, course := range student1.Courses { + fmt.Printf(" - %s\n", course.Name) + } + + // 4. 查询课程的学生 + var course1 Course + db.Preload("Students").First(&course1, 1) + fmt.Printf("%s course students:\n", course1.Name) + for _, student := range course1.Students { + fmt.Printf(" - %s\n", student.Name) + } + + //查询bob的课程 + var student2 Student + db.Preload("Courses").First(&student2, 2) + fmt.Printf("%s's courses:\n", student2.Name) + for _, course := range student2.Courses { + fmt.Printf(" - %s\n", course.Name) + } + +} diff --git a/go-gorm-demo/test.db b/go-gorm-demo/test.db new file mode 100644 index 0000000..05dccdf Binary files /dev/null and b/go-gorm-demo/test.db differ diff --git a/go-gorm-demo/transaction_basic.go b/go-gorm-demo/transaction_basic.go new file mode 100644 index 0000000..e0359ab --- /dev/null +++ b/go-gorm-demo/transaction_basic.go @@ -0,0 +1,70 @@ +package main + +import ( + "fmt" + + "gorm.io/driver/sqlite" + "gorm.io/gorm" +) + +//type User struct { +// ID uint `gorm:"primaryKey"` +// Name string `gorm:"size:100"` +// Email string `gorm:"size:100;unique"` +// Age int +//} +// +//type Account struct { +// ID uint `gorm:"primaryKey"` +// UserID uint +// Balance int +//} + +func main() { + db, _ := gorm.Open(sqlite.Open("test.db"), &gorm.Config{}) + db.AutoMigrate(&User{}, &Account{}) + + // 1. 手动事务 + tx := db.Begin() + + // 创建用户 + user := User{Name: "Alice", Email: "alice@example.com"} + if err := tx.Create(&user).Error; err != nil { + tx.Rollback() + panic(err) + } + + // 创建账户 + account := Account{UserID: user.ID, Balance: 1000} + if err := tx.Create(&account).Error; err != nil { + tx.Rollback() + panic(err) + } + + // 提交事务 + tx.Commit() + fmt.Println("Transaction committed successfully!") + + // 2. 自动事务(推荐) + err := db.Transaction(func(tx *gorm.DB) error { + // 在事务中执行操作 + user2 := User{Name: "Bob", Email: "bob@example.com"} + if err := tx.Create(&user2).Error; err != nil { + return err // 自动回滚 + } + + account2 := Account{UserID: user2.ID, Balance: 2000} + if err := tx.Create(&account2).Error; err != nil { + return err // 自动回滚 + } + + // 返回 nil 自动提交 + return nil + }) + + if err != nil { + fmt.Println("Transaction failed:", err) + } else { + fmt.Println("Auto transaction committed successfully!") + } +} diff --git a/go-gorm-demo/transaction_rollback.go b/go-gorm-demo/transaction_rollback.go new file mode 100644 index 0000000..2974810 --- /dev/null +++ b/go-gorm-demo/transaction_rollback.go @@ -0,0 +1,47 @@ +package main + +import ( + "errors" + "fmt" + + "gorm.io/driver/sqlite" + "gorm.io/gorm" +) + +// type User struct { +// ID uint `gorm:"primaryKey"` +// Name string `gorm:"size:100"` +// Email string `gorm:"size:100;unique"` +// } +//type Account struct { +// ID uint `gorm:"primaryKey"` +// UserID uint +// Balance int +//} + +func main() { + db, _ := gorm.Open(sqlite.Open("test.db"), &gorm.Config{}) + db.AutoMigrate(&User{}, &Account{}) + + // 故意触发错误,测试回滚 + err := db.Transaction(func(tx *gorm.DB) error { + user := User{Name: "Charlie", Email: "charlie@example.com"} + if err := tx.Create(&user).Error; err != nil { + return err + } + + fmt.Println("User created, ID:", user.ID) + + // 故意返回错误,触发回滚 + return errors.New("something went wrong") + }) + + if err != nil { + fmt.Println("Transaction rolled back:", err) + } + + // 检查用户是否存在 + var count int64 + db.Model(&User{}).Where("name = ?", "Charlie").Count(&count) + fmt.Printf("User 'Charlie' count: %d (should be 0)\n", count) +} diff --git a/go-gorm-demo/transaction_savepoint.go b/go-gorm-demo/transaction_savepoint.go new file mode 100644 index 0000000..e292cd0 --- /dev/null +++ b/go-gorm-demo/transaction_savepoint.go @@ -0,0 +1,37 @@ +package main + +import ( + "fmt" + + "gorm.io/driver/sqlite" + "gorm.io/gorm" +) + +//type User struct { +// ID uint `gorm:"primaryKey"` +// Name string `gorm:"size:100"` +// Email string `gorm:"size:100;unique"` +//} + +func main() { + db, _ := gorm.Open(sqlite.Open("test.db"), &gorm.Config{}) + db.AutoMigrate(&User{}) + + db.Transaction(func(tx *gorm.DB) error { + tx.Create(&User{Name: "Alice", Email: "alice@example.com"}) + + // 创建 SavePoint + tx.SavePoint("sp1") + tx.Create(&User{Name: "Bob", Email: "bob@example.com"}) + + // 回滚到 SavePoint + tx.RollbackTo("sp1") + + return nil + }) + + // 检查结果 + var count int64 + db.Model(&User{}).Count(&count) + fmt.Printf("Total users: %d (should be 1, only Alice)\n", count) +} diff --git a/go-goroutine-gpm/go.mod b/go-goroutine-gpm/go.mod new file mode 100644 index 0000000..4374083 --- /dev/null +++ b/go-goroutine-gpm/go.mod @@ -0,0 +1,3 @@ +module go-goroutine-gpm + +go 1.22.2 diff --git a/go-goroutine-gpm/main.go b/go-goroutine-gpm/main.go new file mode 100644 index 0000000..e0858e3 --- /dev/null +++ b/go-goroutine-gpm/main.go @@ -0,0 +1,76 @@ +package main + +import ( + "fmt" + "runtime" + "sync" + "time" +) + +func say(s string, id int) { + for i := 0; i < 3; i++ { + fmt.Printf("[%d] %s: step %d\n", id, s, i) + time.Sleep(100 * time.Millisecond) // 模拟工作 + } +} + +func main() { + fmt.Println("=== 1. 单线程顺序执行 ===") + say("Hello", 1) + say("World", 2) + + fmt.Println("\n=== 2. 使用 Goroutine 并发执行 ===") + go say("Goroutine-A", 1) + go say("Goroutine-B", 2) + + // 主 goroutine 等待 1 秒,否则程序会提前退出 + time.Sleep(1 * time.Second) + + fmt.Println("\n=== 3. 使用 sync.WaitGroup 安全等待 ===") + var wg sync.WaitGroup + + for i := 1; i <= 3; i++ { + wg.Add(1) + go func(id int) { + defer wg.Done() + fmt.Printf("Worker %d started\n", id) + time.Sleep(time.Duration(id*200) * time.Millisecond) + fmt.Printf("Worker %d finished\n", id) + }(i) + } + + wg.Wait() // 阻塞直到所有 goroutine 完成 + fmt.Println("All workers done!") + + fmt.Println("\n=== 4. 查看当前 GOMAXPROCS 和 CPU 核心数 ===") + fmt.Printf("CPU 核心数: %d\n", runtime.NumCPU()) + fmt.Printf("GOMAXPROCS: %d\n", runtime.GOMAXPROCS(0)) // 0 表示不修改,仅查询 + + fmt.Println("\n=== 5. 手动设置 GOMAXPROCS(通常不需要)===") + old := runtime.GOMAXPROCS(2) + fmt.Printf("旧 GOMAXPROCS: %d, 新设为: 2\n", old) + runtime.GOMAXPROCS(old) // 恢复原值 + + fmt.Println("\n=== 6. 观察 Goroutine 数量变化 ===") + fmt.Printf("启动前 Goroutine 数: %d\n", runtime.NumGoroutine()) + + var wg2 sync.WaitGroup + for i := 0; i < 5; i++ { + wg2.Add(1) + go func(n int) { + defer wg2.Done() + time.Sleep(200 * time.Millisecond) + }(i) + } + + time.Sleep(50 * time.Millisecond) // 让 goroutine 启动 + fmt.Printf("启动后 Goroutine 数: %d\n", runtime.NumGoroutine()) + + wg2.Wait() + fmt.Printf("全部完成后 Goroutine 数: %d\n", runtime.NumGoroutine()) + + // 在程序最后添加 + time.Sleep(1 * time.Second) + fmt.Printf("延迟后 Goroutine 数: %d\n", runtime.NumGoroutine()) + +} diff --git a/go-interfaces/go.mod b/go-interfaces/go.mod new file mode 100644 index 0000000..c51ed44 --- /dev/null +++ b/go-interfaces/go.mod @@ -0,0 +1,3 @@ +module go-interfaces + +go 1.22.2 diff --git a/go-interfaces/main.go b/go-interfaces/main.go new file mode 100644 index 0000000..1c58c9a --- /dev/null +++ b/go-interfaces/main.go @@ -0,0 +1,94 @@ +package main + +import "fmt" + +// ========== 1. 定义接口(鸭子类型)========== +type Speaker interface { + Speak() string +} + +type Dog struct{} + +func (d Dog) Speak() string { + return "Woof!" +} + +type Cat struct{} + +func (c Cat) Speak() string { + return "Meow~" +} + +type Robot struct{} + +func (r Robot) Speak() string { + return "Beep boop." +} + +// ========== 2. 空接口(interface{})========== +// 在 Go 1.18+ 中,推荐使用 any(它是 interface{} 的别名) +func printAnything(v any) { + fmt.Printf("接收到: %v (类型: %T)\n", v, v) +} + +// ========== 3. 类型断言 ========== +func describeSpeaker(s Speaker) { + fmt.Println("它说:", s.Speak()) + + // 类型断言:判断具体类型 + if d, ok := s.(Dog); ok { + fmt.Println("这是一只狗!", d) + } else if c, ok := s.(Cat); ok { + fmt.Println("这是一只猫!", c) + } +} + +// 使用 switch 进行类型断言(更优雅) +func identify(v any) { + switch x := v.(type) { + case string: + fmt.Printf("字符串: %s\n", x) + case int: + fmt.Printf("整数: %d\n", x) + case Speaker: + fmt.Printf("会说话的东西: %s\n", x.Speak()) + default: + fmt.Printf("未知类型: %T\n", x) + } +} + +// ========== 主函数 ========== +func main() { + fmt.Println("=== 1. 鸭子类型:只要会 Speak(),就是 Speaker ===") + animals := []Speaker{Dog{}, Cat{}, Robot{}} + for _, a := range animals { + fmt.Println(a.Speak()) + } + + fmt.Println("\n=== 2. 空接口(any)可接收任意类型 ===") + printAnything(42) + printAnything("Hello") + printAnything(Dog{}) + + fmt.Println("\n=== 3. 类型断言:从接口还原具体类型 ===") + describeSpeaker(Dog{}) + describeSpeaker(Cat{}) + + fmt.Println("\n=== 4. 类型 switch:安全高效的类型判断 ===") + identify("Gopher") + identify(100) + identify(Robot{}) + identify(true) // 未知类型 + + fmt.Println("\n=== 5. 安全 vs 不安全的类型断言 ===") + var i any = "hello" + s := i.(string) // 不安全:如果类型不对,会 panic + fmt.Println("不安全断言结果:", s) + + // 安全方式 + if val, ok := i.(int); ok { + fmt.Println("是整数:", val) + } else { + fmt.Println("不是整数!") + } +} diff --git a/go-mini-logger/app.log b/go-mini-logger/app.log new file mode 100644 index 0000000..1d37f78 --- /dev/null +++ b/go-mini-logger/app.log @@ -0,0 +1,3 @@ +[2025-12-22 17:16:55] [INFO] 应用开始处理请求 +[2025-12-22 17:16:55] [WARN] 用户 liumangmang 尝试越权操作 +[2025-12-22 17:16:55] [ERROR] 写入数据库时发生唯一键冲突 diff --git a/go-mini-logger/go.mod b/go-mini-logger/go.mod new file mode 100644 index 0000000..8c3bd0e --- /dev/null +++ b/go-mini-logger/go.mod @@ -0,0 +1,3 @@ +module go-mini-logger + +go 1.22.2 diff --git a/go-mini-logger/logger.go b/go-mini-logger/logger.go new file mode 100644 index 0000000..486a72a --- /dev/null +++ b/go-mini-logger/logger.go @@ -0,0 +1,90 @@ +package main + +import ( + "fmt" + "io" + "os" + "time" +) + +// LogLevel 日志级别 +type LogLevel int + +const ( + LevelInfo LogLevel = iota + LevelWarn + LevelError +) + +func (l LogLevel) String() string { + switch l { + case LevelInfo: + return "INFO" + case LevelWarn: + return "WARN" + case LevelError: + return "ERROR" + default: + return "UNKNOWN" + } +} + +// Logger 接口:定义日志行为 +type Logger interface { + Log(level LogLevel, format string, args ...any) + Info(format string, args ...any) + Warn(format string, args ...any) + Error(format string, args ...any) + SetMinLevel(level LogLevel) +} + +// defaultLogger 结构体:默认实现 +type defaultLogger struct { + minLevel LogLevel + writer io.Writer +} + +// NewLogger 创建新日志实例,默认输出到 os.Stdout +func NewLogger() Logger { + return &defaultLogger{ + minLevel: LevelInfo, + writer: os.Stdout, + } +} + +// NewFileLogger 创建写入文件的日志实例 +func NewFileLogger(filename string) (Logger, error) { + file, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) + if err != nil { + return nil, fmt.Errorf("无法创建日志文件 %s: %w", filename, err) + } + return &defaultLogger{ + minLevel: LevelInfo, + writer: file, + }, nil +} + +// SetMinLevel 设置最低日志级别 +func (l *defaultLogger) SetMinLevel(level LogLevel) { + l.minLevel = level +} + +// Log 核心日志方法 +func (l *defaultLogger) Log(level LogLevel, format string, args ...any) { + if level < l.minLevel { + return // 低于最小级别,丢弃 + } + + // 构建日志消息:[时间] [级别] 消息 + timestamp := time.Now().Format("2006-01-02 15:04:05") + message := fmt.Sprintf(format, args...) + line := fmt.Sprintf("[%s] [%s] %s\n", timestamp, level.String(), message) + + // 写入目标(控制台或文件) + fmt.Fprint(l.writer, line) +} + +// 快捷方法 +func (l *defaultLogger) Info(format string, args ...any) { l.Log(LevelInfo, format, args...) } +func (l *defaultLogger) Warn(format string, args ...any) { l.Log(LevelWarn, format, args...) } +func (l *defaultLogger) Error(format string, args ...any) { l.Log(LevelError, format, args...) } diff --git a/go-mini-logger/main.go b/go-mini-logger/main.go new file mode 100644 index 0000000..0b31ec8 --- /dev/null +++ b/go-mini-logger/main.go @@ -0,0 +1,35 @@ +package main + +import ( + "fmt" + "os" +) + +func main() { + fmt.Println("=== 1. 控制台日志 ===") + consoleLogger := NewLogger() + consoleLogger.Info("程序启动,PID: %d", os.Getpid()) + consoleLogger.Warn("磁盘使用率超过 80%") + consoleLogger.Error("数据库连接失败: timeout") + + fmt.Println("\n=== 2. 设置最低级别为 WARN ===") + consoleLogger.SetMinLevel(LevelWarn) + consoleLogger.Info("这条不会显示") // 被过滤 + consoleLogger.Warn("这条会显示") // 显示 + consoleLogger.Error("错误也会显示") // 显示 + + fmt.Println("\n=== 3. 文件日志(写入 app.log)===") + fileLogger, err := NewFileLogger("app.log") + if err != nil { + consoleLogger.Error("无法创建文件日志: %v", err) + return + } + + fileLogger.Info("应用开始处理请求") + fileLogger.Warn("用户 %s 尝试越权操作", "liumangmang") + fileLogger.Error("写入数据库时发生唯一键冲突") + + fmt.Println("日志已写入 app.log,请查看内容:") + os.ReadFile("app.log") // 不打印,仅确保写入 + fmt.Println("✅ 查看 app.log 可验证文件日志内容") +} diff --git a/go-select-practice/go.mod b/go-select-practice/go.mod new file mode 100644 index 0000000..644cd6c --- /dev/null +++ b/go-select-practice/go.mod @@ -0,0 +1,3 @@ +module go-select-practice + +go 1.22.2 diff --git a/go-select-practice/select_basic.go b/go-select-practice/select_basic.go new file mode 100644 index 0000000..ebe503c --- /dev/null +++ b/go-select-practice/select_basic.go @@ -0,0 +1,34 @@ +package main + +import ( + "fmt" + "time" +) + +func main() { + ch1 := make(chan string) + ch2 := make(chan string) + + // 模拟两个不同来源的“数据源” + go func() { + time.Sleep(1 * time.Second) + ch1 <- "result from ch1 (1s)" + }() + + go func() { + time.Sleep(2 * time.Second) + ch2 <- "result from ch2 (2s)" + }() + + fmt.Println("等待 ch1 或 ch2 的结果...(谁先来处理谁)") + + select { + case v := <-ch1: + fmt.Println("收到 ch1:", v) + case v := <-ch2: + fmt.Println("收到 ch2:", v) + } + + fmt.Println("main 结束") +} + diff --git a/go-select-practice/select_context.go b/go-select-practice/select_context.go new file mode 100644 index 0000000..2ff6d74 --- /dev/null +++ b/go-select-practice/select_context.go @@ -0,0 +1,38 @@ +package main + +import ( + "context" + "fmt" + "time" +) + +// 模拟一个可被取消的操作 +func doWork(ctx context.Context) error { + for i := 1; i <= 5; i++ { + select { + case <-ctx.Done(): + // context 被取消或超时 + fmt.Println("doWork 被取消:", ctx.Err()) + return ctx.Err() + default: + fmt.Println("工作中 step", i) + time.Sleep(1 * time.Second) + } + } + fmt.Println("doWork 正常完成") + return nil +} + +func main() { + ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) + defer cancel() + + fmt.Println("开始工作,最长 3 秒...") + + if err := doWork(ctx); err != nil { + fmt.Println("结束,原因:", err) + return + } + + fmt.Println("结束: 正常完成") +} diff --git a/go-select-practice/select_default.go b/go-select-practice/select_default.go new file mode 100644 index 0000000..c6fba13 --- /dev/null +++ b/go-select-practice/select_default.go @@ -0,0 +1,33 @@ +package main + +import ( + "fmt" + "time" +) + +func main() { + ch := make(chan int) + + go func() { + for i := 1; i <= 5; i++ { + time.Sleep(500 * time.Millisecond) + ch <- i + } + close(ch) + }() + + for { + select { + case v, ok := <-ch: + if !ok { + fmt.Println("channel 已关闭,退出循环") + return + } + fmt.Println("收到:", v) + default: + // 没有数据可读时,做点“其他事” + fmt.Println("没有新数据,先忙点别的...") + time.Sleep(200 * time.Millisecond) + } + } +} diff --git a/go-select-practice/select_timeout.go b/go-select-practice/select_timeout.go new file mode 100644 index 0000000..55f4f14 --- /dev/null +++ b/go-select-practice/select_timeout.go @@ -0,0 +1,46 @@ +package main + +import ( + "errors" + "fmt" + "time" +) + +// 模拟一个可能很慢的操作 +func slowOperation() (string, error) { + time.Sleep(3 * time.Second) // 假设真的很慢 + return "slow result", nil +} + +func doWithTimeout(timeout time.Duration) (string, error) { + resultCh := make(chan string, 1) + errCh := make(chan error, 1) + + go func() { + res, err := slowOperation() + if err != nil { + errCh <- err + return + } + resultCh <- res + }() + + select { + case res := <-resultCh: + return res, nil + case err := <-errCh: + return "", err + case <-time.After(timeout): + return "", errors.New("操作超时") + } +} + +func main() { + fmt.Println("开始调用,最大等待 2 秒...") + res, err := doWithTimeout(2 * time.Second) + if err != nil { + fmt.Println("失败:", err) + return + } + fmt.Println("成功:", res) +} diff --git a/go-slice-map/go.mod b/go-slice-map/go.mod new file mode 100644 index 0000000..9222aea --- /dev/null +++ b/go-slice-map/go.mod @@ -0,0 +1,3 @@ +module go-slice-map + +go 1.22.2 diff --git a/go-slice-map/main.go b/go-slice-map/main.go new file mode 100644 index 0000000..2a938ed --- /dev/null +++ b/go-slice-map/main.go @@ -0,0 +1,119 @@ +package main + +import ( + "fmt" + "sync" +) + +func main() { + fmt.Println("=== 1. Slice 扩容机制 ===") + demoSliceGrowth() + + fmt.Println("\n=== 2. Slice 深拷贝 vs 浅拷贝 ===") + demoSliceCopy() + + fmt.Println("\n=== 3. Map 基本使用 ===") + demoMapUsage() + + fmt.Println("\n=== 4. Map 并发写风险(会 panic!)===") + // ⚠️ 下面这行默认注释掉,避免程序崩溃 + // demoUnsafeConcurrentMap() + + fmt.Println("\n=== 5. 安全的并发 map(sync.Map)===") + demoSafeConcurrentMap() +} + +// 1. 演示 slice 扩容机制 +func demoSliceGrowth() { + var s []int + fmt.Printf("初始: len=%d, cap=%d, addr=%p\n", len(s), cap(s), s) + + for i := 0; i < 10; i++ { + s = append(s, i) + fmt.Printf("append %d: len=%d, cap=%d, addr=%p\n", i, len(s), cap(s), s) + } + // 观察:当容量不足时,底层数组会重新分配,地址改变 +} + +// 2. 深拷贝 vs 浅拷贝 +func demoSliceCopy() { + original := []int{1, 2, 3, 4, 5} + shallow := original // 浅拷贝:共享底层数组 + deep := make([]int, len(original)) + copy(deep, original) // 深拷贝:独立底层数组 + + fmt.Println("修改前:") + fmt.Println("original:", original) + fmt.Println("shallow :", shallow) + fmt.Println("deep :", deep) + + // 修改原 slice + original[0] = 999 + + fmt.Println("\n修改 original[0] = 999 后:") + fmt.Println("original:", original) // [999, 2, 3, 4, 5] + fmt.Println("shallow :", shallow) // [999, 2, 3, 4, 5] ← 被影响! + fmt.Println("deep :", deep) // [1, 2, 3, 4, 5] ← 不受影响 +} + +// 3. Map 基本使用 +func demoMapUsage() { + m := make(map[string]int) + m["apple"] = 5 + m["banana"] = 3 + + fmt.Println("map 内容:", m) + fmt.Println("apple 数量:", m["apple"]) + + // 检查 key 是否存在 + if count, ok := m["orange"]; ok { + fmt.Println("orange 存在,数量:", count) + } else { + fmt.Println("orange 不存在") + } + + // 删除 key + delete(m, "apple") + fmt.Println("删除 apple 后:", m) +} + +// 4. 不安全的并发 map 写入(会导致 panic) +func demoUnsafeConcurrentMap() { + m := make(map[int]int) + var wg sync.WaitGroup + + for i := 0; i < 100; i++ { + wg.Add(1) + go func(key int) { + defer wg.Done() + m[key] = key * 10 // 并发写 → fatal error: concurrent map writes + }(i) + } + + wg.Wait() + fmt.Println("unsafe map done (should not reach here)") +} + +// 5. 使用 sync.Map 实现安全并发 +func demoSafeConcurrentMap() { + var sm sync.Map + var wg sync.WaitGroup + + for i := 0; i < 100; i++ { + wg.Add(1) + go func(key int) { + defer wg.Done() + sm.Store(key, key*10) + }(i) + } + + wg.Wait() + + // 读取部分值验证 + sm.Range(func(key, value any) bool { + if key.(int) < 5 { // 只打印前几个 + fmt.Printf("key=%v, value=%v\n", key, value) + } + return true // 继续遍历 + }) +} diff --git a/go-struct-methods/go.mod b/go-struct-methods/go.mod new file mode 100644 index 0000000..cdf3dbf --- /dev/null +++ b/go-struct-methods/go.mod @@ -0,0 +1,3 @@ +module go-struct-methods + +go 1.22.2 diff --git a/go-struct-methods/main.go b/go-struct-methods/main.go new file mode 100644 index 0000000..1756c4e --- /dev/null +++ b/go-struct-methods/main.go @@ -0,0 +1,61 @@ +package main + +import "fmt" + +// 定义一个 Person 结构体 +type Person struct { + Name string + Age int +} + +// ========== 值接收者方法 ========== +func (p Person) SayHello() { + fmt.Printf("Hello, I'm %s (值接收者)\n", p.Name) +} + +// 值接收者无法修改原始结构体字段 +func (p Person) SetName(name string) { + p.Name = name // 修改的是副本! + fmt.Printf("在值接收者中设置 Name = %s\n", p.Name) +} + +// ========== 指针接收者方法 ========== +func (p *Person) SayHelloPtr() { + fmt.Printf("Hello, I'm %s (指针接收者)\n", p.Name) +} + +// 指针接收者可以真正修改原始结构体 +func (p *Person) SetNamePtr(name string) { + p.Name = name // 修改的是原始对象! + fmt.Printf("在指针接收者中设置 Name = %s\n", p.Name) +} + +// ========== 对比调用行为 ========== +func main() { + fmt.Println("=== 1. 使用值类型变量调用方法 ===") + p1 := Person{Name: "Alice", Age: 30} + p1.SayHello() // OK + p1.SetName("Alicia") // ❌ 不会改变 p1.Name + fmt.Printf("调用值接收者 SetName 后,p1.Name = %s\n", p1.Name) + + p1.SayHelloPtr() // ✅ Go 自动取地址 (&p1) + p1.SetNamePtr("Anna") // ✅ 真正修改了 p1 + fmt.Printf("调用指针接收者 SetNamePtr 后,p1.Name = %s\n", p1.Name) + + fmt.Println("\n=== 2. 使用指针类型变量调用方法 ===") + p2 := &Person{Name: "Bob", Age: 25} + p2.SayHello() // ✅ Go 自动解引用 (*p2) + p2.SayHelloPtr() // ✅ 直接调用 + + p2.SetName("Bobby") // ❌ 副本修改,无效 + fmt.Printf("调用值接收者 SetName 后,p2.Name = %s\n", p2.Name) + + p2.SetNamePtr("Robert") // ✅ 真正修改 + fmt.Printf("调用指针接收者 SetNamePtr 后,p2.Name = %s\n", p2.Name) + + fmt.Println("\n=== 3. 关键规则总结 ===") + fmt.Println("- 值接收者:操作副本,不能修改原结构体") + fmt.Println("- 指针接收者:操作原始数据,可修改") + fmt.Println("- Go 会自动处理 & 和 * 转换(只要变量可寻址)") + fmt.Println("- 如果方法需要修改字段,请使用指针接收者!") +} diff --git a/go-sync-practice/go.mod b/go-sync-practice/go.mod new file mode 100644 index 0000000..a6a9462 --- /dev/null +++ b/go-sync-practice/go.mod @@ -0,0 +1,3 @@ +module go-sync-practice + +go 1.22.2 diff --git a/go-sync-practice/mutex_counter.go b/go-sync-practice/mutex_counter.go new file mode 100644 index 0000000..b5917cc --- /dev/null +++ b/go-sync-practice/mutex_counter.go @@ -0,0 +1,28 @@ +package main + +import ( + "fmt" + "sync" +) + +func main() { + var wg sync.WaitGroup + var mu sync.Mutex + counter := 0 + + for i := 0; i < 1000; i++ { + wg.Add(1) + go func() { + defer wg.Done() + for j := 0; j < 1000; j++ { + mu.Lock() + counter++ + mu.Unlock() + } + }() + } + + wg.Wait() + fmt.Println("期待的结果:", 1000*1000) + fmt.Println("实际结果:", counter) +} diff --git a/go-sync-practice/race_counter.go b/go-sync-practice/race_counter.go new file mode 100644 index 0000000..046da34 --- /dev/null +++ b/go-sync-practice/race_counter.go @@ -0,0 +1,25 @@ +package main + +import ( + "fmt" + "sync" +) + +func main() { + var wg sync.WaitGroup + counter := 0 + + for i := 0; i < 1000; i++ { + wg.Add(1) + go func() { + defer wg.Done() + for j := 0; j < 1000; j++ { + counter++ + } + }() + } + + wg.Wait() + fmt.Println("期待的结果:", 1000*1000) + fmt.Println("实际结果:", counter) +} diff --git a/go-sync-practice/rwmutex_cache.go b/go-sync-practice/rwmutex_cache.go new file mode 100644 index 0000000..c308a2e --- /dev/null +++ b/go-sync-practice/rwmutex_cache.go @@ -0,0 +1,55 @@ +package main + +import ( + "fmt" + "sync" + "time" +) + +type Cache struct { + mu sync.RWMutex + data map[string]string +} + +func (c *Cache) Get(key string) string { + c.mu.RLock() + defer c.mu.RUnlock() + return c.data[key] +} + +func (c *Cache) Set(key, value string) { + c.mu.Lock() + defer c.mu.Unlock() + c.data[key] = value +} + +func main() { + c := &Cache{data: make(map[string]string)} + c.Set("foo", "bar") + + var wg sync.WaitGroup + + // 多个读 goroutine + for i := 0; i < 5; i++ { + wg.Add(1) + go func(id int) { + defer wg.Done() + for j := 0; j < 5; j++ { + v := c.Get("foo") + fmt.Printf("reader-%d 第 %d 次读到: %s\n", id, j, v) + time.Sleep(100 * time.Millisecond) + } + }(i) + } + + // 一个写 goroutine + wg.Add(1) + go func() { + defer wg.Done() + time.Sleep(300 * time.Millisecond) + fmt.Println("writer: 更新 foo -> baz") + c.Set("foo", "baz") + }() + + wg.Wait() +} diff --git a/go-sync-practice/waitgroup_like_latch.go b/go-sync-practice/waitgroup_like_latch.go new file mode 100644 index 0000000..212d12d --- /dev/null +++ b/go-sync-practice/waitgroup_like_latch.go @@ -0,0 +1,29 @@ +package main + +import ( + "fmt" + "sync" + "time" +) + +func worker(id int, wg *sync.WaitGroup) { + defer wg.Done() + fmt.Printf("worker-%d 开始工作\n", id) + time.Sleep(time.Duration(id) * 300 * time.Millisecond) + fmt.Printf("worker-%d 完成\n", id) +} + +func main() { + var wg sync.WaitGroup + + n := 3 + wg.Add(n) + + for i := 1; i <= n; i++ { + go worker(i, &wg) + } + + fmt.Println("main: 等待所有 worker 完成...") + wg.Wait() + fmt.Println("main: 全部完成") +} diff --git a/hello-go/go.mod b/hello-go/go.mod new file mode 100644 index 0000000..f12159a --- /dev/null +++ b/hello-go/go.mod @@ -0,0 +1,3 @@ +module hello-go + +go 1.22.2 diff --git a/hello-go/hello-go b/hello-go/hello-go new file mode 100755 index 0000000..198c1c4 Binary files /dev/null and b/hello-go/hello-go differ diff --git a/hello-go/main.go b/hello-go/main.go new file mode 100644 index 0000000..7399f67 --- /dev/null +++ b/hello-go/main.go @@ -0,0 +1,7 @@ +package main + +import "fmt" + +func main() { + fmt.Println("Hello, Go from Linux Mint!") +}