feat: 添加 Go 内存分配示例并整理笔记
拆分 Go 第三方库笔记并更新索引。 新增 Gitleaks 使用说明,清理过时笔记。
This commit is contained in:
@@ -45,3 +45,4 @@ Thumbs.db
|
|||||||
|
|
||||||
# ---- Note 目录 ----
|
# ---- Note 目录 ----
|
||||||
note/项目/
|
note/项目/
|
||||||
|
docs/plans
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
var retained [][]byte // 全局变量,用来保持对象可达
|
||||||
|
|
||||||
|
func printMemory(stage string) {
|
||||||
|
var m runtime.MemStats
|
||||||
|
runtime.ReadMemStats(&m)
|
||||||
|
|
||||||
|
fmt.Printf("%s: HeapAlloc=%d MiB, TotalAlloc=%d MiB\n",
|
||||||
|
stage,
|
||||||
|
m.HeapAlloc/1024/1024,
|
||||||
|
m.TotalAlloc/1024/1024)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
runtime.GC()
|
||||||
|
printMemory("开始")
|
||||||
|
|
||||||
|
for i := 0; i < 20; i++ {
|
||||||
|
block := make([]byte, 1024*1024) // 每次创建约 1 MiB 的底层数组
|
||||||
|
block[0] = byte(i)
|
||||||
|
retained = append(retained, block)
|
||||||
|
}
|
||||||
|
printMemory("分配后")
|
||||||
|
|
||||||
|
retained = nil // 程序不再持有这些数组
|
||||||
|
runtime.GC()
|
||||||
|
printMemory("GC 后")
|
||||||
|
}
|
||||||
@@ -25,13 +25,13 @@
|
|||||||
#### 三、远程操作
|
#### 三、远程操作
|
||||||
|
|
||||||
| 操作 | 命令 | 说明 |
|
| 操作 | 命令 | 说明 |
|
||||||
| ---------------- | --------------------------------------------------------- | ------------------- |
|
| ---------------- | ----------------------------------------------------------- | ------------------- |
|
||||||
| 克隆仓库 | `git clone <url>` | 拉取远程项目 |
|
| 克隆仓库 | `git clone <url>` | 拉取远程项目 |
|
||||||
| 拉取远程代码 | `git pull` | 获取并合并远程分支 |
|
| 拉取远程代码 | `git pull` | 获取并合并远程分支 |
|
||||||
| 推送代码 | `git push` | 推送当前分支到远程 |
|
| 推送代码 | `git push` | 推送当前分支到远程 |
|
||||||
| 推送并设置上游 | `git push -u origin <branch>` | 建立跟踪关系 |
|
| 推送并设置上游 | `git push -u origin <branch>` | 建立跟踪关系 |
|
||||||
| 查看远程地址 | `git remote -v` | 显示远程仓库地址 |
|
| 查看远程地址 | `git remote -v` | 显示远程仓库地址 |
|
||||||
| 远程查询所有标签 | git ls-remote --tags https://github.com/xxx.git \| grep X | 查看X相关的所有标签 |
|
| 远程查询所有标签 | `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,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 |
|
||||||
@@ -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` 接口) |
|
| **判断标准** | **是否为“空 (Empty)”** (如:空字符串 `""`、长度为 0 的切片/Map、`false`、`0`、指针 `nil`) | **是否为“ Go 类型的零值 (Zero Value)”** (满足 `reflect.Value.IsZero()` 或实现 `IsZero() bool` 接口) |
|
||||||
| **结构体/时间类型处理** | **有重大缺陷**,对非指针的结构体(如 `time.Time`)无效!依然会被序列化。 | **完美支持** 未初始化的 `time.Time{}` 或任何嵌套结构体零值均会被正确跳过。 |
|
| **结构体/时间类型处理** | **有重大缺陷**,对非指针的结构体(如 `time.Time`)无效!依然会被序列化。 | **完美支持** 未初始化的 `time.Time{}` 或任何嵌套结构体零值均会被正确跳过。 |
|
||||||
| **自定义类型零值** | 不支持自定义零值判断逻辑。 | 支持只要实现 `IsZero() bool` 方法即可自定义是否跳过。 |
|
| **自定义类型零值** | 不支持自定义零值判断逻辑。 | 支持只要实现 `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,41 @@
|
|||||||
|
# Gitleaks 使用笔记
|
||||||
|
|
||||||
|
Gitleaks 是扫描疑似密钥的命令行工具,不需要部署 Docker。它通过规则匹配识别令牌、云平台凭据、私钥等;命中不代表凭据仍有效,未命中也不代表绝对安全。
|
||||||
|
|
||||||
|
## Linux 安装
|
||||||
|
|
||||||
|
从 [官方 Releases](https://github.com/gitleaks/gitleaks/releases/latest) 下载对应架构的 Linux 压缩包:`uname -m` 为 `x86_64` 时选 `linux_x64`,为 `aarch64` 时选 `linux_arm64`。核对下载页提供的校验和,解压后安装:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo install -m 0755 gitleaks /usr/local/bin/gitleaks
|
||||||
|
gitleaks version
|
||||||
|
```
|
||||||
|
|
||||||
|
## 提交前自动检测
|
||||||
|
|
||||||
|
在目标 Git 仓库中检查 `.git/hooks/pre-commit` 是否已存在;若存在,将下面的检测命令并入现有脚本,**不要覆盖**。新建时内容为:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
#!/bin/sh
|
||||||
|
gitleaks git --staged --redact --verbose .
|
||||||
|
```
|
||||||
|
|
||||||
|
然后运行 `chmod +x .git/hooks/pre-commit`。正常流程是 `git add <文件>` → `git commit -m "提交说明"`:Git 自动执行 hook;发现疑似密钥时命令返回非零状态,提交被阻止。`--redact` 避免在终端输出完整密钥。若设置了 `core.hooksPath`,应在指定的 hook 目录配置,而非 `.git/hooks/`。
|
||||||
|
|
||||||
|
## 手动扫描与测试
|
||||||
|
|
||||||
|
在仓库目录下运行:
|
||||||
|
|
||||||
|
| 命令 | 检查范围 |
|
||||||
|
| --- | --- |
|
||||||
|
| `gitleaks git --staged --redact --verbose .` | 本次暂存变更的新增内容,与上面的 hook 一致 |
|
||||||
|
| `gitleaks git --redact .` | Git 提交历史中的新增内容 |
|
||||||
|
| `gitleaks dir --redact .` | 当前目录中的文件,包括尚未暂存的文件 |
|
||||||
|
|
||||||
|
手动运行第一条命令可以先确认工具正常工作;没有命中通常返回 `0`,发现疑似密钥或扫描出错返回 `1`。普通文件修改不会因为装了 Gitleaks 就被阻止。要验证**自动拦截**,只用无效的模拟令牌测试,并先确认手动扫描能命中;随后运行普通的 `git commit`,预期 hook 阻止提交。若扫描未命中,别贸然提交测试令牌;若意外提交成功,不要推送,先检查 hook 是否执行及暂存内容。
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
- `--staged` 扫描的是暂存差异中的新增内容,不等于检查每个暂存文件的全部历史内容;未暂存的修改也不在其中。
|
||||||
|
- 本地 hook 通常只对当前仓库有效,且可被 `git commit --no-verify` 绕过;重要项目还应在 CI 中扫描。
|
||||||
|
- 发现真实凭据时,先从暂存内容中移除并修复;若已推送或暴露,应尽快轮换凭据。误报可审慎使用 allowlist 或 `.gitleaksignore`,不要直接忽略未知来源的命中。
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
opencode添加mcp
|
|
||||||
|
|
||||||
```bash
|
|
||||||
opencode mcp add postgres -- npx -y @microsoft/postgres-mcp run
|
|
||||||
```
|
|
||||||
Reference in New Issue
Block a user