wip: 保存 Go 练习和技术笔记

This commit is contained in:
2026-09-24 14:16:17 +08:00
parent 64cc3ec381
commit babd8f2793
20 changed files with 1229 additions and 29 deletions
+6 -1
View File
@@ -1,4 +1,6 @@
## `errors.AsType`
## `errors`
### `errors.AsType`
> *用泛型把* `errors.As` *的“先声明变量再传指针”简化为“直接返回* `(E, bool)`*”,是* `errors.As` *的语法糖升级*
@@ -20,3 +22,6 @@ if mysqlErr, ok := errors.AsType[*mysql.MySQLError](err); ok {
}
```
## `net/http/pprof`
@@ -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。
@@ -0,0 +1,45 @@
# squirrel 的坑
> `github.com/Masterminds/squirrel`,纯 SQL 查询构建器(query builder),2026-09 查证。
## 坑 1:Like 家族多 key 不排序,SQL 输出不确定(真雷)
`Eq` / `NotEq` / `Lt` / `Gt` 等 map 条件生成 SQL 前会对 key 做 `sort.Strings` 字典序排序(v1.1.0 起,`expr.go` 的 `getSortedKeys`),输出确定。
但 `Like` / `NotLike` / `ILike` / `NotILike` 的 `toSql` 是直接 `for key, val := range lk`——跟随 Go map 随机遍历序,**多 key 时每次生成的 SQL 条件顺序随机**:
```
第1次: WHERE nickname LIKE ? AND mobile LIKE ? | args=[%张% %138%]
第3次: WHERE mobile LIKE ? AND nickname LIKE ? | args=[%138% %张%] ← 顺序翻转
```
### 后果
- 查询结果没错(AND 交换律),但 **SQL 字符串 + args 顺序在变**
- 如果用「SQL 字符串 + 参数」生成缓存 key(hash 后当 Redis key 之类),同一查询会打出多个 key:
- 命中率暴跌:n 个 Like 条件最多 n! 种排列 → n! 个缓存 key
- 同一份数据存多份副本,TTL 内占内存
- 依赖 SQL diff 的单测会随机失败
### 规避
- `Like` 只放**单个 key**
- 多模糊条件拆成多次 `.Where(...)`(内部是 append 切片,天然保序)或用 `sq.And{...}` 显式控制顺序
### 上游状态
- 无任何 issue 报告过此事(全量搜索过,含 closed),作者从未回应
- 当年修 Eq 排序的 PR #141 只改了 Eq/Lt 两处,紧邻的 Like 一行未动,讨论里也没人发现——纯遗漏,非有意设计(作者在 #135 明确说过支持排序)
- 库近乎停滞:最新 release v1.5.4(2023-03),master 最后 commit 2024-02,**别指望上游修**
## 坑 2:Eq 排序按字典序,与联合索引列序无关(知道即可)
`Eq{"b":2, "a":1}` 永远生成 `WHERE a = ? AND b = ?`,与你书写的顺序无关(issue #321)。
对主流数据库**基本无影响**:MySQL / PostgreSQL / SQL Server / Oracle 的 CBO 都会重排谓词,索引选择看的是「条件覆盖了哪些列(最左前缀)、选择度、类型是否隐式转换」,不是 WHERE 里的书写顺序。只有对谓词顺序敏感的引擎/老优化器才会踩到。
## 顺带:这个库的亮点
- builder 是**值语义不可变**的(基于 lann/builder 持久化数据结构),每次链式调用返回新副本,可安全分叉复用,不像 GORM 那样共享 Statement、分叉前要 `Session()` 克隆
- `Eq` 家族的确定性输出有源码保证(`expr.go` 排序逻辑),可放心用于缓存 key 场景
- 子查询:`FromSelect(builder, alias)`,或任何 builder 作为 `Sqlizer` 嵌进 `Where/Having/Join`
+61
View File
@@ -0,0 +1,61 @@
# Casbin 学习笔记
> 版本基线:casbin **v3.11.0**;Go import 路径 `github.com/casbin/casbin/v3`(源码仓库 `apache/casbin`)。本文是概念速记,不是教程。
> Casbin 负责**授权**(能不能做某事),不负责**身份认证**(你是谁)。
## 1. 心智模型:PERM
| 段 | 作用 |
|---|---|
| `[request_definition]` `r = sub, obj, act` | 一次访问请求长什么样 |
| `[policy_definition]` `p = sub, obj, act` | 一条策略规则长什么样 |
| `[policy_effect]` `e = some(where (p.eft == allow))` | 多条策略如何合成结论 |
| `[matchers]` `m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act` | `r` 与 `p` 如何配对 |
- `sub, obj, act` 只是最典型的字段,不是硬编码:可加 `dom`、资源属性,也可用对象做 ABAC。
- RBAC 再加 `[role_definition] g = _,_`,表示主体与角色的继承关系;`g(a, b)` 意为“a 属于 b”,角色链会递归解析。
- 模型只描述“怎么判”,策略才决定“允许谁”,两者可分开存储与更新。
## 2. 核心组件
Casbin 运行时由以下组件构成,多数可在不同程度上替换或选配:
| 组件 | 职责 |
|---|---|
| Model | PERM 四段组成的规则模型,描述匹配逻辑,不含具体策略 |
| Policy | 具体策略规则的集合,可在内存或外部存储 |
| Enforcer | 判定入口:把请求套进模型与策略,得到 true/false |
| Adapter | 策略的存储读写层(DB/文件等),由应用选配 |
| RoleManager | 维护角色继承关系,支撑 `g()` |
| Watcher | 感知其他实例的变更并触发重载;Dispatcher 负责派发变更事件 |
组件协作:Model + Policy 交给 Enforcer 判定;Adapter 负责把 Policy 读入/写出;RoleManager 供 `g()` 解析继承;Watcher/Dispatcher 让多实例感知变更。
## 3. 判定链与策略示意
请求 → matcher 匹配 → 命中的策略 effect 合成 → 结果。
```
g, alice, admin # alice 属于 admin
p, admin, report, read # admin 可读 report
Enforce(alice, report, read) => true # 经角色继承
Enforce(bob, report, read) => false # 无授权
```
- 判定按请求逐条扫描策略:matcher 命中后,由 effect 合成最终结论。
- `some(where (p.eft == allow))` 是白名单语义:只要有一条 allow 就放行。
## 4. 关键提醒
- **deny 与 error 都 fail-closed**:`false` 是判定为拒,`err != nil` 是判定失败;安全场景两者都当拒绝,别把错误当放行。
- **并发优先 `SyncedEnforcer`**:常用 API 有锁;普通 `Enforcer` 需自行加锁,也不要假设所有接口都并发安全。
- **内存策略不会自动跨实例同步**:Casbin 核心不内置同步,多副本要自己配置 Watcher 通知或周期重载/对账。
- **ABAC 能判断单条对象的属性/所有权,但不会自动生成 SQL 查询过滤**,列表与行级数据范围仍需业务层实现。
- **迁移 v2 → v3**:只确认 adapter 自身路径带 `/v3` 不够,要核对其 `go.mod` 是否依赖 `github.com/casbin/casbin/v3`;v2 与 v3 的 `model.Model` 是不同 Go 包类型。
- `Enforce` 的参数要与 `r` 定义一致,类型/语义由模型与 matcher 决定,不是固定三元组。
## 官方来源
- v3.0.0 release(module path 变更):<https://github.com/apache/casbin/releases/tag/v3.0.0>
- v3.11.0 `persist/adapter.go`:<https://github.com/apache/casbin/blob/v3.11.0/persist/adapter.go>
- ABAC 文档:<https://casbin.org/docs/abac>
+5
View File
@@ -0,0 +1,5 @@
opencode添加mcp
```bash
opencode mcp add postgres -- npx -y @microsoft/postgres-mcp run
```
@@ -0,0 +1,12 @@
常见图片格式的核心区别,主要在于 **是否压缩、是否支持透明、是否支持矢量、文件大小、适合什么场景**
| 格式 | 类型 | 透明背景 | 动画 | 放大是否模糊 | 文件大小 | 常见用途 |
| -------------- | ---- | ---------- | ------ | ------------ | -------- | ------------------------ |
| **JPG / JPEG** | 位图 | ❌ | ❌ | ✅ 会模糊 | 小 | 照片、摄影、电商图 |
| **PNG** | 位图 | ✅ | ❌ | ✅ 会模糊 | 中~大 | Logo、截图、透明图片、UI |
| **SVG** | 矢量 | ✅ | 可实现 | ❌ 不模糊 | 通常很小 | Logo、图标、网页插画 |
| **WebP** | 位图 | ✅ | ✅ | ✅ 会模糊 | 很小 | 网站图片、App 图片 |
| **GIF** | 位图 | ✅但有限 | ✅ | ✅ 会模糊 | 通常较大 | 简单动图、表情包 |
| **AVIF** | 位图 | ✅ | ✅ | ✅ 会模糊 | 很小 | 新一代网页图片 |
| **BMP** | 位图 | 通常不支持 | ❌ | ✅ 会模糊 | 非常大 | 较少使用 |
| **TIFF** | 位图 | 视情况 | ❌ | ✅ 会模糊 | 很大 | 印刷、扫描、专业摄影 |