Files
lab/note/资源/编程/Golang/版本变化/1.27.md
T

7.2 KiB
Raw Blame History

1.27版本变化

支持泛型方法

Go 1.27 虽然开放了泛型方法,但有明确的限制:

1.接口定义中不允许包含泛型方法。

2.拥有泛型方法的方法,不能用于实现接口。

	// 普通结构体,本身没有任何泛型参数
	type Serializer struct{}

	// T 属于方法 Serialize,每个调用可以独立拥有不同的 T
	func (s *Serializer) Serialize[T any](data T) string {
		return fmt.Sprintf("%v", data)
	}

	// 使用:
	sz := Serializer{}

	// 同一个 sz 实例,每次调用都可以处理完全不同的类型!
	str1 := sz.Serialize("hello") // 自动推导 T = string
	str2 := sz.Serialize(123)     // 自动推导 T = int
	str3 := sz.Serialize(true)    // 自动推导 T = bool

结构体字面量:字段选择器当 key

嵌套或匿名结构体中的字段,现在可以直接初始化,不必再一层层嵌套字面量

	type Person struct {
		Sex  string
		Name string
	}
	type Student struct {
		StudentID int
		Person
	}

// Go 1.27 之前:必须显式嵌套 Person
	s1 := Student{
		StudentID: 1,
		Person: Person{
			Sex: "male",
			Name: "张三",
		},
	}
// Go 1.27:嵌套结构体Person的字段可以直接当 key 用
	s1 := Student{
		StudentID: 1,
		Sex:       "male",
		Name:      "张三",
	}

函数类型推断:从“手动挡”到“自动挡”

	// Go 1.27 之前:必须手动写出类型实参 [int],否则编译报错
	fn := IntToString(FormatToString[int])
	formatter:= []IntToString{FormatToString[int]}

	// Go 1.27 后:自动推导,不用手写
	fn := IntToString(FormatToString)
	formatter := []IntToString{FormatToString}

标准库更新:encoding/json/v2

Go 1.27 标准库中的 v1 encoding/json 内部已经被改用 v2 引擎驱动,保留了 v1 的原始 API 签名和行为

新旧版本主要行为对比

场景 encoding/json (v1) encoding/json/v2
nil 切片/Map 序列化 转为 null 转为 [] 和 {} (符合语义,防前端空指针)
重复键处理 (Duplicate Keys) 允许,后面的覆盖前面的 直接报错 (增强安全性,防 JSON 注入)
非法 UTF-8 字符 格式化替换,静默忽略 直接报错 (要求严格符合规范)
字段名匹配 (大小写敏感) 默认忽略大小写匹配 默认严格匹配大小写 (提升解析性能)
固定长度数组解包 长度不一致时自动截断或补零 必须与 JSON 数组长度严格相等,否则报错

支持可选项

Options 作用
json.FormatNilSliceAsNull(true) 允许使用 v1 的 nil 行为
json.MatchCaseInsensitiveNames(true) 允许忽略大小写

新增标签omitzero

维度 omitempty omitzero
判断标准 是否为“空 (Empty)” (如:空字符串 ""、长度为 0 的切片/Map、false、0、指针 nil) 是否为“ Go 类型的零值 (Zero Value)” (满足 reflect.Value.IsZero() 或实现 IsZero() bool 接口)
结构体/时间类型处理 有重大缺陷,对非指针的结构体(如 time.Time)无效!依然会被序列化。 完美支持 未初始化的 time.Time{} 或任何嵌套结构体零值均会被正确跳过。
自定义类型零值 不支持自定义零值判断逻辑。 支持只要实现 IsZero() bool 方法即可自定义是否跳过。

官方扩展库 bcrypt

bcrypt 是什么

bcrypt 是专门用于密码哈希的算法,不是可逆加密。

password
↓
bcrypt
↓
hash

核心原则

数据库只存 hash
bcrypt 自动处理 salt
使用 CompareHashAndPassword 验证
cost 根据服务器性能决定
不要自己用 MD5 / SHA256 存密码

cost

cost 是 bcrypt 的计算难度。

生产环境通常可以从 10 ~ 12 开始测试,具体值应根据服务器性能决定。单次 bcrypt 计算控制在大约 100ms ~ 300ms 是比较常见的工程范围。

cost 每增加 1,计算量大约接近翻倍。

cost 越高:

  • 计算越慢
  • 暴力破解成本越高
  • 服务器 CPU 消耗也越高

bcrypt 生成的 hash 本身会记录当时使用的 cost。

因此,即使生产环境将当前配置从:

cost = 10

升级为:

cost = 12

数据库中原本使用 cost = 10 生成的旧 hash,仍然可以通过:

bcrypt.CompareHashAndPassword(...)

正常验证。

CompareHashAndPassword 会根据已有 hash 中记录的信息进行密码验证,不依赖当前系统配置的 cost。

登录成功后,可以通过 bcrypt.Cost() 获取旧 hash 使用的 cost。如果低于当前配置,可以重新生成 hash 并更新数据库。


72-byte 限制

bcrypt 密码输入最多支持约 72 bytes。

如果只允许 ASCII:

72 bytes ≈ 72 个字符

通常已经足够。

建议业务层主动限制:

if len([]byte(password)) > 72 {
	return errors.New("password too long")
}

基础使用

依赖导入

import "golang.org/x/crypto/bcrypt"

golang.org/x/crypto/bcrypt 属于 Go 官方维护的扩展库,但不属于 Go 标准库。


生成 hash

hash, err := bcrypt.GenerateFromPassword(
	[]byte(password),
	12,
)

生成的 hash 类似:

$2a$12$xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

其中大致包含:

$2a$  → bcrypt 版本
12$   → cost
后续内容 → 编码后的 salt + hash

bcrypt 会自动生成随机 salt,因此不需要自己生成或额外保存 salt。


验证密码

err := bcrypt.CompareHashAndPassword(
	[]byte(passwordHash),
	[]byte(password),
)

if err == nil {
	// 密码正确
}

判断密码是否相同

不能直接比较两个 bcrypt hash,因为 bcrypt 每次都会生成新的随机 salt。

即使密码完全相同:

hash1 != hash2

判断某个密码是否和旧密码相同时,应使用:

err := bcrypt.CompareHashAndPassword(
	[]byte(oldHash),
	[]byte(password),
)

if err == nil {
	// password 与 oldHash 对应的密码相同
}

获取 hash 的 cost

cost, err := bcrypt.Cost(
	[]byte(user.PasswordHash),
)

通常用于用户登录成功后,判断当前密码 hash 是否使用了旧的 cost。

例如:

数据库 hash cost = 10
当前系统 cost = 12
↓
登录成功
↓
重新生成 cost = 12 的 hash
↓
更新数据库

这样可以在用户正常登录过程中逐步升级旧密码 hash。