refactor(go)!: 分离练习区与独立项目区

- 将现有练习及 module 文件迁移至 go/practice,保持代码和依赖不变
- 新增 go/projects 项目区说明,更新目录约定与开发命令

BREAKING CHANGE: Go 练习命令需改在 go/practice 目录下执行
This commit is contained in:
2026-10-15 22:26:57 +08:00
parent 86f6eb39b2
commit d375463de5
21 changed files with 91 additions and 33 deletions
+50
View File
@@ -0,0 +1,50 @@
package main
import (
"context"
"fmt"
"math/rand"
"net/http"
_ "net/http/pprof"
"time"
)
// 来自文章 https://www.cnblogs.com/hwnd/p/20682814
func simulateRequest() {
timeout := 500 * time.Millisecond
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(timeout))
defer cancel()
resultCh := make(chan string) // 无缓冲!
// 启动 worker goroutine 处理"请求"
go func() {
time.Sleep(time.Duration(rand.Intn(500)+300) * time.Millisecond)
select {
case <-time.After(timeout):
fmt.Println("worker 超时,放弃处理")
case <-ctx.Done():
case resultCh <- fmt.Sprintf("处理完成,耗时 %dms", rand.Intn(500)+300):
}
}()
// TODO: 模拟请求取消或超时的情况(比如 500ms 后调用方不再关心结果)
// 如果 worker 还没完成,它就 leak 了
select {
case resultCh := <-resultCh:
fmt.Println(resultCh)
case <-ctx.Done():
fmt.Println("请求方取消了请求")
}
}
func main() {
for i := 0; i < 5; i++ {
simulateRequest()
fmt.Printf("第 %d 个请求处理完毕\n\n", i+1)
time.Sleep(200 * time.Millisecond)
}
fmt.Println("所有请求处理完毕")
// 运行后观察:goroutine 数量只增不减
http.ListenAndServe(":6060", nil)
}
+25
View File
@@ -0,0 +1,25 @@
package main
import (
"fmt"
)
type User struct {
Name string
}
func NewUser() *User {
u := User{}
return &u
}
func ClosureFunc() func() int {
a := 1
return func() int {
return a
}
}
func main() {
fmt.Println(NewUser())
cf:= ClosureFunc()
fmt.Println(cf())
}
+56
View File
@@ -0,0 +1,56 @@
package main
import (
"log"
"net/http"
_ "net/http/pprof" // 注册 pprof 相关 handler
"time"
)
// memoryBlocks 保留一部分内存块,方便内存剖析观察。
// 这个全局变量会一直存活,便于分析堆上的对象。
var memoryBlocks [64][]byte
func main() {
// 启动 pprof HTTP 服务
go func() {
log.Println("pprof server started at http://localhost:6060/debug/pprof/")
if err := http.ListenAndServe("localhost:6060", nil); err != nil {
log.Fatal(err)
}
}()
// 模拟 CPU 密集型业务
go cpuIntensiveTask()
// 模拟内存分配型业务
go memoryIntensiveTask()
// 阻塞主协程,让程序持续运行
select {}
}
// fib 递归计算斐波那契数列,非常耗费 CPU
func fib(n int) int {
if n <= 1 {
return n
}
return fib(n-1) + fib(n-2)
}
// cpuIntensiveTask 循环执行 CPU 密集型计算
func cpuIntensiveTask() {
for {
_ = fib(30)
}
}
// memoryIntensiveTask 循环分配内存并保留在全局切片中
func memoryIntensiveTask() {
for i := 0; ; i++ {
idx := i % len(memoryBlocks)
// 每次分配 1MB,替换掉旧的内存块
memoryBlocks[idx] = make([]byte, 1<<20) // 1 << 20 = 1MB
time.Sleep(100 * time.Millisecond)
}
}
+34
View File
@@ -0,0 +1,34 @@
package main
import (
"fmt"
"runtime"
)
var retained [][]byte // 全局变量,用来保持对象可达
func printMemory(stage string) {
var m runtime.MemStats
runtime.ReadMemStats(&m)
fmt.Printf("%s: HeapAlloc=%d MiB, TotalAlloc=%d MiB\n",
stage,
m.HeapAlloc/1024/1024,
m.TotalAlloc/1024/1024)
}
func main() {
runtime.GC()
printMemory("开始")
for i := 0; i < 20; i++ {
block := make([]byte, 1024*1024) // 每次创建约 1 MiB 的底层数组
block[0] = byte(i)
retained = append(retained, block)
}
printMemory("分配后")
retained = nil // 程序不再持有这些数组
runtime.GC()
printMemory("GC 后")
}
+116
View File
@@ -0,0 +1,116 @@
package main
import (
"net/http"
"time"
"github.com/gin-contrib/sessions"
"github.com/gin-contrib/sessions/cookie"
"github.com/gin-gonic/gin"
)
var (
mockUsers = map[string]string{
"zhangsan": "123456",
"lisi": "654321",
}
// secretKey = "your-secret-key"
hmacSecretKey = "5KiinhGcvoXFmgOttvgMWd9ugadE0KVP"
blockSecretKey = "OLZmfTWHGyjCfJoH1Y8SjbSY"
)
const sessionTTL = 60 * 5
func sessionOptions(maxAge int) sessions.Options {
return sessions.Options{
Path: "/",
Domain: "", // 设置为 "" 表示当前域名
MaxAge: maxAge, // 单位为秒。0 表示不写Max-Age(浏览器会话cookie); -1 表示删除 cookie
Secure: false, // 生产走 HTTPS 时改 true
HttpOnly: true, // true 表示禁止 JS 读取 cookie
SameSite: http.SameSiteStrictMode, // 严格模式,仅在同源请求中发送 cookie
}
}
// 代码来自文章 https://www.cnblogs.com/XY-Heruo/p/22864951
func main() {
r := gin.Default()
// Create cookie-based session store with a secret key
// 传单个密钥时, 只做签名认证,不加密cookie内容. 认证密钥建议 32 或 64 字节
// 传一对密钥时,第一个用于认证(HMAC),第二个用于加密(AES-128, AES-192, AES-256)。加密密钥必须是16、24或32字节长度,对应 AES-128/192/256。
// 多对密钥用于密钥轮换,第一对用于当前加密和认证,后续的用于解密和认证旧的cookie。Example: cookie.NewStore([]byte("newAuth"), []byte("newEnc"), []byte("oldAuth"), []byte("oldEnc"))
store := cookie.NewStore([]byte(hmacSecretKey), []byte(blockSecretKey))
store.Options(sessionOptions(sessionTTL)) // 全局设置 cookie 过期时间为 5 分钟
r.Use(sessions.Sessions("mysession", store))
r.POST("/login", login)
auth := r.Group("/", authRequired())
auth.GET("/profile", profile)
auth.POST("/logout", logout)
r.Run(":8080")
}
func login(c *gin.Context) {
session := sessions.Default(c)
username := c.PostForm("username")
password := c.PostForm("password")
if username == "" || password == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "username or password is empty"})
return
}
if mockUsers[username] != password {
c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid username or password"})
return
}
session.Set("user", username)
session.Set("expire_at", time.Now().Add(time.Second*sessionTTL).Unix()) // 设置过期时间
if err := session.Save(); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "save session failed"})
return
}
c.JSON(http.StatusOK, gin.H{"message": "logged in"})
}
func logout(c *gin.Context) {
session := sessions.Default(c)
session.Clear()
session.Options(sessionOptions(-1)) // 让浏览器删除 cookie
if err := session.Save(); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "save session failed"})
return
}
c.JSON(http.StatusOK, gin.H{"message": "logged out"})
}
func profile(c *gin.Context) {
session := sessions.Default(c)
user := session.Get("user").(string)
c.JSON(http.StatusOK, gin.H{"user": user})
}
func authRequired() gin.HandlerFunc {
return func(c *gin.Context) {
session := sessions.Default(c)
if session.Get("user") == nil {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
return
}
if expireAt, ok := session.Get("expire_at").(int64); !ok || time.Now().Unix() > expireAt {
session.Clear()
session.Options(sessionOptions(-1))
session.Save()
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "expired session"})
return
}
c.Next()
}
}