refactor(go)!: 分离练习区与独立项目区
- 将现有练习及 module 文件迁移至 go/practice,保持代码和依赖不变 - 新增 go/projects 项目区说明,更新目录约定与开发命令 BREAKING CHANGE: Go 练习命令需改在 go/practice 目录下执行
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,34 @@
|
||||
package error
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type MatchError struct {
|
||||
code int
|
||||
err error
|
||||
}
|
||||
|
||||
func (e *MatchError) Error() string {
|
||||
return fmt.Sprintf("code: %d, error: %v", e.code, e.err)
|
||||
}
|
||||
func GetMatchError() error {
|
||||
return &MatchError{code: 1, err: errors.New("123")}
|
||||
}
|
||||
func GetError() error {
|
||||
return errors.New("123")
|
||||
}
|
||||
func TestError(t *testing.T) {
|
||||
if err, ok := errors.AsType[*MatchError](GetMatchError()); ok {
|
||||
t.Log(err)
|
||||
} else {
|
||||
t.Errorf("expected MatchError, got %T", GetMatchError())
|
||||
}
|
||||
if err, ok := errors.AsType[*MatchError](GetError()); ok {
|
||||
t.Log(err)
|
||||
} else {
|
||||
t.Errorf("expected MatchError, got %T", GetError())
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package net
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNet(t *testing.T) {
|
||||
s := net.JoinHostPort("127.0.0.1", "8080")
|
||||
|
||||
t.Log(s)
|
||||
t.Log(s)
|
||||
addrs, err := net.LookupAddr("198.18.0.18")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(addrs)
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
package testing
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestNormal(t *testing.T) {
|
||||
numbers := map[int][]int{
|
||||
1: {8, 2, 4, 4},
|
||||
2: {0, 5, 3},
|
||||
3: {1},
|
||||
}
|
||||
for _, numbers := range numbers {
|
||||
middle := GetMiddNumber(numbers)
|
||||
t.Log(middle)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func GetMiddNumber(numbers []int) float64 {
|
||||
sort.Ints(numbers)
|
||||
length := len(numbers)
|
||||
isOddNUmber := length%2 == 1
|
||||
if !isOddNUmber {
|
||||
return float64(numbers[length/2-1]+numbers[length/2]) / 2.0
|
||||
}
|
||||
return float64(numbers[length/2])
|
||||
}
|
||||
|
||||
func TestChannel(t *testing.T) {
|
||||
var ball = make(chan string)
|
||||
kickBall := func(playerName string) {
|
||||
for {
|
||||
t.Log(<-ball, "传球", "\n")
|
||||
time.Sleep(time.Second)
|
||||
ball <- playerName
|
||||
}
|
||||
}
|
||||
go kickBall("张三")
|
||||
go kickBall("李四")
|
||||
go kickBall("王二麻子")
|
||||
go kickBall("刘大")
|
||||
ball <- "裁判" // 开球
|
||||
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]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package regularmatch
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestChineseRegExp(t *testing.T) {
|
||||
reg, err := regexp.Compile("[\u4e00-\u9fa5]")
|
||||
if err != nil {
|
||||
t.Errorf("Failed to compile regex: %v", err)
|
||||
}
|
||||
matchStrSlice := reg.FindAllString("hello 世界", -1)
|
||||
|
||||
if len(matchStrSlice) != 2 {
|
||||
t.Errorf("Expected 2 matches, got %d", len(matchStrSlice))
|
||||
}
|
||||
if matchStrSlice[0] != "世" || matchStrSlice[1] != "界" {
|
||||
t.Errorf("Expected [世, 界], got %v", matchStrSlice)
|
||||
}
|
||||
}
|
||||
|
||||
func TestChinaMobile(t *testing.T) {
|
||||
reg, err := regexp.Compile(`^1(3\d|4[5-9]|5[0-35-9]|6[2567]|7[0-8]|8\d|9[0-35-9])\d{8}$`)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to compile regex: %v", err)
|
||||
}
|
||||
// matchStrSlice := reg.FindAllString("123-12345678 1234-12345678", -1)
|
||||
// if len(matchStrSlice) != 2 {
|
||||
// t.Errorf("Expected 2 matches, got %d", len(matchStrSlice))
|
||||
// }
|
||||
matchStrSlice := reg.FindAllString("17623862704", -1)
|
||||
t.Log(matchStrSlice[0])
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func appendSlice(s []int, n int) {
|
||||
s = append(s, n)
|
||||
fmt.Printf("len=%d,cap=%d\n", len(s), cap(s))
|
||||
}
|
||||
func TestSliceAppend(t *testing.T) {
|
||||
s1 := make([]int, 5)
|
||||
appendSlice(s1, 10)
|
||||
if len(s1) != 5 || cap(s1) != 5 {
|
||||
t.Errorf("Expected len=5, cap=5; got len=%d, cap=%d", len(s1), cap(s1))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user