wip: 保存 Go 练习和技术笔记
This commit is contained in:
@@ -0,0 +1,294 @@
|
||||
## 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。
|
||||
Reference in New Issue
Block a user