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

294 lines
7.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## 1.27版本变化
### 支持泛型方法
Go 1.27 虽然开放了泛型方法,但有明确的限制:
1.接口定义中不允许包含泛型方法。
2.拥有泛型方法的方法,不能用于实现接口。
```go
// 普通结构体,本身没有任何泛型参数
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
嵌套或匿名结构体中的字段,现在可以直接初始化,不必再一层层嵌套字面量
```go
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
// 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` 是专门用于**密码哈希**的算法,不是可逆加密。
```text
password
↓
bcrypt
↓
hash
```
#### 核心原则
```text
数据库只存 hash
bcrypt 自动处理 salt
使用 CompareHashAndPassword 验证
cost 根据服务器性能决定
不要自己用 MD5 / SHA256 存密码
```
------
#### cost
`cost` 是 bcrypt 的计算难度。
生产环境通常可以从 `10 ~ 12` 开始测试,具体值应根据服务器性能决定。单次 bcrypt 计算控制在大约 `100ms ~ 300ms` 是比较常见的工程范围。
`cost` 每增加 1,计算量大约接近翻倍。
cost 越高:
- 计算越慢
- 暴力破解成本越高
- 服务器 CPU 消耗也越高
bcrypt 生成的 hash 本身会记录当时使用的 `cost`。
因此,即使生产环境将当前配置从:
```text
cost = 10
```
升级为:
```text
cost = 12
```
数据库中原本使用 `cost = 10` 生成的旧 hash,仍然可以通过:
```go
bcrypt.CompareHashAndPassword(...)
```
正常验证。
`CompareHashAndPassword` 会根据已有 hash 中记录的信息进行密码验证,不依赖当前系统配置的 cost。
登录成功后,可以通过 `bcrypt.Cost()` 获取旧 hash 使用的 cost。如果低于当前配置,可以重新生成 hash 并更新数据库。
------
#### 72-byte 限制
bcrypt 密码输入最多支持约 `72 bytes`。
如果只允许 ASCII:
```text
72 bytes ≈ 72 个字符
```
通常已经足够。
建议业务层主动限制:
```go
if len([]byte(password)) > 72 {
return errors.New("password too long")
}
```
------
### 基础使用
#### 依赖导入
```go
import "golang.org/x/crypto/bcrypt"
```
`golang.org/x/crypto/bcrypt` 属于 Go 官方维护的扩展库,但不属于 Go 标准库。
------
#### 生成 hash
```go
hash, err := bcrypt.GenerateFromPassword(
[]byte(password),
12,
)
```
生成的 hash 类似:
```text
$2a$12$xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```
其中大致包含:
```text
$2a$ → bcrypt 版本
12$ → cost
后续内容 → 编码后的 salt + hash
```
bcrypt 会自动生成随机 salt,因此不需要自己生成或额外保存 salt。
------
#### 验证密码
```go
err := bcrypt.CompareHashAndPassword(
[]byte(passwordHash),
[]byte(password),
)
if err == nil {
// 密码正确
}
```
------
#### 判断密码是否相同
不能直接比较两个 bcrypt hash,因为 bcrypt 每次都会生成新的随机 salt。
即使密码完全相同:
```text
hash1 != hash2
```
判断某个密码是否和旧密码相同时,应使用:
```go
err := bcrypt.CompareHashAndPassword(
[]byte(oldHash),
[]byte(password),
)
if err == nil {
// password 与 oldHash 对应的密码相同
}
```
------
#### 获取 hash 的 cost
```go
cost, err := bcrypt.Cost(
[]byte(user.PasswordHash),
)
```
通常用于**用户登录成功后**,判断当前密码 hash 是否使用了旧的 cost。
例如:
```text
数据库 hash cost = 10
当前系统 cost = 12
↓
登录成功
↓
重新生成 cost = 12 的 hash
↓
更新数据库
```
这样可以在用户正常登录过程中逐步升级旧密码 hash。