wip: 保存 Go 练习和技术笔记
This commit is contained in:
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user