feat: 添加 Go 内存分配示例并整理笔记

拆分 Go 第三方库笔记并更新索引。
新增 Gitleaks 使用说明,清理过时笔记。
This commit is contained in:
2026-10-08 16:18:54 +08:00
parent e0676bfc03
commit 1a48960022
16 changed files with 471 additions and 413 deletions
+8 -8
View File
@@ -24,14 +24,14 @@
#### 三、远程操作
| 操作 | 命令 | 说明 |
| ---------------- | --------------------------------------------------------- | ------------------- |
| 克隆仓库 | `git clone <url>` | 拉取远程项目 |
| 拉取远程代码 | `git pull` | 获取并合并远程分支 |
| 推送代码 | `git push` | 推送当前分支到远程 |
| 推送并设置上游 | `git push -u origin <branch>` | 建立跟踪关系 |
| 查看远程地址 | `git remote -v` | 显示远程仓库地址 |
| 远程查询所有标签 | git ls-remote --tags https://github.com/xxx.git \| grep X | 查看X相关的所有标签 |
| 操作 | 命令 | 说明 |
| ---------------- | ----------------------------------------------------------- | ------------------- |
| 克隆仓库 | `git clone <url>` | 拉取远程项目 |
| 拉取远程代码 | `git pull` | 获取并合并远程分支 |
| 推送代码 | `git push` | 推送当前分支到远程 |
| 推送并设置上游 | `git push -u origin <branch>` | 建立跟踪关系 |
| 查看远程地址 | `git remote -v` | 显示远程仓库地址 |
| 远程查询所有标签 | `git ls-remote --tags https://github.com/xxx.git \| grep X` | 查看X相关的所有标签 |
#### 四、合并与变基
@@ -1,200 +0,0 @@
### validator
[GitHub地址](github.com/go-playground/validator)
包验证器,可以用于验证参数是否符合规则,比如下方的比较验证,还有其他可以查看网址的详细介绍
go-zero自带的有校验器,可以使用
| Tag | Description |
| ---- | --------------------- |
| eq | Equals |
| gt | Greater than |
| gte | Greater than or equal |
| lt | Less Than |
| lte | Less Than or Equal |
| ne | Not Equal |
### goverter
[GitHub地址](https://github.com/jmattheis/goverter)
类型转换器,可以用于类型的转换,比如下面的代码
```go
func (l *CreatePopupLogic) CreatePopup(in *backgroundrpcmodel.ResPopup) (*backgroundrpcmodel.ResResult, error) {
_, err := l.svcCtx.PopupModel.Insert(l.ctx, u.Convert.SysPopupR2S(in.Info))
if err != nil {
return nil, rerr.NewFromSQL(err)
}
return &backgroundrpcmodel.ResResult{
Result: true,
}, nil
}
```
使用
```
goverter gen .
```
### asynq
[GitHub网址](https://github.com/hibiken/asynq)
- 入队流程
```go
payload := &jobtype.xxxPayload{}
payloadByte, err := jsoniter.Marshal(payload)
if err != nil {
logt.Error("xxx jsoniter.Marshal err:", err)
return nil, zerr.New(rescode.DataFormatConversionError)
}
_, err = l.svcCtx.AsynqClient.Enqueue(asynq.NewTask(jobtype.xxx, payloadBytes, asynq.Queue(consts.QueueLow)))
if err != nil {
logt.Error("xxx enqueue err:", err)
return nil, zerr.New(rescode.EnqueueErr)
}
```
- 自行定义了队列优先级,在入队的时候可以加参数进行选择
```
consts.QueueCritical: 6,
consts.QueueDefault: 3,
consts.QueueLow: 1,
```
- 处理前流程(反序列化,获取数据)
```go
var payload jobtype.xxx
err := json.Unmarshal(task.Payload(), &payload)
if err != nil {
logt.Error("xxx unmarshal payload err:", err)
return zerr.New(rescode.DataFormatConversionError)
}
```
### gorm
[官网文档](https://gorm.io/zh_CN/docs/)
**使用gorm + sqlx,但是复用sql连接**
```go
func NewServiceContext(c config.Config) *ServiceContext {
logx.Infof("Cache config: %+v", c.CacheRedis)
gormDB, err := gorm.Open(postgres.Open(c.Database.DataSource), &gorm.Config{})
if err != nil {
msg := fmt.Sprintf("连接数据库失败 %s ", err.Error())
logx.Error(msg)
panic(msg)
}
sqlDB, err := gormDB.DB()
if err != nil {
msg := fmt.Sprintf("获取底层连接失败: %s", err.Error())
logx.Error(msg)
panic(msg)
}
sqlxConn := sqlx.NewSqlConnFromDB(sqlDB)
return &ServiceContext{
Config: c,
UserAuthModel: models.NewUserAuthModel(sqlxConn, c.CacheRedis, gormDB),
}
}
```
### excelize
读写Excel的Go库
[github](https://github.com/qax-os/excelize)
#### 创建工作簿
```go
f := excelize.NewFile()
defer func() {
if err := f.Close(); err != nil {
fmt.Println(err)
}
}()
// 创建一个新的Sheet,返回的index为该工作簿的索引
index, err := f.NewSheet("SheetName")
if err != nil {
return fmt.Errorf("创建Sheet失败: %w", err)
}
```
#### 打开文件
```go
f, err := excelize.OpenFile(filePath)
if err != nil {
return fmt.Errorf("打开 Excel失败: %w", err)
}
defer func() {
if err := f.Close(); err != nil {
fmt.Println(err)
}
}()
```
#### 写入cell值
```go
f.SetCellValue("SheetName", "B2", 100)
```
#### 设置工作簿默认活动工作表
```go
f.SetActiveSheet(index)
```
#### 创建或更新excel表
```go
if err := f.SaveAs("Book1.xlsx"); err != nil {
return fmt.Errorf("保存失败: %w", err)
}
```
#### 获取指定cell值
```go
cell, err := f.GetCellValue("Sheet1", "B2")
if err != nil {
return fmt.Errorf("读取cell失败: %w", err)
}
fmt.Println(cell)
```
#### 获取所有工作簿的cell值(二维数组)
```go
rows, err := f.GetRows("Sheet1")
if err != nil {
return fmt.Errorf("读取失败: %w", err)
}
for _, row := range rows {
for _, colCell := range row {
fmt.Print(colCell, "\t")
}
fmt.Println()
}
```
@@ -0,0 +1,8 @@
# Go 常用第三方库
- [validator](validator.md) —— 参数校验器,验证参数是否符合规则
- [goverter](goverter.md) —— 类型转换器,用于类型转换代码生成
- [asynq](asynq.md) —— 异步任务队列,任务的入队与处理流程
- [gorm](gorm.md) —— ORM,gorm + sqlx 复用 sql 连接的用法
- [excelize](excelize.md) —— 读写 Excel 的 Go 库
- [bcrypt](bcrypt.md) —— 密码哈希;Go 团队维护的官方扩展库 `golang.org/x/crypto/bcrypt`,不属于标准库
@@ -0,0 +1,38 @@
# asynq
[GitHub网址](https://github.com/hibiken/asynq)
- 入队流程
```go
payload := &jobtype.xxxPayload{}
payloadByte, err := jsoniter.Marshal(payload)
if err != nil {
logt.Error("xxx jsoniter.Marshal err:", err)
return nil, zerr.New(rescode.DataFormatConversionError)
}
_, err = l.svcCtx.AsynqClient.Enqueue(asynq.NewTask(jobtype.xxx, payloadBytes, asynq.Queue(consts.QueueLow)))
if err != nil {
logt.Error("xxx enqueue err:", err)
return nil, zerr.New(rescode.EnqueueErr)
}
```
- 自行定义了队列优先级,在入队的时候可以加参数进行选择
```
consts.QueueCritical: 6,
consts.QueueDefault: 3,
consts.QueueLow: 1,
```
- 处理前流程(反序列化,获取数据)
```go
var payload jobtype.xxx
err := json.Unmarshal(task.Payload(), &payload)
if err != nil {
logt.Error("xxx unmarshal payload err:", err)
return zerr.New(rescode.DataFormatConversionError)
}
```
@@ -0,0 +1,193 @@
# 官方扩展库 `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,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>
@@ -0,0 +1,80 @@
# excelize
读写Excel的Go库
[github](https://github.com/qax-os/excelize)
## 创建工作簿
```go
f := excelize.NewFile()
defer func() {
if err := f.Close(); err != nil {
fmt.Println(err)
}
}()
// 创建一个新的Sheet,返回的index为该工作簿的索引
index, err := f.NewSheet("SheetName")
if err != nil {
return fmt.Errorf("创建Sheet失败: %w", err)
}
```
## 打开文件
```go
f, err := excelize.OpenFile(filePath)
if err != nil {
return fmt.Errorf("打开 Excel失败: %w", err)
}
defer func() {
if err := f.Close(); err != nil {
fmt.Println(err)
}
}()
```
## 写入cell值
```go
f.SetCellValue("SheetName", "B2", 100)
```
## 设置工作簿默认活动工作表
```go
f.SetActiveSheet(index)
```
## 创建或更新excel表
```go
if err := f.SaveAs("Book1.xlsx"); err != nil {
return fmt.Errorf("保存失败: %w", err)
}
```
## 获取指定cell值
```go
cell, err := f.GetCellValue("Sheet1", "B2")
if err != nil {
return fmt.Errorf("读取cell失败: %w", err)
}
fmt.Println(cell)
```
## 获取所有工作簿的cell值(二维数组)
```go
rows, err := f.GetRows("Sheet1")
if err != nil {
return fmt.Errorf("读取失败: %w", err)
}
for _, row := range rows {
for _, colCell := range row {
fmt.Print(colCell, "\t")
}
fmt.Println()
}
```
@@ -0,0 +1,29 @@
# gorm
[官网文档](https://gorm.io/zh_CN/docs/)
**使用gorm + sqlx,但是复用sql连接**
```go
func NewServiceContext(c config.Config) *ServiceContext {
logx.Infof("Cache config: %+v", c.CacheRedis)
gormDB, err := gorm.Open(postgres.Open(c.Database.DataSource), &gorm.Config{})
if err != nil {
msg := fmt.Sprintf("连接数据库失败 %s ", err.Error())
logx.Error(msg)
panic(msg)
}
sqlDB, err := gormDB.DB()
if err != nil {
msg := fmt.Sprintf("获取底层连接失败: %s", err.Error())
logx.Error(msg)
panic(msg)
}
sqlxConn := sqlx.NewSqlConnFromDB(sqlDB)
return &ServiceContext{
Config: c,
UserAuthModel: models.NewUserAuthModel(sqlxConn, c.CacheRedis, gormDB),
}
}
```
@@ -0,0 +1,23 @@
# goverter
[GitHub地址](https://github.com/jmattheis/goverter)
类型转换器,可以用于类型的转换,比如下面的代码
```go
func (l *CreatePopupLogic) CreatePopup(in *backgroundrpcmodel.ResPopup) (*backgroundrpcmodel.ResResult, error) {
_, err := l.svcCtx.PopupModel.Insert(l.ctx, u.Convert.SysPopupR2S(in.Info))
if err != nil {
return nil, rerr.NewFromSQL(err)
}
return &backgroundrpcmodel.ResResult{
Result: true,
}, nil
}
```
使用
```
goverter gen .
```
@@ -0,0 +1,15 @@
# validator
[GitHub地址](github.com/go-playground/validator)
包验证器,可以用于验证参数是否符合规则,比如下方的比较验证,还有其他可以查看网址的详细介绍
go-zero自带的有校验器,可以使用
| Tag | Description |
| ---- | --------------------- |
| eq | Equals |
| gt | Greater than |
| gte | Greater than or equal |
| lt | Less Than |
| lte | Less Than or Equal |
| ne | Not Equal |
-3
View File
@@ -22,6 +22,3 @@ if mysqlErr, ok := errors.AsType[*mysql.MySQLError](err); ok {
}
```
## `net/http/pprof`
@@ -96,199 +96,3 @@ Go 1.27 虽然开放了泛型方法,但有明确的限制:
| **判断标准** | **是否为“空 (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。