wip: 保存 Go 练习和技术笔记

This commit is contained in:
2026-09-24 14:16:17 +08:00
parent 64cc3ec381
commit babd8f2793
20 changed files with 1229 additions and 29 deletions
+100
View File
@@ -0,0 +1,100 @@
package linkedlist
import (
"fmt"
"strings"
)
// Node 是单向链表的一个节点,持有数据与指向下一个节点的指针。
type Node[T comparable] struct {
Data T
Next *Node[T]
}
// LinkedList 是持有头指针的单向链表,零值即为空表。
type LinkedList[T comparable] struct {
head *Node[T]
}
// InsertHead 将新节点插入表头。
func (l *LinkedList[T]) InsertHead(data T) {
l.head = &Node[T]{Data: data, Next: l.head}
}
// InsertTail 将新节点追加到表尾;空表时新节点直接成为表头。
func (l *LinkedList[T]) InsertTail(data T) {
newNode := &Node[T]{Data: data}
if l.head == nil {
l.head = newNode
return
}
current := l.head
for current.Next != nil {
current = current.Next
}
current.Next = newNode
}
// DeleteHead 删除表头节点;空表时无操作。
func (l *LinkedList[T]) DeleteHead() {
if l.head != nil {
l.head = l.head.Next
}
}
// DeleteTail 删除表尾节点;空表时无操作,仅剩一个节点时表变空。
func (l *LinkedList[T]) DeleteTail() {
switch {
case l.head == nil:
case l.head.Next == nil:
l.head = nil
default:
current := l.head
for current.Next.Next != nil {
current = current.Next
}
current.Next = nil
}
}
// Search 返回首个 Data 等于 data 的节点;不存在时返回 nil。
func (l *LinkedList[T]) Search(data T) *Node[T] {
for current := l.head; current != nil; current = current.Next {
if current.Data == data {
return current
}
}
return nil
}
// Len 返回链表的节点个数。
func (l *LinkedList[T]) Len() int {
count := 0
for current := l.head; current != nil; current = current.Next {
count++
}
return count
}
// Reverse 原地反转链表。
func (l *LinkedList[T]) Reverse() {
var prev, current *Node[T]
current = l.head
for current != nil {
next := current.Next
current.Next = prev
prev = current
current = next
}
l.head = prev
}
// String 以 "1 -> 2 -> nil" 的格式输出整条链,便于打印与调试。
func (l LinkedList[T]) String() string {
var sb strings.Builder
for current := l.head; current != nil; current = current.Next {
fmt.Fprintf(&sb, "%v -> ", current.Data)
}
sb.WriteString("nil")
return sb.String()
}
+29
View File
@@ -0,0 +1,29 @@
package main
import (
"fmt"
"lab/algorithms/linkedlist"
)
func main() {
var list linkedlist.LinkedList[int] // 零值即为空表
list.InsertTail(3)
list.InsertTail(5)
list.InsertHead(1)
list.InsertHead(0)
fmt.Println(&list) // 0 -> 1 -> 3 -> 5 -> nil
list.DeleteHead()
list.DeleteTail()
fmt.Println(&list) // 1 -> 3 -> nil
found := list.Search(3)
if found != nil {
fmt.Printf("找到节点,Data = %d\n", found.Data)
}
list.Reverse()
fmt.Println(&list) // 3 -> 1 -> nil
fmt.Printf("链表长度: %d\n", list.Len())
}
+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)
}
+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)
}
}
+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()
}
}
+38 -10
View File
@@ -1,15 +1,43 @@
module lab
go 1.26.2
go 1.27.0
require (
github.com/richardlehane/mscfb v1.0.7 // indirect
github.com/richardlehane/msoleps v1.0.6 // indirect
github.com/tiendc/go-deepcopy v1.7.2 // indirect
github.com/xuri/efp v0.0.1 // indirect
github.com/xuri/excelize/v2 v2.11.0 // indirect
github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9 // indirect
golang.org/x/crypto v0.53.0 // indirect
golang.org/x/net v0.56.0 // indirect
golang.org/x/text v0.38.0 // indirect
github.com/gin-contrib/sessions v1.1.2
github.com/gin-gonic/gin v1.12.0
golang.org/x/crypto v0.56.0
)
require (
github.com/bytedance/gopkg v0.1.4 // indirect
github.com/bytedance/sonic v1.15.2 // indirect
github.com/bytedance/sonic/loader v0.5.1 // indirect
github.com/cloudwego/base64x v0.1.7 // indirect
github.com/gabriel-vasile/mimetype v1.4.13 // indirect
github.com/gin-contrib/sse v1.1.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.30.3 // indirect
github.com/goccy/go-json v0.10.6 // indirect
github.com/goccy/go-yaml v1.19.2 // indirect
github.com/gorilla/context v1.1.2 // indirect
github.com/gorilla/securecookie v1.1.2 // indirect
github.com/gorilla/sessions v1.4.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.4.0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-isatty v0.0.23 // 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.4.3 // indirect
github.com/quic-go/qpack v0.6.0 // indirect
github.com/quic-go/quic-go v0.60.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.3.1 // indirect
go.mongodb.org/mongo-driver/v2 v2.8.0 // indirect
golang.org/x/arch v0.29.0 // indirect
golang.org/x/net v0.57.0 // indirect
golang.org/x/sys v0.47.0 // indirect
golang.org/x/text v0.41.0 // indirect
google.golang.org/protobuf v1.36.11 // indirect
)
+100 -18
View File
@@ -1,18 +1,100 @@
github.com/richardlehane/mscfb v1.0.7 h1:oeoiM0WE79vHwE8RpIYYvIAc8ajTH2mb6UZm55/+EB0=
github.com/richardlehane/mscfb v1.0.7/go.mod h1:pe0+IUIc0AHh0+teNzBlJCtSyZdFOGgV4ZK9bsoV+Jo=
github.com/richardlehane/msoleps v1.0.6 h1:9BvkpjvD+iUBalUY4esMwv6uBkfOip/Lzvd93jvR9gg=
github.com/richardlehane/msoleps v1.0.6/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
github.com/tiendc/go-deepcopy v1.7.2 h1:Ut2yYR7W9tWjTQitganoIue4UGxZwCcJy3orjrrIj44=
github.com/tiendc/go-deepcopy v1.7.2/go.mod h1:4bKjNC2r7boYOkD2IOuZpYjmlDdzjbpTRyCx+goBCJQ=
github.com/xuri/efp v0.0.1 h1:fws5Rv3myXyYni8uwj2qKjVaRP30PdjeYe2Y6FDsCL8=
github.com/xuri/efp v0.0.1/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI=
github.com/xuri/excelize/v2 v2.11.0 h1:HxaEFl6sRN2+8J5a8HaKq+0M4FsjBGMnWWtjOCPSG88=
github.com/xuri/excelize/v2 v2.11.0/go.mod h1:jxFLbzaIwGQ5ufFNvYfUOHqXhfPaNmP14KWfmNz2Uak=
github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9 h1:+C0TIdyyYmzadGaL/HBLbf3WdLgC29pgyhTjAT/0nuE=
github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ=
golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto=
golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio=
golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o=
golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec=
golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE=
golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4=
github.com/bytedance/gopkg v0.1.4 h1:oZnQwnX82KAIWb7033bEwtxvTqXcYMxDBaQxo5JJHWM=
github.com/bytedance/gopkg v0.1.4/go.mod h1:v1zWfPm21Fb+OsyXN2VAHdL6TBb2L88anLQgdyje6R4=
github.com/bytedance/sonic v1.15.2 h1:90H+rcF/FwLXwfB1cudOLq/je83n683Utf4Cbp0xHCo=
github.com/bytedance/sonic v1.15.2/go.mod h1:mT2NbXunuaEbnZ+mRIX/vYqKISmgEuHFDI4UzmKx2SA=
github.com/bytedance/sonic/loader v0.5.1 h1:Ygpfa9zwRCCKSlrp5bBP/b/Xzc3VxsAW+5NIYXrOOpI=
github.com/bytedance/sonic/loader v0.5.1/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
github.com/cloudwego/base64x v0.1.7 h1:NppS+Fgzg5ovhn4NkUXaDT3x9jldgH5ToMCqzBSi2zI=
github.com/cloudwego/base64x v0.1.7/go.mod h1:Cu1PV9zfrSf7ET2tIbWbbEy7jO7HHJ13q4X2SQ8aWYg=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM=
github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
github.com/gin-contrib/sessions v1.1.2 h1:utWRqOHT/kbqxxqF/mtutV9wfGLi1+uu5YuvWUhj/Ow=
github.com/gin-contrib/sessions v1.1.2/go.mod h1:qRxYwA/TYJw0wv28oFzs4gY/8G8z69id862SNgX9Gmw=
github.com/gin-contrib/sse v1.1.1 h1:uGYpNwTacv5R68bSGMapo62iLTRa9l5zxGCps4hK6ko=
github.com/gin-contrib/sse v1.1.1/go.mod h1:QXzuVkA0YO7o/gun03UI1Q+FTI8ZV/n5t03kIQAI89s=
github.com/gin-gonic/gin v1.12.0 h1:b3YAbrZtnf8N//yjKeU2+MQsh2mY5htkZidOM7O0wG8=
github.com/gin-gonic/gin v1.12.0/go.mod h1:VxccKfsSllpKshkBWgVgRniFFAzFb9csfngsqANjnLc=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
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.30.3 h1:4MU6YkEwx7GbcPJOZxrtbu+QfF3pJLJuaYTeAH0DYy8=
github.com/go-playground/validator/v10 v10.30.3/go.mod h1:4Axh7oCNGcoGkqLoE4YWt6n20mcEIsPRlB7vPk3lpyc=
github.com/goccy/go-json v0.10.6 h1:p8HrPJzOakx/mn/bQtjgNjdTcN+/S6FcG2CTtQOrHVU=
github.com/goccy/go-json v0.10.6/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/gorilla/context v1.1.2 h1:WRkNAv2uoa03QNIc1A6u4O7DAGMUVoopZhkiXWA2V1o=
github.com/gorilla/context v1.1.2/go.mod h1:KDPwT9i/MeWHiLl90fuTgrt4/wPcv75vFAZLaOOcbxM=
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
github.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzqQ=
github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik=
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.4.0 h1:S6Hrbc7+ywsr0r+RLapfGBHfyefhCTwEh3A0tV913Dw=
github.com/klauspost/cpuid/v2 v2.4.0/go.mod h1:19jmZ9mjzoF//ddRSUsv0zfBTJWh3QJh9FNxZTMrGxU=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/mattn/go-isatty v0.0.23 h1:cYwCQTQf3HB6xUC+BtyCLZNr7IzbOmoZbmssVNzSyiQ=
github.com/mattn/go-isatty v0.0.23/go.mod h1:nMCL3Zebbrt45jsMDgnfIwz6ydEQApk5oEI3HqDio6A=
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.4.3 h1:GTRvJQutkOSftxIFD5xw9aepkYNuPWmVJpffdDPYVpY=
github.com/pelletier/go-toml/v2 v2.4.3/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/quic-go/go-ossfuzz-seeds v0.1.0 h1:APacT+iIaNF6fd8AGEiN3bT/Jtkd2jz4v4TzM7MFjy0=
github.com/quic-go/go-ossfuzz-seeds v0.1.0/go.mod h1:3IOHRbJIc+L6YKMwfDtJAM9Vj9k0YY4muhuyUYk5tbk=
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
github.com/quic-go/quic-go v0.60.0 h1:xcQioE8OM66UQLeUMHltK1CCcOu3JbVB4JAQdDQSB+0=
github.com/quic-go/quic-go v0.60.0/go.mod h1:wpKpjmPpftl30sL6pFh7REVpjbcCVy4zt2vDyK1TuJk=
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/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
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.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
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.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY=
github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
go.mongodb.org/mongo-driver/v2 v2.8.0 h1:CxWDGQYY8QQwNjAl/aq2sfWakdnWZynnqJ9F4DhHbP8=
go.mongodb.org/mongo-driver/v2 v2.8.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0=
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
golang.org/x/arch v0.29.0 h1:8sSET5wB0+exBm0FGmOtdHMqjlRdV2DRD3/IV6OZgho=
golang.org/x/arch v0.29.0/go.mod h1:0X+GdSIP+kL5wPmpK7sdkEVTt2XoYP0cSjQSbZBwOi8=
golang.org/x/crypto v0.56.0 h1:GUh5Ii4J5jtcseSMiRqr1jXCNHoxjeV9Fmekc2oLy6Y=
golang.org/x/crypto v0.56.0/go.mod h1:OMW5y6CY9l38uPLmxU6l6pwcXp1obtLo3e6gT7gQR2I=
golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE=
golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU=
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8=
golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
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=
+149
View File
@@ -0,0 +1,149 @@
package crypto
import (
"errors"
"testing"
"time"
"golang.org/x/crypto/bcrypt"
)
var (
ErrInvalidPassword = errors.New("invalid password")
ErrInvalidHash = errors.New("invalid password hash")
)
type Hasher struct {
cost int // cost 可以理解成 bcrypt 的“计算难度”参数,cost 越高,计算 bcrypt hash 就越慢,攻击者暴力破解密码也越贵
}
func NewHasher(cost int) (*Hasher, error) {
if cost < bcrypt.MinCost || cost > bcrypt.MaxCost {
return nil, errors.New("invalid bcrypt cost")
}
return &Hasher{
cost: cost,
}, nil
}
// Hash 用于注册、重置密码、修改密码。
func (h *Hasher) Hash(password string) (string, error) {
if len(password) == 0 {
return "", ErrInvalidPassword
}
// bcrypt 只使用密码前 72 bytes。
// 生产环境建议显式拒绝,而不是静默截断。
if len([]byte(password)) > 72 {
return "", ErrInvalidPassword
}
hash, err := bcrypt.GenerateFromPassword(
[]byte(password),
h.cost,
)
if err != nil {
return "", err
}
return string(hash), nil
}
// Verify 用于登录时验证密码。
func (h *Hasher) Verify(password, encodedHash string) (bool, error) {
if len(password) == 0 || len([]byte(password)) > 72 {
return false, ErrInvalidPassword
}
if encodedHash == "" {
return false, ErrInvalidHash
}
err := bcrypt.CompareHashAndPassword(
[]byte(encodedHash),
[]byte(password),
)
switch {
case err == nil:
return true, nil
case errors.Is(err, bcrypt.ErrMismatchedHashAndPassword):
return false, nil
default:
// 例如数据库里的 hash 已损坏或格式不合法。
return false, ErrInvalidHash
}
}
// NeedsRehash 判断数据库中的 hash 是否使用了较旧的 cost。
func (h *Hasher) NeedsRehash(encodedHash string) bool {
cost, err := bcrypt.Cost([]byte(encodedHash))
if err != nil {
return true
}
return cost != h.cost
}
func TestBcrypt(t *testing.T) {
oldHasher, err := NewHasher(10)
if err != nil {
t.Fatal(err)
}
newHasher, err := NewHasher(12)
if err != nil {
t.Fatal(err)
}
password := "password"
oldHash, err := oldHasher.Hash(password)
if err != nil {
t.Fatal(err)
}
t.Log(oldHash)
newHash, err := newHasher.Hash(password)
if err != nil {
t.Fatal(err)
}
t.Log(newHash)
err = bcrypt.CompareHashAndPassword([]byte(oldHash), []byte(password))
if err != nil {
t.Fatal(err)
}
}
func TestHashPassword(t *testing.T) {
hasher, err := NewHasher(12)
if err != nil {
t.Fatal(err)
}
password := "password"
hash, err := hasher.Hash(password)
if err != nil {
t.Fatal(err)
}
t.Log(hash)
if ok, err := hasher.Verify(password, hash); err != nil || !ok {
t.Fatal("verify failed")
}
if ok := hasher.NeedsRehash(hash); !ok {
t.Fatal("needs rehash failed")
}
}
func TestGenerateTime(t *testing.T) {
start := time.Now()
_, err := bcrypt.GenerateFromPassword(
[]byte("TestPassword123!"),
14,
)
if err != nil {
t.Fatal(err)
}
t.Log(time.Since(start), err)
}
+68
View File
@@ -0,0 +1,68 @@
package main
import (
"fmt"
"testing"
"time"
// 引入两个包进行对比
jsonV1 "encoding/json"
jsonV2 "encoding/json/v2"
)
func TestV2Null(t *testing.T) {
type Product struct {
Name string `json:"name"`
Tags []string `json:"tags"`
}
p := Product{
Name: "product1",
}
data1, _ := jsonV1.Marshal(p)
data2, _ := jsonV2.Marshal(p)
t.Log(string(data1))
t.Log(string(data2))
}
func TestOmitZeroVsEmpty(t *testing.T) {
// ===== V1 结构体:使用 omitempty =====
type EventV1 struct {
Title string `json:"title"`
CreatedAt time.Time `json:"created_at,omitempty"` // ⚠️ omitempty 对 time.Time 无效
}
// ===== V2 结构体:使用 omitzero =====
type EventV2 struct {
Title string `json:"title"`
CreatedAt time.Time `json:"created_at,omitzero"` // ✅ omitzero 能识别 IsZero()
}
// 都不给 CreatedAt 赋值,保持 Go 的零值 time.Time{}
// 1. 测试 v1 + omitempty
e1 := EventV1{Title: "Go Meetup"}
data1, _ := jsonV1.Marshal(e1)
fmt.Println("v1 + omitempty 输出:", string(data1))
// 2. 测试 v2 + omitzero
e2 := EventV2{Title: "Go Meetup"}
data2, _ := jsonV2.Marshal(e2)
fmt.Println("v2 + omitzero 输出:", string(data2))
}
func TestMultiStyle(t *testing.T) {
type MultiStyleUser struct {
// 1. 严格匹配:JSON key 必须精确是 "user_id"
ID string `json:"user_id,case:strict"`
// 2. 忽略大小写:JSON key 是 "Name"、"NAME"、"name" 都能成功赋值
Name string `json:"name,case:ignore"`
}
// 假设第三方传来的 JSON 风格混杂
jsonData := []byte(`{
"user_id": "1001",
"NAME": "Gopher"
}`)
var m MultiStyleUser
jsonV2.Unmarshal(jsonData, &m)
t.Log(m)
}
+61
View File
@@ -1,6 +1,7 @@
package testing
import (
"fmt"
"sort"
"testing"
"time"
@@ -46,3 +47,63 @@ func TestChannel(t *testing.T) {
var c chan bool // 一个零值nil通道
<-c // 永久阻塞在此
}
func TestFeature1(t *testing.T) {
type Person struct {
Sex string
Name string
}
type Student struct {
StudentID int
Person
}
s1 := Student{
StudentID: 1,
Sex: "male",
Name: "张三",
}
t.Log(s1)
}
func TestFeature2(t *testing.T) {
// Go 1.27 之前:必须手动写出类型实参 [int],否则编译报错
fn1 := IntToString(FormatToString[int])
fn2 := IntToString(FormatToString)
oldFormat := []IntToString{FormatToString[int]}
newFormat := []IntToString{FormatToString}
t.Log(fn1(1))
t.Log(fn2(2))
for i, fn := range oldFormat {
t.Log(fn(i))
}
for i, fn := range newFormat {
t.Log(fn(i))
}
}
// 一个泛型格式化函数
func FormatToString[T any](v T) string {
return fmt.Sprintf("value: %v", v)
}
// 具名函数类型:接收一个 int,返回 string
type IntToString func(int) string
func TestSort(t *testing.T) {
slice := []int{8, 2, 4, 4}
Sort1(slice)
t.Log(slice)
}
func Sort1(slice []int) {
for i := 0; i < len(slice)-1; i++ {
for j := 0; j < len(slice)-1-i; j++ {
if slice[j] > slice[j+1] {
slice[j], slice[j+1] = slice[j+1], slice[j]
}
}
}
}