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
+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]
}
}
}
}