## 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。