Compare commits
15
Commits
main
...
cde8281523
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cde8281523 | ||
|
|
5263eb3a5f | ||
|
|
f40efea159 | ||
|
|
722a5b13e0 | ||
|
|
a2dbd51f35 | ||
|
|
241a7c43da | ||
|
|
735a339deb | ||
|
|
b6063cb9f9 | ||
|
|
ef18635509 | ||
|
|
3819ae9d94 | ||
|
|
e486d47e2a | ||
|
|
e6fe90a7c0 | ||
|
|
e00245ea66 | ||
|
|
f7f5d379a9 | ||
|
|
be711334fb |
@@ -19,6 +19,12 @@ vendor/
|
||||
.pub/
|
||||
build/
|
||||
|
||||
# ---- Node / TypeScript ----
|
||||
node_modules/
|
||||
dist/
|
||||
.astro/
|
||||
*.tsbuildinfo
|
||||
|
||||
# ---- IDE / 编辑器 ----
|
||||
.idea/
|
||||
.vscode/
|
||||
@@ -28,3 +34,6 @@ build/
|
||||
# ---- 操作系统 ----
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# ---- Note 目录 ----
|
||||
note/项目/
|
||||
@@ -0,0 +1,36 @@
|
||||
# AGENTS.md
|
||||
|
||||
## 仓库结构
|
||||
|
||||
按语言分层的个人练习仓库,每种语言一个顶层目录,各自独立管理依赖。
|
||||
|
||||
| 目录 | 说明 |
|
||||
|---|---|
|
||||
| `go/` | Go module `lab`(Go 1.26.2),go.mod 在该目录内 |
|
||||
| `note/` | 个人笔记(非代码),`note/项目/` 被 gitignore |
|
||||
| `dart/` | 预留,尚未建立 |
|
||||
|
||||
## Go 约定
|
||||
|
||||
- **Module 在 `go/` 目录下,不在仓库根**——所有 go 命令需在 `go/` 内执行
|
||||
- 每个叶子目录 = 一个独立小程序(`package main` + `main.go`)
|
||||
- 多个小程序可并存,不同目录、各自独立
|
||||
- 主题目录(basics/concurrency/stdlib/patterns/algorithms/web/testing/project)可随时增删
|
||||
- 测试文件用 `*_test.go`,运行单个测试:`cd go && go test ./testing/ -run TestSliceAppend`
|
||||
|
||||
## 开发命令
|
||||
|
||||
```
|
||||
# Go——所有命令在 go/ 目录下执行
|
||||
cd go
|
||||
go run ./<主题>/<程序名>/ # 运行单个程序
|
||||
go test ./... # 运行全部测试
|
||||
go test ./testing/ -v # 运行 testing 包测试
|
||||
go mod tidy # 整理依赖
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
- 仓库无 CI、无 pre-commit hooks、无代码检查——每个程序自行保证可编译即可
|
||||
- 笔记和练习代码严格分离:程序相关说明写在程序目录的 README.md,系统性知识放 `note/`
|
||||
- `.gitignore` 已覆盖 Go 二进制、Dart/Flutter 构建产物、IDE 配置、操作系统文件
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
- `go/` — Go(共用一个 module,约定见 [go/README.md](go/README.md))
|
||||
- `dart/` — Dart
|
||||
- `ts/` — TypeScript / JavaScript(每个实验独立 npm 项目,约定见 [ts/README.md](ts/README.md))
|
||||
|
||||
## 约定
|
||||
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
type User struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
func NewUser() *User {
|
||||
u := User{}
|
||||
return &u
|
||||
}
|
||||
|
||||
func ClosureFunc() func() int {
|
||||
a := 1
|
||||
return func() int {
|
||||
return a
|
||||
}
|
||||
}
|
||||
func main() {
|
||||
fmt.Println(NewUser())
|
||||
cf:= ClosureFunc()
|
||||
fmt.Println(cf())
|
||||
}
|
||||
@@ -1,3 +1,15 @@
|
||||
module lab
|
||||
|
||||
go 1.26.2
|
||||
|
||||
require (
|
||||
github.com/richardlehane/mscfb v1.0.7 // indirect
|
||||
github.com/richardlehane/msoleps v1.0.6 // indirect
|
||||
github.com/tiendc/go-deepcopy v1.7.2 // indirect
|
||||
github.com/xuri/efp v0.0.1 // indirect
|
||||
github.com/xuri/excelize/v2 v2.11.0 // indirect
|
||||
github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9 // indirect
|
||||
golang.org/x/crypto v0.53.0 // indirect
|
||||
golang.org/x/net v0.56.0 // indirect
|
||||
golang.org/x/text v0.38.0 // indirect
|
||||
)
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
github.com/richardlehane/mscfb v1.0.7 h1:oeoiM0WE79vHwE8RpIYYvIAc8ajTH2mb6UZm55/+EB0=
|
||||
github.com/richardlehane/mscfb v1.0.7/go.mod h1:pe0+IUIc0AHh0+teNzBlJCtSyZdFOGgV4ZK9bsoV+Jo=
|
||||
github.com/richardlehane/msoleps v1.0.6 h1:9BvkpjvD+iUBalUY4esMwv6uBkfOip/Lzvd93jvR9gg=
|
||||
github.com/richardlehane/msoleps v1.0.6/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
|
||||
github.com/tiendc/go-deepcopy v1.7.2 h1:Ut2yYR7W9tWjTQitganoIue4UGxZwCcJy3orjrrIj44=
|
||||
github.com/tiendc/go-deepcopy v1.7.2/go.mod h1:4bKjNC2r7boYOkD2IOuZpYjmlDdzjbpTRyCx+goBCJQ=
|
||||
github.com/xuri/efp v0.0.1 h1:fws5Rv3myXyYni8uwj2qKjVaRP30PdjeYe2Y6FDsCL8=
|
||||
github.com/xuri/efp v0.0.1/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI=
|
||||
github.com/xuri/excelize/v2 v2.11.0 h1:HxaEFl6sRN2+8J5a8HaKq+0M4FsjBGMnWWtjOCPSG88=
|
||||
github.com/xuri/excelize/v2 v2.11.0/go.mod h1:jxFLbzaIwGQ5ufFNvYfUOHqXhfPaNmP14KWfmNz2Uak=
|
||||
github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9 h1:+C0TIdyyYmzadGaL/HBLbf3WdLgC29pgyhTjAT/0nuE=
|
||||
github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ=
|
||||
golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto=
|
||||
golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio=
|
||||
golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o=
|
||||
golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec=
|
||||
golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE=
|
||||
golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4=
|
||||
@@ -0,0 +1,34 @@
|
||||
package error
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type MatchError struct {
|
||||
code int
|
||||
err error
|
||||
}
|
||||
|
||||
func (e *MatchError) Error() string {
|
||||
return fmt.Sprintf("code: %d, error: %v", e.code, e.err)
|
||||
}
|
||||
func GetMatchError() error {
|
||||
return &MatchError{code: 1, err: errors.New("123")}
|
||||
}
|
||||
func GetError() error {
|
||||
return errors.New("123")
|
||||
}
|
||||
func TestError(t *testing.T) {
|
||||
if err, ok := errors.AsType[*MatchError](GetMatchError()); ok {
|
||||
t.Log(err)
|
||||
} else {
|
||||
t.Errorf("expected MatchError, got %T", GetMatchError())
|
||||
}
|
||||
if err, ok := errors.AsType[*MatchError](GetError()); ok {
|
||||
t.Log(err)
|
||||
} else {
|
||||
t.Errorf("expected MatchError, got %T", GetError())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package net
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNet(t *testing.T) {
|
||||
s := net.JoinHostPort("127.0.0.1", "8080")
|
||||
|
||||
t.Log(s)
|
||||
t.Log(s)
|
||||
addrs, err := net.LookupAddr("198.18.0.18")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(addrs)
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package testing
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestNormal(t *testing.T) {
|
||||
numbers := map[int][]int{
|
||||
1: {8, 2, 4, 4},
|
||||
2: {0, 5, 3},
|
||||
3: {1},
|
||||
}
|
||||
for _, numbers := range numbers {
|
||||
middle := GetMiddNumber(numbers)
|
||||
t.Log(middle)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func GetMiddNumber(numbers []int) float64 {
|
||||
sort.Ints(numbers)
|
||||
length := len(numbers)
|
||||
isOddNUmber := length%2 == 1
|
||||
if !isOddNUmber {
|
||||
return float64(numbers[length/2-1]+numbers[length/2]) / 2.0
|
||||
}
|
||||
return float64(numbers[length/2])
|
||||
}
|
||||
|
||||
func TestChannel(t *testing.T) {
|
||||
var ball = make(chan string)
|
||||
kickBall := func(playerName string) {
|
||||
for {
|
||||
t.Log(<-ball, "传球", "\n")
|
||||
time.Sleep(time.Second)
|
||||
ball <- playerName
|
||||
}
|
||||
}
|
||||
go kickBall("张三")
|
||||
go kickBall("李四")
|
||||
go kickBall("王二麻子")
|
||||
go kickBall("刘大")
|
||||
ball <- "裁判" // 开球
|
||||
var c chan bool // 一个零值nil通道
|
||||
<-c // 永久阻塞在此
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package regularmatch
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestChineseRegExp(t *testing.T) {
|
||||
reg, err := regexp.Compile("[\u4e00-\u9fa5]")
|
||||
if err != nil {
|
||||
t.Errorf("Failed to compile regex: %v", err)
|
||||
}
|
||||
matchStrSlice := reg.FindAllString("hello 世界", -1)
|
||||
|
||||
if len(matchStrSlice) != 2 {
|
||||
t.Errorf("Expected 2 matches, got %d", len(matchStrSlice))
|
||||
}
|
||||
if matchStrSlice[0] != "世" || matchStrSlice[1] != "界" {
|
||||
t.Errorf("Expected [世, 界], got %v", matchStrSlice)
|
||||
}
|
||||
}
|
||||
|
||||
func TestChinaMobile(t *testing.T) {
|
||||
reg, err := regexp.Compile(`^1(3\d|4[5-9]|5[0-35-9]|6[2567]|7[0-8]|8\d|9[0-35-9])\d{8}$`)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to compile regex: %v", err)
|
||||
}
|
||||
// matchStrSlice := reg.FindAllString("123-12345678 1234-12345678", -1)
|
||||
// if len(matchStrSlice) != 2 {
|
||||
// t.Errorf("Expected 2 matches, got %d", len(matchStrSlice))
|
||||
// }
|
||||
matchStrSlice := reg.FindAllString("17623862704", -1)
|
||||
t.Log(matchStrSlice[0])
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.5 MiB |
@@ -0,0 +1,303 @@
|
||||
# HTTP
|
||||
|
||||
## 上传文件
|
||||
|
||||
> **https://github.com/zeromicro/zero-examples/tree/main/http/upload**
|
||||
|
||||
### 前端
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
|
||||
<title>Document</title>
|
||||
</head>
|
||||
<body>
|
||||
<form
|
||||
enctype="multipart/form-data"
|
||||
action="http://localhost:8888/upload"
|
||||
method="post"
|
||||
>
|
||||
<input type="file" name="myFile" />
|
||||
<input type="submit" value="upload" />
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
> **参数说明**
|
||||
|
||||
- `enctype="multipart/form-data"` 用于支持文件的二进制传输
|
||||
|
||||
- `<input type="file">` 让用户挑选本地文件
|
||||
|
||||
- `action` 和 `method`,将数据以 `POST` 方式提交到后端地址
|
||||
|
||||
|
||||
|
||||
### 后端
|
||||
|
||||
```yaml
|
||||
Name: upload-api
|
||||
Host: 0.0.0.0
|
||||
Port: 8888
|
||||
MaxBytes: 1073741824
|
||||
Timeout: 30000
|
||||
```
|
||||
|
||||
> **yaml文件需要设置上传的最大字节,并且如果为大文件,需要考虑超时时间设置是否合理**
|
||||
|
||||
```go
|
||||
func (l *UploadLogic) Upload(r *http.Request) (resp *types.Response, err error) {
|
||||
err = r.ParseMultipartForm(10 << 27)
|
||||
if err != nil {
|
||||
fmt.Println("错误发生:", err)
|
||||
return nil, err
|
||||
}
|
||||
file, handler, err := r.FormFile("myFile")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
fmt.Printf("Uploaded File: %+v\n", handler.Filename)
|
||||
fmt.Printf("File Size: %+v\n", handler.Size)
|
||||
fmt.Printf("MIME Header: %+v\n", handler.Header)
|
||||
|
||||
tempFile, err := os.Create(path.Join(".", handler.Filename))
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return nil, err
|
||||
}
|
||||
defer tempFile.Close()
|
||||
io.Copy(tempFile, file)
|
||||
|
||||
return &types.Response{
|
||||
OK: 0,
|
||||
}, nil
|
||||
}
|
||||
```
|
||||
|
||||
> **API逻辑说明**
|
||||
|
||||
- 需要在handler里面将`*http.Request`传递到逻辑层
|
||||
|
||||
- `ParseMultipartForm`用于解析 `multipart/form-data` 类型的请求体
|
||||
|
||||
主要作用是:
|
||||
|
||||
解析表单字段(包括文件上传字段)
|
||||
|
||||
将请求体拆分成多个部分(parts),并填充到 `r.MultipartForm` 中
|
||||
|
||||
内存与磁盘分配:最多在内存中保留 `maxFileSize` 字节,超出的部分会写入临时文件
|
||||
|
||||
- `FormFile(key string)` 从已解析的 `r.MultipartForm` 中提取名为 `"myFile"` 的文件部分
|
||||
|
||||
- `tempFile, err := os.Create(path.Join(".", handler.Filename))`将文件上传到当前目录
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# DTM
|
||||
|
||||
## 基础概念
|
||||
|
||||
- **AP(Application)**:应用程序,发起全局事务请求。
|
||||
- **TM(Transaction Manager)**:事务协调者,负责协调分布式事务的执行流程。
|
||||
- **RM(Resource Manager)**:资源管理者,例如订单服务、库存服务等,执行具体业务与补偿逻辑
|
||||
- **事务分支**:我们把每个服务管理的全局事务组成部分,称为事务分支,例如前面的转账,分为转出和转入两个事务分支
|
||||
- **分支操作**:每个事务分支,在SAGA、XA、TCC等事务模式下,会有多个操作,例如转出事务分支,包括正向操作TransOut和TransOutCompensate
|
||||
- **本地事务**:转出事务分支中的正向操作,通常会开启一个事务,对余额进行扣减,我们将数据操作的这个事务,称为本地事务
|
||||
- **GID**:全局事务ID,用于标记全局事务,必须唯一。该ID可以采用UUID生成,也可以使用业务上的ID,例如订单ID等
|
||||
|
||||
| 模式 | 场景 | 典型使用 |
|
||||
| ---------- | ---------------------------------------- | --------------------------------- |
|
||||
| 二阶段消息 | 需要确保消息通知或缓存更新与本地事务同步 | 发短信/推送、消息投递、缓存一致性 |
|
||||
| Saga | 流程复杂、长链路、不需强一致性 | 电商下单→扣库存→支付等微服务流程 |
|
||||
| TCC | 强一致、高隔离资源场景 | 金融系统、库存冻结、大额支付 |
|
||||
|
||||
## 二阶段消息
|
||||
|
||||
是dtm首创的事务模式,用于替换本地事务表和事务消息这两种现有的方案。它能够保证本地事务的提交和全局事务提交是“原子的”,适合解决不需要回滚的分布式事务场景,比如发消息、更新缓存等
|
||||
|
||||
- **准备阶段**(Prepare) ,应用先发一个准备信号给 DTM Server,表示“我现在要做本地事务了”。DTM Server 接收到后先记录这个请求,但不触发后续通知。
|
||||
- 执行本地事务, 应用开始执行本地事务(如写数据库、扣款),并在同一个数据库里,记录这次全局事务的 gid(全局事务 ID)。这保证了后续可以根据 gid 判断本地事务是否已成功。
|
||||
- **提交阶段**(Submit), 本地事务成功后,应用再向 DTM 提交提交请求。DTM 执行这个操作后,才真正触发下游服务或消息投递。开始调用其他微服务或消息队列,实现完整的全局动作。
|
||||
|
||||
**优点:**
|
||||
|
||||
- 保证“本地事务 + 消息”业务要么同时成功,否则都不执行。(强触发保障)
|
||||
- 对开发者透明,业务侵入小。
|
||||
|
||||
**缺点:**
|
||||
|
||||
- 需要开发一套 Prepare/Submit 调用,系统需定期监控、回查状态。
|
||||
- 不适用于需要复杂补偿的场景。
|
||||
|
||||
## SAGA
|
||||
|
||||
核心思想是将长事务拆分为多个短事务,由Saga事务协调器协调,如果每个短事务都成功提交完成,那么全局事务就正常完成,如果某个步骤失败,则根据相反顺序一次调用补偿操作。例如我们要进行一个类似于银行跨行转账的业务,将A中的30元转给B,根据Saga事务的原理,我们将整个全局事务,切分为以下服务:
|
||||
|
||||
- **转出**(TransOut)服务,这里转出将会进行操作A-30
|
||||
- **转出补偿**(TransOutCompensate)服务,回滚上面的转出操作,即A+30
|
||||
- **转入**(TransIn)服务,转入将会进行B+30
|
||||
- **转入补偿**(TransInCompensate)服务,回滚上面的转入操作,即B-30
|
||||
|
||||
**优点:**
|
||||
|
||||
- 流程自然、开发简单,只要为操作实现补偿逻辑即可。
|
||||
- 对资源无锁设计,支持高并发、长流程,性能佳。
|
||||
|
||||
**缺点:**
|
||||
|
||||
- 是最终一致性,存在临时中间状态(如库存已扣但未支付)。
|
||||
- 无事务隔离,可能出现脏写或并发冲突。
|
||||
- 补偿逻辑可能复杂易错(幂等、防重复)。
|
||||
|
||||
## TCC
|
||||
|
||||
TCC 是一种“预留-确认-取消”的事务模式,将事务分为三个阶段,高精度控制点 的场景,比如:支付系统、资金冻结、库存预扣等
|
||||
|
||||
- **Try** 阶段:尝试执行,完成所有业务检查(一致性), 预留必须业务资源(准隔离性)
|
||||
- **Confirm** 阶段:如果所有分支的Try都成功了,则走到Confirm阶段。Confirm真正执行业务,不作任何业务检查,只使用 Try 阶段预留的业务资源
|
||||
- **Cancel** 阶段:如果所有分支的Try有一个失败了,则走到Cancel阶段。Cancel释放 Try 阶段预留的业务资源。
|
||||
|
||||
**优点:**
|
||||
|
||||
- 强一致性:通过 Try 预留资源,Confirm/Cancel 明确执行或回滚,避免中间状态。
|
||||
- 高并发友好,资源锁粒度小;性能优于传统 XA/2PC。
|
||||
|
||||
**缺点:**
|
||||
|
||||
- 业务侵入性高,每个参与方需实现三个操作(Try/Confirm/Cancel)。
|
||||
- 实现复杂:需处理悬挂、空回滚、幂等、补偿重试等问题。
|
||||
|
||||
## SAGA实践
|
||||
|
||||
- 准备RM和DTM数据表
|
||||
|
||||
[GitHub地址](https://github.com/dtm-labs/dtm/tree/main/sqls)
|
||||
|
||||
- DTM配置
|
||||
|
||||
[GitHub地址](https://github.com/dtm-labs/dtm/blob/main/conf.sample.yml)
|
||||
|
||||
- docker部署参考配置
|
||||
|
||||
```yaml
|
||||
LogLevel: 'info'
|
||||
Server:
|
||||
HttpPort: 36789 #HTTP port
|
||||
GrpcPort: 36790 #Grpc port
|
||||
Store:
|
||||
Driver: "mysql" # 数据库类型
|
||||
Host: "mysql" # 使用容器名
|
||||
Port: 3306
|
||||
User: "root"
|
||||
Password: "root"
|
||||
Db: "dtm"
|
||||
MicroService:
|
||||
Driver: "dtm-driver-gozero" #使用go-zero的注册服务发现驱动
|
||||
Target: "etcd://etcd:2379/dtmservice" # 使用容器名,当前dtm的server直接注册到微服务所在的etcd集群中使用
|
||||
EndPoint: "dtm:36790" # 使用容器名,集群中的微服务可以直接通过etcd获得此地址跟dtm交互
|
||||
```
|
||||
|
||||
- go-zero的yaml参考配置
|
||||
|
||||
```yaml
|
||||
#AP使用etcd的配置
|
||||
Etcd:
|
||||
Hosts:
|
||||
- etcd:2379
|
||||
Key: order.api
|
||||
#RM使用etcd的配置
|
||||
OrderRpc:
|
||||
Etcd:
|
||||
Hosts:
|
||||
- etcd:2379
|
||||
Key: order.rpc
|
||||
#RM使用etcd的配置
|
||||
StockRpc:
|
||||
Etcd:
|
||||
Hosts:
|
||||
- etcd:2379
|
||||
Key: stock.rpc
|
||||
```
|
||||
|
||||
SAGA 事务编排使用步骤
|
||||
|
||||
```go
|
||||
// dtm服务在etcd中的调用地址
|
||||
var dtmServer = "etcd://localhost:2379/dtmservice"
|
||||
|
||||
// 从配置中通过BuildTarget获得对应RM的etcd调用地址
|
||||
orderTarget, err := l.svcCtx.Config.OrderRpcConf.BuildTarget()
|
||||
stockTarget, err := l.svcCtx.Config.StockRpcConf.BuildTarget()
|
||||
|
||||
// 构造对应的请求结构体,用于后面的本地事务的执行或者回滚补偿
|
||||
createOrderReq := &order.CreateReq{UserId: req.UserId, GoodsId: req.GoodsId, Num: req.Num}
|
||||
deductReq := &stock.DecuctReq{GoodsId: req.GoodsId, Num: req.Num}
|
||||
|
||||
// 使用dtmgrpc注册一个全局事务ID,用于标记全局事务
|
||||
gid := dtmgrpc.MustGenGid(dtmServer)
|
||||
|
||||
// 开启全局事务,并提交
|
||||
// dtm需要从dtm服务器调用该方法,所以不走强类型,而是走动态的url: busiServer+"/trans.TransSvc/TransOut"
|
||||
// "/trans.TransSvc/TransOut"对应的是grpc_pb.go文件中Invoke方法所使用的路径
|
||||
saga := dtmgrpc.NewSagaGrpc(dtmServer, gid).
|
||||
Add(
|
||||
orderTarget+"/pb.order/create",
|
||||
orderTarget+"/pb.order/createRollback",
|
||||
createOrderReq,
|
||||
).
|
||||
Add(
|
||||
stockTarget+"/pb.stock/deduct",
|
||||
stockTarget+"/pb.stock/deductRollback",
|
||||
deductReq,
|
||||
)
|
||||
err = saga.Submit()
|
||||
```
|
||||
|
||||
服务实现(包含子事务屏障)
|
||||
|
||||
```go
|
||||
// 从 gRPC 请求上下文中提取分布式事务信息,构建一个用于管理子事务屏障的对象
|
||||
barrier, err := dtmgrpc.BarrierFromGrpc(l.ctx)
|
||||
// 获取一个sqlx的数据库连接
|
||||
db, err := sqlx.NewMysql(l.svcCtx.Config.DB.DataSource).RawDB()
|
||||
|
||||
if err != nil {
|
||||
// 一般数据库不会错误不需要dtm回滚,就让他一直重试
|
||||
// 返回为status.Error(codes.Internal, err.Error()),会让dtm一直重试,不会回滚,直到
|
||||
// 返回为status.Error(codes.Aborted, dtmcli.ResultFailure),会让dtm立刻调用回滚方法
|
||||
return nil, status.Error(codes.Internal, err.Error())
|
||||
}
|
||||
|
||||
// 开启本地事务(这里没写,正常应该在本地事务中再开启子事务屏障进行业务处理
|
||||
// 将业务逻辑包裹在子事务屏障的保护下执行
|
||||
if err := barrier.CallWithDB(db, func(tx *sql.Tx) error {
|
||||
order := new(model.Order)
|
||||
order.GoodsId = in.GoodsId
|
||||
order.Num = in.Num
|
||||
order.UserId = in.UserId
|
||||
|
||||
_, err = l.svcCtx.OrderModel.Insert(tx, order)
|
||||
if err != nil {
|
||||
return fmt.Errorf("创建订单失败 err : %v , order:%+v \n", err, order)
|
||||
}
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
// 同上,让dtm一直重试,不回滚
|
||||
return nil, status.Error(codes.Internal, err.Error())
|
||||
}
|
||||
|
||||
// 补偿操作跟上面差不多,就是业务逻辑修改
|
||||
```
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
|
||||
|
||||
## oh-my-openagent
|
||||
|
||||
### Agent角色
|
||||
|
||||
| **Agent 名称** | **属性** | **一句话特点** |
|
||||
| ----------------- | -------- | ------------------------------------------------------------ |
|
||||
| Sisyphus | 台前主控 | 项目总管、日常聊天与轻量开发入口,居中调度,维持记忆的多面手 |
|
||||
| Prometheus | 台前主控 | 复杂需求的规划者、Gap(差距)分析师,只出谋划策(写 Plan),不动手写代码 |
|
||||
| Atlas | 台前主控 | 流水线执行者、项目经理,盯着 TODO 督战的施工队包工头 |
|
||||
| Hephaestus | 台前主控 | 火神、地狱级 Bug 终结者,单兵作战,不计成本死磕疑难杂症 |
|
||||
| Oracle | 幕后工具 | 代码审查员、安全顾问,挑刺、CR、架构安全性把关 |
|
||||
| Momus | 幕后工具 | Debug 专家,专门吃报错日志并给出修复建议 |
|
||||
| Metis | 幕后工具 | 质检员、对齐审计,检查代码跟计划对不对齐,防止跑偏 |
|
||||
| Sisyphus-junior | 幕后工具 | 基层打工人、代码搬砖牛马,真正动手改文件的“牛马码农” |
|
||||
| Explore | 幕后工具 | 代码库检索员,在代码库里疯狂乱窜、扫图、找文件 |
|
||||
| Librarian | 幕后工具 | 文档与知识检索员,专门翻文档、查第三方库用法 |
|
||||
| Multimodal-looker | 幕后工具 | 视觉质检员,负责看截图、核对 UI 视觉问题 |
|
||||
|
||||
### 内置分类预设
|
||||
|
||||
| 分类名称(Category) | 预设心智与应用场景 |
|
||||
| ------------------ | ------------------------------------------------------------ |
|
||||
| visual-engineering | **像素级前端 UI 专家**。专门处理前端组件、页面样式美化、CSS 动画调整,默认偏向调度带有视觉能力的模型。 |
|
||||
| deep | **目标导向的硬核攻坚战**。针对棘手难啃的底层问题,严格遵循“单次调用:一个目标 + 一个交付物”原则,死磕到底。 |
|
||||
| quick | **低成本牛马杂活线**。处理单文件小改动、错别字修复、简单注释补充。默认绑定极便宜的 Mini 或 Flash 模型来帮你疯狂省钱。 |
|
||||
| ultrabrain | 高浓度脑力推演 |
|
||||
| writing | **散文与技术文档写手**。专门用来编写官方 Markdown 文档、技术方案白皮书或代码内说明。 |
|
||||
|
||||
|
||||
|
||||
## oh-my-opencode-slim
|
||||
|
||||
### 预设
|
||||
|
||||
`oh-my-opencode-slim.json`中的顶级字段`presets`为预设对象数组,默认设置了两套:一套是`openai`,一套是`opencode-go`。可以自行增删预设或者修改`agent`角色的模型。
|
||||
|
||||
```
|
||||
# 在opencode会话中输入
|
||||
/preset <name>
|
||||
```
|
||||
|
||||
### Agent角色
|
||||
|
||||
| **Agent 名称** | **角色** | **模型选择** |
|
||||
| -------------- | ----------------------- | ------------------------------------------------------------ |
|
||||
| Orchestrator | 首席委派者和战略协调员 | 选择您的默认、最强的全能型编程模型。Orchestrator 既是核心编程智能体,又是委派者,因此它需要强大的实现能力、出色的判断力和可靠的指令遵循度。 |
|
||||
| Explorer | 代码库侦察 | 选择快速、低成本的模型。Explorer 处理宽泛的侦察工作,因此速度 and 效率通常比使用最强推理模型更重要。 |
|
||||
| Oracle | 战略顾问和终极调试者 | 选择您最强的高推理模型,用于架构设计、疑难调试、方案权衡以及代码审查。 |
|
||||
| Council | 多LLM共识与提炼 | 使用一个强大的综合提炼模型作为 Council 智能体本身,并选择多样化的模型作为议员。Council 的价值在于对比不同的模型视角,而不仅仅是在所有地方都选择同一个最强的模型。 |
|
||||
| Librarian | 外部知识检索 | 选择快速、低成本的模型。Librarian 处理调研和文档查询,因此速度和效率通常比使用最强推理模型更重要。 |
|
||||
| Designer | UI/UX实现和极致视觉呈现 | 选择在 UI/UX 判断、前端实现和视觉打磨方面表现强劲的模型。 |
|
||||
| Fixer | 快速实现专家 | 选择一个快速、可靠的编程模型来执行常规且范围明确的开发工作。Fixer 通常从 Orchestrator 接收具体的计划或受限的指令,非常适合高效执行诸如编写测试、更新测试和直接的代码更改等任务。 |
|
||||
| Observer | 多模态处理 | 如果Orchestrator模型不是多模态模型,可以启用 Observer 来处理图像、屏幕截图、PDF 以及其他视觉文件。 |
|
||||
|
||||
### Skill分配
|
||||
|
||||
## 技能分配
|
||||
|
||||
控制每个代理人可以使用的技能`~/.config/opencode/oh-my-opencode-slim.json`(或`.jsonc`):
|
||||
|
||||
| 句法 | 意义 |
|
||||
| ------------------- | ------------------------------- |
|
||||
| `["*"]` | 所有已安装的技能 |
|
||||
| `["*", "!codemap"]` | 除以下技能外的所有技能`codemap` |
|
||||
| `["simplify"]` | 仅有的`simplify` |
|
||||
| `[]` | 没有技能 |
|
||||
| `["!*"]` | 拒绝所有技能 |
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
### 常用命令
|
||||
|
||||
#### 一、分支管理
|
||||
|
||||
| 操作 | 命令 | 说明 |
|
||||
| -------------------- | -------------------------------------- | ---------------------------- |
|
||||
| 查看本地分支 | `git branch` | 显示所有本地分支 |
|
||||
| 查看远程分支 | `git branch -r` | 显示远程分支 |
|
||||
| 创建新分支 | `git checkout -b feature/xxx` | 基于当前分支创建并切换新分支 |
|
||||
| 基于远程创建本地分支 | `git checkout -b dev origin/dev` | 跟踪远程分支创建 |
|
||||
| 删除本地分支 | `git branch -d feature/xxx` | 删除(已合并)本地分支 |
|
||||
| 删除远程分支 | `git push origin --delete feature/xxx` | 删除远程分支 |
|
||||
| 重命名分支 | `git branch -m old new` | 修改分支名 |
|
||||
|
||||
#### 二、提交相关
|
||||
|
||||
| 操作 | 命令 | 说明 |
|
||||
| ------------------ | ------------------------------- | ------------------------------------------ |
|
||||
| 添加到暂存区 | `git add .` 或 `git add <file>` | 添加改动文件 |
|
||||
| 提交代码 | `git commit` | 使用文本编辑器编写提交信息后提交 |
|
||||
| 提交代码 | `git commit -m "描述信息"` | 提交到本地仓库,可使用多个`-m`提交多行信息 |
|
||||
| 修改上一次提交信息 | `git commit --amend` | 用于补充或改描述 |
|
||||
| 查看提交历史 | `git log --oneline` | 简洁模式显示历史 |
|
||||
|
||||
#### 三、远程操作
|
||||
|
||||
| 操作 | 命令 | 说明 |
|
||||
| ---------------- | --------------------------------------------------------- | ------------------- |
|
||||
| 克隆仓库 | `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 merge feature/xxx` | 把目标分支合并进当前分支 |
|
||||
| 取消合并 | `git merge --abort` | 终止合并,直接回退到合并前的状态 |
|
||||
| 变基(rebase) | `git rebase dev` | 让你的提交在最新 `dev` 上 |
|
||||
| 解决冲突后继续 | `git add . && git rebase --continue` | 继续变基或合并流程 |
|
||||
|
||||
#### 五、查看状态和差异
|
||||
|
||||
| 操作 | 命令 | 说明 |
|
||||
| ---------------- | ------------------- | ---------------------------- |
|
||||
| 查看状态 | `git status` | 当前分支、暂存区、工作区状态 |
|
||||
| 查看修改内容 | `git diff` | 查看未暂存的更改 |
|
||||
| 查看暂存内容差异 | `git diff --cached` | 查看已 add 的更改 |
|
||||
|
||||
#### 六、撤销操作
|
||||
|
||||
| 操作 | 命令 | 说明 |
|
||||
| ----------------------- | ------------------------- | -------------------- |
|
||||
| 撤销 add | `git reset <file>` | 把文件移出暂存区 |
|
||||
| 撤销 commit(保留修改) | `git reset --soft HEAD~1` | 撤销最近一次提交 |
|
||||
| 丢弃修改 | `git checkout -- <file>` | 丢弃对文件的本地更改 |
|
||||
| 丢弃所有工作区修改 | `git reset --hard` | 恢复到最后一次提交 |
|
||||
|
||||
#### 七、其他常用
|
||||
|
||||
| 操作 | 命令 | 说明 |
|
||||
| ---------------------- | ------------------------------------------------------------ | ---- |
|
||||
| 查看当前分支 | `git branch --show-current` 或 `git rev-parse --abbrev-ref HEAD` | |
|
||||
| 查看某次提交内容 | `git show <commit-id>` | |
|
||||
| 切换到上一个分支 | `git checkout -` | |
|
||||
| 清理无效引用 | `git remote prune origin` | |
|
||||
| 从远程仓库获取所有标签 | `git fetch origin --tags` | |
|
||||
@@ -0,0 +1,117 @@
|
||||
|
||||
|
||||
[来源网站](https://www.conventionalcommits.org/zh-hant/v1.0.0/)
|
||||
|
||||
## 提交说明
|
||||
|
||||
### 基础结构
|
||||
|
||||
```
|
||||
<type>[optional scope]: <description>
|
||||
|
||||
[optional body]
|
||||
|
||||
[optional footer(s)]
|
||||
|
||||
# 译文
|
||||
|
||||
<类型>[可选 范围]: <描述>
|
||||
|
||||
[可选 正文]
|
||||
|
||||
[可选 脚注]
|
||||
```
|
||||
|
||||
### 类型(type)
|
||||
|
||||
- feat: 表示在代码库中新增了一个功能;
|
||||
- fix: 表示在代码库中修复了一个 bug;
|
||||
|
||||
- build: 用于修改项目构建系统或外部依赖,例如升级依赖版本等;
|
||||
- chore: 用于其他不修改`src`或`test`文件的杂项任务,例如更新`.gitignore`、修改编辑器配置等;
|
||||
- ci: 用于修改持续集成/持续部署的配置与脚本,例如`GitHub Actions`、`Jenkins`、`Travis` 等工作流;
|
||||
- docs: 用于修改文档,例如修改`README`文件、API文档等;
|
||||
- style: 用于修改代码格式(不影响代码逻辑),例如调整缩进、空格、分号、引号风格等;
|
||||
- refactor: 用于重构代码,例如修改代码结构、变量名、函数名等但不修改功能逻辑;
|
||||
- improvement: 用于对现有实现进行改进,例如优化算法、增强健壮性等(不涉及新功能或 bug 修复);
|
||||
- revert: 用于回退之前的提交,描述应使用被回退提交的标题,并在脚注中用 `Refs:` 引用被回退的 commit SHA;
|
||||
- perf: 用于优化性能,例如提升代码的性能、减少内存占用等;
|
||||
- wip: 用于保存尚未完成的工作进度(Work In Progress);
|
||||
- test: 用于修改测试用例,例如添加、删除、修改代码的测试用例等;
|
||||
|
||||
> 破坏性变更**必须**在提交信息中标记出来,有两种方式:
|
||||
>
|
||||
> 1.在 `<类型>[可选 范围]` 后、冒号前添加 `!`,例如:`feat!: ...` 或 `refactor(api)!: ...`
|
||||
>
|
||||
> 2.*在脚注中添加* `BREAKING CHANGE: <描述>`
|
||||
>
|
||||
> 这两种方式可以同时使用,也可以只用其一。
|
||||
|
||||
### 范围(Scope)
|
||||
|
||||
范围用于标识本次修改影响的模块或组件,例如:
|
||||
|
||||
- feat(auth): ... (auth 模块)
|
||||
|
||||
- fix(parser): ... (parser 模块)
|
||||
|
||||
- build(deps): ... (依赖管理)
|
||||
|
||||
范围名称建议使用小写英文,多个单词用 `-` 连接,例如 `user-profile`。
|
||||
|
||||
### 描述(description)
|
||||
|
||||
描述用于简要概括本次变更的内容,要求:
|
||||
|
||||
- 使用祈使语气、现在时态(例如用 "add" 而非 "added" 或 "adds");
|
||||
|
||||
- 小写开头,末尾不加句号;
|
||||
|
||||
- 长度建议控制在50个字符以内(中文约 25 个字);
|
||||
|
||||
### 正文(Body)
|
||||
|
||||
正文是可选的,用于对本次变更进行更详细的说明,包括:
|
||||
|
||||
- 变更的动机(为什么这样做)
|
||||
|
||||
- 与之前行为的对比
|
||||
|
||||
- 实现方案的简要说明
|
||||
|
||||
正文与描述之间**必须**空一行,正文内部段落之间也可以空行分隔。
|
||||
|
||||
### 脚注(Footer)
|
||||
|
||||
脚注用于记录元信息,常见用法:
|
||||
|
||||
- `BREAKING CHANGE: <描述>` — 标记破坏性变更;
|
||||
|
||||
- `Closes #123` 或 `Fixes #456` — 关联 Issue/PR;
|
||||
|
||||
- `Refs: abc123` — 引用相关提交;
|
||||
|
||||
- `Reviewed-by: <name>` — 标记审查者;
|
||||
|
||||
- `Co-authored-by: <name> <email>` — 标记共同作者;
|
||||
|
||||
## 示例
|
||||
|
||||
### 基础功能提交
|
||||
feat(auth): add JWT token refresh mechanism
|
||||
|
||||
### Bug 修复(含关联 issue)
|
||||
fix(parser): handle empty string input edge case
|
||||
|
||||
Closes #123
|
||||
|
||||
### 破坏性变更(使用 ! 前缀)
|
||||
feat(api)!: change user endpoint response format
|
||||
|
||||
BREAKING CHANGE: the `GET /api/users` endpoint now returns
|
||||
a paginated response instead of a flat array.
|
||||
|
||||
### 回退提交
|
||||
revert: remove experimental cache layer
|
||||
|
||||
Refs: a1b2c3d
|
||||
@@ -0,0 +1,24 @@
|
||||
### SSH
|
||||
|
||||
#### 快速连接服务器
|
||||
|
||||
在ssh的配置文件`~/.ssh/config`中可以设置常用的服务配置,这样就可以直接使用`ssh myserver`快速连接服务器了
|
||||
|
||||
```
|
||||
Host myserver
|
||||
HostName your-server.com
|
||||
Port 2222
|
||||
User ubuntu
|
||||
IdentityFile ~/.ssh/mykey
|
||||
```
|
||||
|
||||
#### 本地转发
|
||||
|
||||
```bash
|
||||
# 使用示例
|
||||
ssh -L 25432:localhost:5432 ubuntu@your-server.com
|
||||
```
|
||||
|
||||
**作用**:以本地端口25432请求远程服务器,在远程服务器看来,这是以远程`localhost:5432`发起的请求
|
||||
|
||||
**使用场景**:当远程服务器某服务限制了只接收远程服务器的127.0.0.1连接,这时候又需要远程连接这个服务,这样的话就能和访问本地服务一样访问了
|
||||
@@ -0,0 +1,84 @@
|
||||
|
||||
|
||||
## 程序、进程与线程
|
||||
|
||||
### 程序
|
||||
|
||||
#### 定义
|
||||
|
||||
程序是存储在硬盘的一组指令集和,它是对计算任务的完整描述,是一个被动实体。类似于一本菜谱,记录了完整的烹饪步骤。
|
||||
|
||||
#### 核心特征
|
||||
|
||||
| 属性 | 说明 |
|
||||
| :------------- | :-------------------------------------------------------- |
|
||||
| **静态性** | 程序是一个文件(如 .exe、.go),不占用 CPU 或内存执行资源 |
|
||||
| **持久性** | 可长期存在于存储介质上 |
|
||||
| **无生命周期** | 没有创建、运行、消亡的状态变迁 |
|
||||
|
||||
### 进程
|
||||
|
||||
#### 定义
|
||||
|
||||
进程是程序的一次动态执行过程,是操作系统进行资源分配的基本单位。它就像按照菜谱进行烹饪的过程,需要占用资源,有始有终。它由以下三部分构成。
|
||||
|
||||
| 组成部分 | 作用 |
|
||||
| :-------------------- | :----------------------------------------------------- |
|
||||
| **程序段** | 描述进程要完成的功能,是控制进程执行的指令集 |
|
||||
| **数据段** | 程序执行时所需的数据和工作区(全局变量、堆等) |
|
||||
| **进程控制块(PCB)** | 包含进程的描述信息和控制信息,是**进程存在的唯一标志** |
|
||||
|
||||
#### 核心特征
|
||||
|
||||
- **动态性**:进程有生命周期(创建 → 就绪 → 运行 → 阻塞 → 终止)
|
||||
- **独立性**:每个进程拥有独立的地址空间(虚拟内存空间、代码、数据、系统资源的集合)
|
||||
- **并发性**:多个进程可在宏观上同时运行
|
||||
- **资源拥有者**:进程拥有独立的文件描述符、环境变量、用户ID等资源
|
||||
|
||||
### 线程
|
||||
|
||||
#### 定义
|
||||
|
||||
线程是CPU进行运算调度的最小单位,是进程中的一条执行路径,一个进程至少有一个线程(也就是主线程)。它就像多位厨师共用厨房的资源(水、灶台、食材等),但是每人也都有自己的独有的刀具、围裙等,他们需要协调使用厨房做菜。
|
||||
|
||||
#### 核心特征
|
||||
|
||||
线程虽然共享进程的资源,但每个线程拥有自己的**私有数据**:
|
||||
|
||||
| **独有内容** | **说明** |
|
||||
| :------------------ | :--------------------------------- |
|
||||
| 线程 ID | 唯一标识 |
|
||||
| 寄存器状态 | 包括程序计数器(PC)和栈指针 |
|
||||
| 栈 | 独立运行栈,用于函数调用和局部变量 |
|
||||
| 信号掩码 | 线程级别的信号处理设置 |
|
||||
| 优先级 | 调度优先级 |
|
||||
| 线程专用存储(TLS) | 线程私有的全局变量 |
|
||||
|
||||
|
||||
|
||||
## 用户态与内核态
|
||||
|
||||
### 内核态
|
||||
|
||||
运行操作系统内核代码,拥有对硬件的完全控制权,负责管理内存、进程、文件系统、网络等底层资源。
|
||||
|
||||
### 用户态
|
||||
|
||||
运行用户应用程序,无法直接访问硬件或内核数据结构,必须通过 系统调用(System Call)请求内核代为执行特权操作。
|
||||
|
||||
### 切换过程
|
||||
|
||||
为了保护系统安全与稳定,操作系统会对CPU的执行权限进行分级,不同的CPU架构有不同的等级,但是通常都遵循应用在最低特权级(用户态)运行,内核在次高特权级(内核态)运行,通过CPU硬件强制隔离特权操作。当在X86架构的CPU运行时,一个应用程序需要执行特权操作(比如读写文件),过程大致是这样的:
|
||||
|
||||
1. **触发**
|
||||
程序调用 `read()` 等库函数,最终执行 CPU 的 `syscall` 指令。这条指令告诉 CPU:“我要切换到内核态干正事”。
|
||||
2. **CPU 硬件自动切换**
|
||||
CPU 在硬件层面做三件关键事:
|
||||
- 把特权级从 **Ring 3(用户态)** 切换为 **Ring 0(内核态)**。
|
||||
- 将栈指针从用户栈切换到**内核栈**(从 MSR 中读取预设的内核栈指针)。
|
||||
- 把返回地址(下一条指令)和标志寄存器保存到特定寄存器(如 `rcx`、`r11`),然后跳转到内核预先设置好的**入口函数**。
|
||||
3. **内核软处理**
|
||||
入口函数将刚才没来得及保存的全部寄存器压入内核栈(形成 `pt_regs` 结构),然后根据**系统调用号**(放在 `rax` 通用寄存器中)从系统调用表里找到对应的内核函数(如 `ksys_read`)并执行。
|
||||
4. **内核执行 & 返回**
|
||||
内核函数完成实际工作(检查权限、读取数据等),把结果放在 `rax` 通用寄存器中。返回前,内核检查是否有其他任务需要处理(如信号、调度),然后通过 `sysret` 指令恢复之前保存的寄存器、用户栈和返回地址,特权级自动切回 **Ring 3**,程序从当初的下一条指令继续运行。
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
## Nginx 反代未透传客户端 IP 导致服务端日志全量变为 127.0.0.1
|
||||
|
||||
### 现象
|
||||
|
||||
后端解析IP后全量变成 `127.0.0.1`。
|
||||
|
||||
### 原因
|
||||
|
||||
后端解析IP的顺序是
|
||||
```
|
||||
X-Forwarded-For → X-Real-IP → RemoteAddr
|
||||
```
|
||||
|
||||
**Nginx**转发至后端服务的 location 块缺少`proxy_set_header`指令:
|
||||
|
||||
```nginx
|
||||
location /olive3 {
|
||||
|
||||
proxy_pass http://127.0.0.1:55430;
|
||||
|
||||
proxy_connect_timeout 300s;
|
||||
|
||||
proxy_send_timeout 300s;
|
||||
|
||||
proxy_read_timeout 300s;
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
### 修复
|
||||
|
||||
在缺失的 location 块中补充 header 透传:
|
||||
|
||||
```nginx
|
||||
location /api {
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_pass http://127.0.0.1:55430;
|
||||
# ... 其他配置不变
|
||||
}
|
||||
```
|
||||
|
||||
- `$proxy_add_x_forwarded_for`:将客户端真实 IP 追加到已有 `X-Forwarded-For` 尾部,而非覆盖
|
||||
- `$remote_addr`:Nginx 直接看到的对端地址,作为 `X-Real-IP` 传递
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
## 快速复制文件路径
|
||||
|
||||
### Shift + 右键菜单(通用)
|
||||
|
||||
1. 在文件资源管理器中,**按住 `Shift` 键不放**
|
||||
2. 右键点击目标文件
|
||||
3. 选择 **「复制为路径(A)」**
|
||||
|
||||
### 功能区按钮
|
||||
|
||||
1. 选中文件
|
||||
2. 点击资源管理器顶部的 **「主页」** 选项卡
|
||||
3. 点击 **「复制路径」** 按钮(剪贴板图标)
|
||||
|
||||
### 拖拽到终端
|
||||
|
||||
- 直接把文件**拖入** PowerShell 或 CMD 窗口,会自动粘贴完整路径。
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
配置文件位置
|
||||
|
||||
```
|
||||
C:\Users\admin\AppData\Roaming\Code\User\keybindings.json
|
||||
```
|
||||
|
||||
该文件保存的是用户级快捷键列表,每一个快捷键配置都是包含`key`、`command`、`when`(可选)字段
|
||||
|
||||
| 字段 | 作用 |
|
||||
| :------------ | :------------------------------- |
|
||||
| **`key`** | 你按下的键盘组合 |
|
||||
| **`command`** | 要执行的命令 ID |
|
||||
| **`when`** | 规定这条快捷键在什么“语境”下生效 |
|
||||
|
||||
完整示例:
|
||||
|
||||
```json
|
||||
// markdown 代码块
|
||||
{
|
||||
"key": "ctrl+shift+`",
|
||||
"command": "markdown.extension.editing.toggleCodeBlock",
|
||||
"when": "editorTextFocus && editorLangId == 'markdown'"
|
||||
},
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 按键的书写规则
|
||||
|
||||
### 基本格式
|
||||
|
||||
- **全小写**:修饰键和字母键都要小写,写 `ctrl` 而不是 `Ctrl`,写 `a` 而不是 `A`。
|
||||
- **用 `+` 连接**:按键之间用加号连接,不带空格。例如 `ctrl+shift+a`。
|
||||
- **修饰键顺序任意**:`ctrl+alt+a` 和 `alt+ctrl+a` 效果相同,但习惯按 `ctrl -> shift -> alt -> 字母` 的顺序写
|
||||
|
||||
### 支持的修饰键
|
||||
|
||||
| 修饰键 | 写法 | 说明 |
|
||||
| :------------ | :------ | :---------------------------------------------- |
|
||||
| Ctrl | `ctrl` | Windows / Linux 上常用 `ctrl`,macOS 对应 `cmd` |
|
||||
| Shift | `shift` | |
|
||||
| Alt | `alt` | macOS 上是 `opt`(或者也支持 `alt`) |
|
||||
| Cmd (Mac) | `cmd` | 在 Windows 上无效,建议用 `ctrl` 兼顾跨平台 |
|
||||
| Win (Windows) | `win` | 很少用 |
|
||||
| Meta (macOS) | `meta` | 通常等同 `cmd` |
|
||||
|
||||
### 特殊按键名称
|
||||
|
||||
| 按键 | 写法 |
|
||||
| :----------- | :------------------------------------- |
|
||||
| 方向键 | `up` `down` `left` `right` |
|
||||
| 功能键 | `f1` ~ `f19` |
|
||||
| 回车 | `enter` |
|
||||
| 空格 | `space` |
|
||||
| 退格 | `backspace` |
|
||||
| Tab | `tab` |
|
||||
| 反引号 | ``` (就是 backtick) |
|
||||
| 逗号、句号 | `,` `.` 等直接写符号本身 |
|
||||
| 数字键 | `0`~`9` |
|
||||
| 计算器小键盘 | `numpad0` ~ `numpad9`,`numpad_add` 等 |
|
||||
|
||||
### 按键组合
|
||||
|
||||
VS Code 支持**二段按键**(一次按键序列),写法是:
|
||||
|
||||
```json
|
||||
"key": "ctrl+k ctrl+b"
|
||||
```
|
||||
|
||||
意思是先按 `Ctrl+K`,松开后再按 `Ctrl+B`。
|
||||
|
||||
## 命令ID
|
||||
|
||||
`command` 是一个**全局唯一的字符串**,由扩展或 VS Code 自己注册。不能自己编造,必须使用确切的 ID。
|
||||
|
||||
通过`ctrl+k ctrl+b`按键组合或者在设置中点击“键盘快捷方式”即可查看当前能设置的所有的命令。虽然显示的是命令,但是实际上是命令的名字,右键可以获取命令的ID。也可以不通过修改配置文件,在图形化界面设置快捷键。
|
||||
|
||||
|
||||
|
||||
## 上下文条件表达式
|
||||
|
||||
`when` 是一个**布尔表达式**,只有计算结果为 `true` 时,快捷键才会触发,可以组合变量使用逻辑运算符。
|
||||
|
||||
### 常用上下文变量
|
||||
|
||||
| 变量 | 说明 | 常见值 |
|
||||
| :------------------------------ | :------------------------------------------------- | :------------------------------------------ |
|
||||
| `editorTextFocus` | 焦点是否在编辑器的**文本区域**(光标可以输入文字) | `true` / `false` |
|
||||
| `editorLangId` | 当前编辑器的语言 ID(不是文件后缀) | `"markdown"`, `"javascript"`, `"python"` 等 |
|
||||
| `editorHasSelection` | 编辑器里是否有文本被选中 | `true` / `false` |
|
||||
| `isLinux`, `isWindows`, `isMac` | 当前操作系统 | `true` / `false` |
|
||||
| `resourceExtname` | 当前文件的后缀名(带`.`) | `".md"`, `".js"` 等 |
|
||||
| `activeEditor` | 是否有激活的编辑器 | `true` / `false` |
|
||||
| `terminalFocus` | 焦点是否在集成终端 | `true` / `false` |
|
||||
| `listFocus` | 焦点在列表/树视图吗 | `true` / `false` |
|
||||
|
||||
### 逻辑运算符
|
||||
|
||||
| 运算符 | 含义 |
|
||||
| :--------- | :----------------------------------------------------------- |
|
||||
| `&&` | 与 |
|
||||
| ` | |
|
||||
| `!` | 非 |
|
||||
| `==`, `!=` | 等于 / 不等于(字符串) |
|
||||
| `=~` | 正则匹配(例如:`editorLangId =~ /^markdown$ |
|
||||
| `in` | 测试左侧值是否在给定的右值数组里,如 `editorLangId in ['markdown', 'rmd']` |
|
||||
|
||||
### 例子分析
|
||||
|
||||
```json
|
||||
"when": "editorTextFocus && editorLangId == 'markdown'"
|
||||
```
|
||||
|
||||
表达式含义:确保你正在编辑文本,而不是在侧边栏、终端或其他地方**并且**当前文件被 VS Code 识别为 Markdown 语言
|
||||
|
||||
## 常见问题排查
|
||||
|
||||
| 现象 | 可能原因 |
|
||||
| :------------------------------- | :------------------------------------------------------ |
|
||||
| 按键完全没反应 | `command` 拼写错误;`when` 条件一直为 false;扩展未激活 |
|
||||
| 在其他语言文件里也生效了 | 缺少 `editorLangId == 'xxx'` 限制,考虑加上 |
|
||||
| 一次性按了没反应,但按键序列可以 | 可能你定义成了 chord,但习惯是单键 |
|
||||
| 提示 “command 'xxx' not found” | 命令 ID 不存在,检查扩展是否已安装并启用 |
|
||||
+1661
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,225 @@
|
||||
### **小六壬时课 · 知识库**
|
||||
|
||||
#### **一、 简介:什么是小六壬?**
|
||||
|
||||
小六壬,相传为三国时期诸葛亮、唐代李淳风所创,是一种简便、快速的预测方法,因其起课方便、便于携带(仅需掐指一算),故有“袖珍式预测学”或“马前课”之称。
|
||||
|
||||
其核心思想是,根据事发或问事时的“时间”或“心念”所触发的即时信息,来推演事物的状态和发展趋势。它主要用于对**单一、具体、短期**的事情进行快速占断,属于“一事一占”的动念占法。
|
||||
|
||||
**核心特点**:
|
||||
|
||||
- **简便快捷**:无需复杂工具,仅凭掌诀即可推算。
|
||||
- **随机性强**:动念即可成课,捕捉瞬间天机。
|
||||
- **应用广泛**:适用于寻物、问事、决疑、出行等日常生活的方方面面。
|
||||
|
||||
#### **二、 核心概念:六神宫位与掌诀**
|
||||
|
||||
小六壬共有六个基本“角色”,称为“六神”(或六宫),它们分布在食指、中指和无名指的指节上,形成一个固定的“掌上地盘”,即“掌诀”。
|
||||
|
||||
**掌诀位置图示**: 您可以伸出左手,按照下图在手指上定位六神:
|
||||
|
||||
- **大安**:食指根部
|
||||
- **留连**:食指尖部
|
||||
- **速喜**:中指尖部
|
||||
- **赤口**:无名指尖部
|
||||
- **小吉**:无名指根部
|
||||
- **空亡**:中指根部
|
||||
|
||||
(其顺序为顺时针循环)
|
||||
|
||||
**六神基础信息表**:
|
||||
|
||||
| 宫位/六神 | 五行 | 吉凶 | 核心意象与关键词 |
|
||||
| :------------------ | :--- | :--- | :--------------------------------- |
|
||||
| **大安 (Dà'ān)** | 木 | 吉 | 安泰、平稳、静止、官方、身体、龙 |
|
||||
| **留连 (Liúlián)** | 水 | 凶 | 延迟、纠缠、牵绊、缓慢、暧昧、暗昧 |
|
||||
| **速喜 (Sùxǐ)** | 火 | 吉 | 快速、喜讯、文书、宴饮、光明、朱雀 |
|
||||
| **赤口 (Chìkǒu)** | 金 | 凶 | 口舌、官非、争斗、惊恐、凶险、白虎 |
|
||||
| **小吉 (Xiǎojí)** | 木 | 吉 | 合作、财利、和合、顺畅、六合 |
|
||||
| **空亡 (Kōngwáng)** | 土 | 凶 | 落空、无果、忧虑、阴私、迷茫、玄武 |
|
||||
|
||||
#### **三、 主要起课方法**
|
||||
|
||||
起课,就是根据参数确定最终落在哪一个宫位上。
|
||||
|
||||
**1. 传统时间起课法(月、日、时)** 这是最正统的方法,以农历时间为准。
|
||||
|
||||
- 第一步:月上起日
|
||||
- 从**大安**宫开始,顺时针将月份数代入。例如,一月为大安,二月为留连,三月为速喜……以此类推。
|
||||
- 第二步:日上起时
|
||||
- 从上一步月份的落宫开始,顺时针将日期数(初一、初二……)代入。
|
||||
- 第三步:时上定宫
|
||||
- 从上一步日期的落宫开始,顺时针将时辰数(子时为1,丑时为2……)代入,最终落下的宫位即为结果。
|
||||
|
||||
**【示例】**:占测农历八月初十下午4点(申时)。
|
||||
|
||||
1. **定月**:从大安宫数8个月,落点在**赤口**。(1大安→2留连→3速喜→4赤口→5小吉→6空亡→7大安→8赤口)
|
||||
|
||||
2. **定日**:从赤口宫数10日,落点在**小吉**。(1小吉→2空亡→...→6赤口→...→10小吉)
|
||||
|
||||
3. 定时
|
||||
|
||||
:申时是第9个地支。从上一步的小吉宫数9个时辰,最终落点在
|
||||
|
||||
速喜
|
||||
|
||||
。
|
||||
|
||||
- **结果**:速喜。
|
||||
|
||||
**2. 报数起课法** 当不知晓具体时辰或为求心念感应时使用。
|
||||
|
||||
- 让问事者随意报出**三个数字**(1位数到3位数均可)。
|
||||
- 将每个数字分别**除以6,取其余数**(若余数为0,则按6计算)。
|
||||
- **第一步**:从“大安”宫起,按第一个余数顺时针数,定下基点。
|
||||
- **第二步**:从基点宫位起,按第二个余数顺时针数,定下过程点。
|
||||
- **第三步**:从过程点起,按第三个余数顺时针数,定下最终结果。
|
||||
|
||||
**【示例】**:报数为 7, 11, 32。
|
||||
|
||||
1. **第一数**:`7 ÷ 6 = 1 ... 余 1`。从大安数1,落在**大安**。
|
||||
|
||||
2. **第二数**:`11 ÷ 6 = 1 ... 余 5`。从大安数5,落在**小吉**。
|
||||
|
||||
3. 第三数
|
||||
|
||||
:
|
||||
|
||||
```
|
||||
32 ÷ 6 = 5 ... 余 2
|
||||
```
|
||||
|
||||
。从小吉数2,落在
|
||||
|
||||
空亡
|
||||
|
||||
。
|
||||
|
||||
- **结果**:空亡。
|
||||
|
||||
#### **四、 六神象义详解与分类占断**
|
||||
|
||||
这是解卦的核心,需要结合所问之事灵活判断。
|
||||
|
||||
**1. 大安**
|
||||
|
||||
- **核心**:身不动,心安宁。静待为吉,妄动有失。
|
||||
- **问事**:事可成,但需时日,不可急躁。过程平顺。
|
||||
- **寻物/寻人**:人或物未走远,在家中或原地,或在东方、东北方,多在静态、稳定的环境中(如办公室、卧室)。
|
||||
- **感情**:关系稳定,感情安泰,但可能缺乏激情。
|
||||
- **财运**:财运稳定,宜守成,不宜投机。
|
||||
- **健康**:身体安康,即使有病也易于恢复。
|
||||
|
||||
**2. 留连**
|
||||
|
||||
- **核心**:事未了,心纠结。拖延、纠缠、暧昧不清。
|
||||
- **问事**:事情进展缓慢,有阻碍,难以立刻解决,容易节外生枝。
|
||||
- **寻物/寻人**:难寻,或被某事/某人牵绊住。物品可能在潮湿、脏乱或纠缠之地。
|
||||
- **感情**:关系纠缠不清,或处于冷战、延迟状态。有藕断丝连之象。
|
||||
- **财运**:财运不畅,资金周转困难,或有债务纠纷。
|
||||
- **健康**:病情迁延,难以速愈,多为慢性病或旧病复发。
|
||||
|
||||
**3. 速喜**
|
||||
|
||||
- **核心**:火光之象,喜信速至。宜速战速决,不宜拖延。
|
||||
- **问事**:事能速成,有喜讯或好结果。尤其利于文书、考试、信息类事宜。
|
||||
- **寻物/寻人**:能快速找到。人或物在南方,或在热闹、明亮之处。
|
||||
- **感情**:进展神速,热情洋溢,或有喜事临门(如订婚、结婚)。
|
||||
- **财运**:偏财运旺,有意外之财,或短期投资获利。
|
||||
- **健康**:多为急症、热症(如发烧、炎症),但来得快去得也快。
|
||||
|
||||
**4. 赤口**
|
||||
|
||||
- **核心**:白虎开口,是非官讼。主凶险、争斗、惊扰。
|
||||
- **问事**:事多纷争,易有口舌是非,甚至官司诉讼。过程不顺,需防小人。
|
||||
- **寻物/寻人**:难寻。多有争吵或处于危险境地。物品可能已损坏。
|
||||
- **感情**:关系紧张,争吵频繁,易有分离之象。
|
||||
- **财运**:破财之象,谨防因纠纷、官非而损财。
|
||||
- **健康**:需防意外伤害、手术、或急性恶疾。有血光之灾的警示。
|
||||
|
||||
**5. 小吉**
|
||||
|
||||
- **核心**:六合之象,诸事和合。代表合作、财利、转机。
|
||||
- **问事**:事情顺利,有贵人相助,适宜合作、商议。
|
||||
- **寻物/寻人**:易寻。人或物在附近,或与友人、同伴在一起。
|
||||
- **感情**:关系和谐,琴瑟和鸣,是发展的良好时机。
|
||||
- **财运**:正财、合伙之财运佳,交易顺利。
|
||||
- **健康**:病情好转,遇到良医,康复有望。
|
||||
|
||||
**6. 空亡**
|
||||
|
||||
- **核心**:万事成空,虚无之象。信息不实,劳而无功。
|
||||
- **问事**:事情没有头绪,心中没底,最终可能不了了之或毫无结果。有虚假、欺骗的成分。
|
||||
- **寻物/寻人**:人已远去,物已丢失,找回希望渺茫。
|
||||
- **感情**:关系空虚,前景不明,或对方心意不诚。
|
||||
- **财运**:财运空空,投资无望,谨防被骗。
|
||||
- **健康**:病情诊断不明,或查无此症,也可能指精神、心理层面的问题。
|
||||
|
||||
#### **五、 断辞内容**
|
||||
|
||||
1、大安:身不动时,属木,为青龙,凡谋事主一、五、七。
|
||||
|
||||
断辞曰:
|
||||
|
||||
大安事事昌,求谋在东方,失物去不远,
|
||||
|
||||
宅舍保安康,行人身未动,病者主无妨,
|
||||
|
||||
将军回田野,仔细与推详。
|
||||
|
||||
2、留连:卒未归时,属水,为玄武,凡谋事主二、八、十。
|
||||
|
||||
断辞曰:
|
||||
|
||||
留连事难成,求谋曰未明,官事只宜缓,
|
||||
|
||||
去者未回程,失物南方见,急讨方称心,
|
||||
|
||||
更须防口舌,人口且平平。
|
||||
|
||||
3、速喜:人便至时,属火,为朱雀,凡谋事主三、六、九。
|
||||
|
||||
断辞曰:
|
||||
|
||||
速喜喜来临,求财向南行,失物申午未,
|
||||
|
||||
逢人路上寻,官事有福德,病者无祸侵,
|
||||
|
||||
田家六畜吉,行人有信音。
|
||||
|
||||
4、赤口:官事凶时,属金,为白虎,凡谋事主四、七、十。
|
||||
|
||||
断辞曰:
|
||||
|
||||
赤口主口舌,官非切要防,失物急去寻,
|
||||
|
||||
行人有惊慌,鸡犬多作怪,病者出西方,
|
||||
|
||||
更须防咒诅,恐怕染蕴疾。
|
||||
|
||||
5、小吉:人来喜时,属木,为六合,凡谋事主一、五、七。
|
||||
|
||||
断辞曰:
|
||||
|
||||
小吉最吉昌,路上好商良,阳人来报喜,
|
||||
|
||||
失物在坤方,行人立便至,交关真是强,
|
||||
|
||||
凡事皆和合,病者事无仿。
|
||||
|
||||
6、空亡:音信稀时,属土,为勾陈,凡谋事主一、五、七。
|
||||
|
||||
断辞曰:
|
||||
|
||||
空亡事不长,阴人小乘张,求财无有利,
|
||||
|
||||
行人有灾殃,失物寻不见,官事主刑伤,
|
||||
|
||||
病人逢暗鬼,乞解保安康。
|
||||
|
||||
#### **六、 使用原则与心法**
|
||||
|
||||
- **心诚意专**:占测时心态必须虔诚、专注,意念集中于所问之事。
|
||||
- **一事一占**:同一件事在短期内不应反复占问,以第一次的结果为准。所谓“再三渎,渎则不告”。
|
||||
- **动念即占**:当某个问题或念头强烈地出现在脑海中时,是最佳的占测时机。
|
||||
- **趋吉避凶**:小六壬提供的是一种预警和参考。得到吉卦,应顺势而为;得到凶卦,则应反思、谨慎、规避,而非坐以待毙。它是一种积极的“行为指导”,而非消极的“宿命论”。
|
||||
@@ -0,0 +1,21 @@
|
||||
## CONCAT函数
|
||||
|
||||
`CONCAT` 是 SQL 中用于**字符串拼接**的函数,它可以将两个或多个字符串合并成一个新的字符串。
|
||||
|
||||
------
|
||||
|
||||
### 基本语法
|
||||
|
||||
```sql
|
||||
CONCAT(string1, string2, ..., stringN)
|
||||
```
|
||||
|
||||
### 不同数据库的 CONCAT 行为
|
||||
|
||||
| 数据库 | 行为 |
|
||||
| :--------------------- | :----------------------------------------------- |
|
||||
| **MySQL** | 支持多参数;**任一参数为 NULL,整个结果为 NULL** |
|
||||
| **SQL Server** (2012+) | 支持多参数;**NULL 会被自动转为空字符串** `''` |
|
||||
| **Oracle** | 只支持 **2 个参数**;NULL 视为空字符串 |
|
||||
| **PostgreSQL** | 支持多参数;NULL 视为空字符串 |
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
|
||||
|
||||
## B-Tree
|
||||
|
||||
**适用场景**
|
||||
|
||||
| 场景 | 示例 |
|
||||
| :------- | :-------------------------- |
|
||||
| 等值查询 | WHERE user_id = 100 |
|
||||
| 范围查询 | WHERE age BETWEEN 20 AND 30 |
|
||||
| 排序优化 | ORDER BY created_at DESC |
|
||||
| 前缀匹配 | WHERE name LIKE '张%' |
|
||||
|
||||
**不适用场景**
|
||||
|
||||
- LIKE '%关键词%'(后缀/中间模糊匹配)
|
||||
|
||||
- 全文搜索
|
||||
|
||||
- 数组包含查询
|
||||
|
||||
|
||||
|
||||
## GIN
|
||||
|
||||
**结构特点**
|
||||
|
||||
- 倒排索引(Inverted Index) 机制:从"内容"映射到"行"
|
||||
|
||||
- 索引的是复合值内部的独立元素
|
||||
|
||||
- 索引构建较慢,但查询性能极佳
|
||||
|
||||
**适用场景**
|
||||
|
||||
> `@>`表示包含、`<@`表示被包含、`&&`表示重叠、`@@`表示全文搜索匹配、`?`表示JSONB 键存在
|
||||
|
||||
| 场景 | 示例 |
|
||||
| :----------- | :----------------------------------------------------- |
|
||||
| 全文搜索 | WHERE to_tsvector(content) @@ to_tsquery('postgresql') |
|
||||
| 数组包含 | WHERE tags @> ARRAY['java', 'sql'] |
|
||||
| JSONB 查询 | WHERE data @> '{"status": "active"}' |
|
||||
| 多值元素搜索 | WHERE arr && ARRAY[1, 2] |
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
## 权重抽奖
|
||||
|
||||
### 轮盘赌
|
||||
|
||||
适合奖品数量较少,一次性抽奖的场景
|
||||
|
||||
核心顺序遍历,逐个减权重:是用一个随机数 `r`,依次减去每个奖品的权重,当 `r` 变成负数的那一刻,说明它“落入了”当前奖品的权重区间。时间复杂度 **O(n)**,空间复杂度**O(1)**
|
||||
|
||||
```go
|
||||
func Draw(rewards []*Reward) *Reward {
|
||||
totalWeight := 0
|
||||
for _, r := range rewards {
|
||||
totalWeight += r.Weight
|
||||
}
|
||||
r := rand.Intn(totalWeight) // [0, totalWeight)
|
||||
for _, reward := range rewards {
|
||||
r -= reward.Weight
|
||||
if r < 0 {
|
||||
return reward
|
||||
}
|
||||
}
|
||||
return rewards[len(rewards)-1] // 兜底,理论上不会走到
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
### 前缀和 + 二分查找
|
||||
|
||||
适合奖品数量较多,需要频繁抽奖的通用场景
|
||||
|
||||
核心是构建前缀和数组,二分查找:把权重想象成一条线段,每个奖品占据一段,生成 `[0, 100)` 的随机数,落在哪个区间就中哪个奖品。二分查找快速定位区间,时间复杂度 **O(log n)**,空间复杂度**O(n)**
|
||||
|
||||
```
|
||||
权重: 50 25 15 7 2 1
|
||||
├─────────┼────────┼──────┼────┼──┼──┤
|
||||
0 50 75 90 97 99 100
|
||||
← 谢谢参与 →←5元券→←10元券→←月卡→←年卡→←大奖→
|
||||
```
|
||||
|
||||
```go
|
||||
type Reward struct {
|
||||
Name string
|
||||
Weight int
|
||||
}
|
||||
|
||||
func Draw(rewards []*Reward) *Reward {
|
||||
prefixSumArray := make([]int, len(rewards)) // 前缀和数组
|
||||
totalWeight := 0 // 总权重
|
||||
for i, p := range rewards {
|
||||
totalWeight += p.Weight
|
||||
prefixSumArray[i] = totalWeight
|
||||
fmt.Println(totalWeight)
|
||||
}
|
||||
r := rand.Intn(totalWeight) // r ∈ [0, totalWeight-1]
|
||||
idx := sort.SearchInts(prefixSumArray, r+1) // 查找目标 ∈ [1, totalWeight]
|
||||
return rewards[idx]
|
||||
}
|
||||
```
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
## 轮询算法
|
||||
|
||||
### 平滑加权轮询
|
||||
|
||||
#### 原理
|
||||
|
||||
每台服务器维护一个 `当前权重`(currentWeight),每次选择 `currentWeight` 最大的服务器,选中后将其 `currentWeight` 减去总权重,然后**所有服务器都加上自己的固定权重**。如此循环,高权重节点被选中的频率更高,但不会连续出现。
|
||||
|
||||
#### 实际应用
|
||||
|
||||
- **Nginx** 的 `upstream` 默认负载均衡算法
|
||||
- **LVS**(Linux Virtual Server)
|
||||
- 几乎所有需要「按比例分配且平滑」的场景
|
||||
|
||||
#### 轮询示例
|
||||
|
||||
```go
|
||||
type server struct {
|
||||
name string
|
||||
weight int
|
||||
curWeight int
|
||||
}
|
||||
|
||||
func NextServer(servers []*server) *server {
|
||||
var totalWeight int
|
||||
// 1.先给所有服务器加上各自的固定权重
|
||||
for i := range servers {
|
||||
servers[i].curWeight += servers[i].weight
|
||||
totalWeight += servers[i].weight
|
||||
}
|
||||
// 2.再找出 curWeight 最大的服务器
|
||||
maxIdx := 0
|
||||
for i := 1; i < len(servers); i++ {
|
||||
if servers[i].curWeight > servers[maxIdx].curWeight {
|
||||
maxIdx = i
|
||||
}
|
||||
}
|
||||
// 3.选中的服务器减去总权重
|
||||
servers[maxIdx].curWeight -= totalWeight
|
||||
return servers[maxIdx]
|
||||
}
|
||||
```
|
||||
|
||||
| 轮次 | 操作前 (currentWeight) | 选中 | 操作后 |
|
||||
| ---- | ---------------------- | ----- | --------------- |
|
||||
| 初始 | A=0, B=0, C=0 | — | — |
|
||||
| 1 | A=5, B=1, C=1 | **A** | A=−2, B=1, C=1 |
|
||||
| 2 | A=3, B=2, C=2 | **A** | A=−4, B=2, C=2 |
|
||||
| 3 | A=1, B=3, C=3 | **B** | A=1, B=−4, C=3 |
|
||||
| 4 | A=6, B=−3, C=4 | **A** | A=−1, B=−3, C=4 |
|
||||
| 5 | A=4, B=−2, C=5 | **C** | A=4, B=−2, C=−2 |
|
||||
| 6 | A=9, B=−1, C=−1 | **A** | A=2, B=−1, C=−1 |
|
||||
| 7 | A=7, B=0, C=0 | **A** | A=0, B=0, C=0 |
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
## API
|
||||
|
||||
### goctl代码生成
|
||||
|
||||
可以在doc/genCode.md查看
|
||||
|
||||
```powershell
|
||||
# 生成多个types类型的话需要关闭这个实验性功能
|
||||
goctl env -w GOCTL_EXPERIMENTAL=off
|
||||
|
||||
# 代码生成,会自动处理引入的子api文件
|
||||
goctl api go -api background.api -dir . --home D:/zhangyuxin/template/1.8.3
|
||||
goctl api go -api login.api -dir . --home ../../../deploy/goctl
|
||||
goctl api go -api main.api -dir . --home ../../deploy/goctl
|
||||
```
|
||||
|
||||
### 参数校验
|
||||
|
||||
| 接收规则 | 说明 | 示例 |
|
||||
| -------- | ------------------------------------------ | ------------------------------- |
|
||||
| optional | 当前字段是可选参数,允许为零值(zero value) | `json:"foo,optional"` |
|
||||
| options | 当前参数仅可接收的枚举值 | `json:"gender,options=foo|bar"` |
|
||||
| default | 当前参数默认值 | `json:"gender,default=male"` |
|
||||
| range | 当前参数数值有效范围,支持开/闭区间 | `json:"age,range=[0:120]"` |
|
||||
|
||||
#### **`optional`** **与** **`omitempty`** **的区别**
|
||||
|
||||
两者都写在 json/form tag 的 options 里,但作用域完全不同,不可互相替代
|
||||
|
||||
##### `optional` —— goctl 专用,控制 Swagger 与请求校验
|
||||
|
||||
- **Swagger 生成**:标了 `optional` 的字段不进 `required` 数组,Apifox/Swagger UI 显示为可选;未标的字段进 `required` 数组,显示为必填
|
||||
- **请求校验**:goctl 生成的 logic 不会对标了 `optional` 的字段做必填校验
|
||||
- **判定逻辑**:goctl 遍历 tag options,找到 `optional` 关键字即判定为可选
|
||||
|
||||
##### `omitempty` —— Go 标准库指令,控制 JSON 序列化
|
||||
|
||||
- **JSON 序列化**:字段为零值(0、""、nil、false)时,响应 JSON 中**不输出**该字段
|
||||
- **Swagger 生成**:**goctl 完全不识别**,字段仍按未标 `optional` 处理,即判定为 required
|
||||
- **请求校验**:无影响
|
||||
|
||||
## RPC
|
||||
|
||||
### goctl代码生成
|
||||
|
||||
```powershell
|
||||
# 主proto文件代码生成
|
||||
goctl rpc protoc user.proto --go_out=. --go-grpc_out=. --zrpc_out=. --proto_path=. -m --home ../../../deploy/goctl
|
||||
|
||||
# 主proto文件中引入的proto代码生成
|
||||
protoc.exe -I ./ --go_out=. --go-grpc_out=. ./user_model.proto
|
||||
|
||||
```
|
||||
|
||||
当`proto`文件里面定义了多个 service的时候,需要额外注意,还要再main函数中手动注册一下
|
||||
|
||||
> 每个 proto 文件应该聚焦于一个核心概念/实体。一个文件里定义太多 service 通常是设计需要重新考虑的信号
|
||||
|
||||
注册示例
|
||||
|
||||
```go
|
||||
import (
|
||||
ebookServer "olive/services/product/rpc/internal/server/ebookservice"
|
||||
)
|
||||
|
||||
var configFile = flag.String("f", "etc/product.yaml", "the config file")
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
var c config.Config
|
||||
conf.MustLoad(*configFile, &c)
|
||||
ctx := svc.NewServiceContext(c)
|
||||
|
||||
s := zrpc.MustNewServer(c.RpcServerConf, func(grpcServer *grpc.Server) {
|
||||
// 新服务
|
||||
productrpcmodel.RegisterEbookServiceServer(grpcServer, ebookServer.NewEbookServiceServer(ctx))
|
||||
})
|
||||
defer s.Stop()
|
||||
|
||||
fmt.Printf("Starting product RPC server at %s...\n", c.ListenOn)
|
||||
s.Start()
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
### proto代码生成错误:File not found错误
|
||||
|
||||
- 错误描述
|
||||
|
||||
执行 protoc 报 google/protobuf/timestamp.proto: File not found. 的错提示File not found
|
||||
|
||||
- 解决方式
|
||||
|
||||
重新下载[protoc](https://github.com/protocolbuffers/protobuf/releases),解压后,将整个include目录复制到protoc执行文件同一级目录下
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## model
|
||||
|
||||
可以在doc/genCode.md查看
|
||||
|
||||
**Model 层**:只做 **“业务循环”** + **“SQL 查询”** + **“结果封装”**,不关心字符串到时间的转换和 Protobuf 类型。
|
||||
|
||||
### goctl代码生成
|
||||
|
||||
```powershell
|
||||
# mysql根据远程数据库表生成
|
||||
goctl model mysql datasource --url="olive:xxvkikaDDx01&&99@tcp(127.0.0.1:3306)/ebming" --table="user_invitation_record" --dir="." -c --home ../../../../deploy/goctl/1.8.3
|
||||
|
||||
# mysql根据ddl生成
|
||||
goctl model mysql ddl --src h5.sql --dir . -c --home D:/zhangyuxin/template/1.8.3
|
||||
|
||||
# pgsql根据远程数据库表生成
|
||||
goctl model pg datasource -url "postgres://labman:tN5bWz2KpL9Fy4@127.0.0.1:15432/sern" -table user -dir ./user --cache --home ../../goctl_template/1.8.3
|
||||
|
||||
# pgsql根据远程数据库表生成,带模式(schema)
|
||||
goctl model pg datasource -url "postgres://admin:zhangyuxin@127.0.0.1:25432/test?sslmode=disable" -s "users" -table "profile" -dir ./go-zero/model/users/ --cache --home ../../deploy/goctl
|
||||
```
|
||||
|
||||
@@ -0,0 +1,263 @@
|
||||
|
||||
|
||||
## 正确关闭`HTTP`响应
|
||||
|
||||
> **注意事项**
|
||||
>
|
||||
> - 先检查 `err`,再 `defer close`
|
||||
>
|
||||
> - 必须消费完响应体,否则连接无法复用
|
||||
>
|
||||
> - `defer` 会延迟到函数结束,如果需要在循环中处理大量响应,建议手动关闭(避免 `defer` 累积)
|
||||
|
||||
```go
|
||||
resp, err := http.Get("https://example.com")
|
||||
if err != nil {
|
||||
// 处理请求错误(此时 resp 可能为 nil)
|
||||
return err
|
||||
}
|
||||
// 确保在函数退出时关闭响应体
|
||||
defer resp.Body.Close()
|
||||
|
||||
// 手动关闭,不用 defer
|
||||
// io.Copy(io.Discard, resp.Body)
|
||||
// resp.Body.Close()
|
||||
|
||||
// 读取或处理响应体(即使不需要内容也建议消费完)
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// 使用 body ...
|
||||
```
|
||||
|
||||
## 切片内存泄漏
|
||||
|
||||
> 即使原切片变量本身被置为 `nil` 或超出作用域,只要**还存在任何一个指向其底层数组的子切片**,整个底层数组就不会被回收
|
||||
|
||||
如果新切片需要长期保存,而原切片很大且即将被丢弃,那么**直接使用原切片[:n] 会导致新切片底层仍然引用原切片的整个底层数组**,使得那个大数组无法被 GC 回收,造成内存泄漏。此时应该用 `copy` 创建一份独立的、只包含所需元素的新切片。
|
||||
|
||||
```go
|
||||
func getFirstLines() []string {
|
||||
hugeLines := readHugeFile() // 假设这个切片有 100 万行,占用 100MB 内存
|
||||
// 错误做法:返回子切片
|
||||
return hugeLines[:3] // 虽然只返回3行,但底层数组的 100MB 内存无法被回收!
|
||||
}
|
||||
|
||||
// 正确做法
|
||||
func getFirstLines() []string {
|
||||
hugeLines := readHugeFile()
|
||||
// 只拷贝需要的3行
|
||||
result := make([]string, 3)
|
||||
copy(result, hugeLines[:3])
|
||||
return result // 原来的 hugeLines 可以被GC回收,只占用很小的内存
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 堆栈
|
||||
|
||||
### 核心区别
|
||||
|
||||
| 特性 | 栈 | 堆 |
|
||||
| --------- | -------------- | ------------------ |
|
||||
| 分配/释放 | 移动指针,极快 | 需要查找 + GC 回收 |
|
||||
| 生命周期 | 函数返回即销毁 | GC 决定何时回收 |
|
||||
| 内存布局 | 连续,无碎片 | 分散,有碎片 |
|
||||
| 大小限制 | 较小(MB级) | 较大(GB级) |
|
||||
|
||||
### 逃逸判断规则
|
||||
|
||||
```
|
||||
变量在以下情况逃逸到堆:
|
||||
|
||||
1. 返回指针 → func f() *int { x := 42; return &x }
|
||||
2. 存入全局变量 → globalPtr = &x
|
||||
3. 闭包捕获 → return func() { return x }
|
||||
4. 发送到 channel → ch <- &x
|
||||
5. 存入外部结构体 → c.Value = &x
|
||||
6. 接口装箱逃逸 → return interface{}(x)
|
||||
7. 反射调用 → fmt.Println(x)
|
||||
8. 大对象 → make([]int, 10000)
|
||||
```
|
||||
|
||||
### 常见误区澄清
|
||||
|
||||
| 误区 | 正解 |
|
||||
| ------------------ | -------------------------------- |
|
||||
| 指针一定在堆上 | ❌ 函数内创建和使用的指针可在栈上 |
|
||||
| 接口一定在堆上 | ❌ 不逃逸的接口内部值可在栈上 |
|
||||
| 结构体指针一定逃逸 | ❌ 小结构体 + 内联优化可能栈分配 |
|
||||
|
||||
### 验证方法
|
||||
|
||||
```bash
|
||||
# 查看逃逸分析
|
||||
go build -gcflags="-m" main.go
|
||||
|
||||
# 更详细信息
|
||||
go build -gcflags="-m -m" main.go
|
||||
```
|
||||
|
||||
### 优化建议
|
||||
|
||||
```go
|
||||
// ✅ 推荐:小对象返回值
|
||||
func getUser() User {
|
||||
return User{
|
||||
ID: 1
|
||||
}
|
||||
}
|
||||
|
||||
// ❌ 避免:小对象返回指针(增加 GC 压力)
|
||||
func getUser() *User {
|
||||
return &User{
|
||||
ID: 1
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ 推荐:传指针给只读函数
|
||||
func read(p *Data) int {
|
||||
return p.Value
|
||||
}
|
||||
|
||||
// ❌ 避免:在热路径使用 fmt.Print
|
||||
// 用 strconv 或 log 代替
|
||||
```
|
||||
|
||||
### 记忆口诀
|
||||
|
||||
```
|
||||
生命周期越界 → 堆
|
||||
外部有引用 → 堆
|
||||
闭包捕获 → 堆
|
||||
反射调用 → 堆
|
||||
|
||||
函数内使用 → 栈
|
||||
返回值拷贝 → 栈
|
||||
只读指针 → 栈
|
||||
```
|
||||
|
||||
### 本质理解
|
||||
|
||||
**栈** = 函数的私有工作区,函数结束自动清理
|
||||
|
||||
**堆** = 共享仓库,需要 GC 定期打扫
|
||||
|
||||
逃逸分析 = 编译器判断"这个变量能不能安全地放在栈上"
|
||||
|
||||
|
||||
|
||||
## 类型转换/序列化
|
||||
|
||||
```
|
||||
//int64转换string
|
||||
strconv.FormatInt(num,10)
|
||||
|
||||
//序列化为string
|
||||
jsoniter.ConfigCompatibleWithStandardLibrary.MarshalToString(input)
|
||||
```
|
||||
|
||||
在 encode/decode JSON 数据并且数据类型是interface时,Go 默认会将数值当做 float64 处理
|
||||
|
||||
```go
|
||||
type User struct {
|
||||
Name string `json:"name"`
|
||||
Age int `json:"age"`
|
||||
Height float64 `json:"height"`
|
||||
}
|
||||
type UserInter struct {
|
||||
Name interface{} `json:"name"`
|
||||
Age interface{} `json:"age"`
|
||||
Height interface{} `json:"height"`
|
||||
}
|
||||
|
||||
func TestJson(t *testing.T) {
|
||||
one := &User{
|
||||
Name: "tom",
|
||||
Age: 18,
|
||||
}
|
||||
oneJson, err := jsoniter.Marshal(one)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
var two UserInter
|
||||
var three map[string]interface{}
|
||||
err = jsoniter.Unmarshal(oneJson, &two)
|
||||
err = jsoniter.Unmarshal(oneJson, &three)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
fmt.Println(reflect.TypeOf(two.Name))
|
||||
fmt.Println(reflect.TypeOf(two.Age)) //会打印float64
|
||||
fmt.Println(reflect.TypeOf(two.Height)) //会打印float64
|
||||
fmt.Println(reflect.TypeOf(three["name"]))
|
||||
fmt.Println(reflect.TypeOf(three["age"])) //会打印float64
|
||||
fmt.Println(reflect.TypeOf(three["height"])) //会打印float64
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
## `HTTP`阅后即焚
|
||||
|
||||
处理 HTTP Body 只能读一次的问题时,根据你的**数据量大小**选择武器:
|
||||
|
||||
1. **小请求(配置、JSON)**:使用 `io.ReadAll` 存入内存,再通过 `io.NopCloser` 塞回 `r.Body` 或设置 `r.GetBody`。
|
||||
2. **中等请求(签名校验)**:使用 `io.TeeReader` 边读边处理,节省一次全量扫描。
|
||||
3. **大请求(文件上传)**:**禁止 ReadAll**。直接流式 `io.Copy` 到目的地,或使用临时文件做中转。
|
||||
|
||||
|
||||
|
||||
## map的坑
|
||||
|
||||
go的map内部是哈希表,元素会因 rehash(扩容)而移动到新的内存位置,如果允许 `&m[key]` 拿到一个指针,map 一旦扩容,这个指针就变成悬空指针(指向已失效的内存),为了避免这种内存安全问题,Go 直接禁止对 map 值取地址,下面是示例代码:
|
||||
|
||||
```go
|
||||
type User struct {
|
||||
Name string
|
||||
Age int
|
||||
}
|
||||
|
||||
func main() {
|
||||
m := map[int]User{1: {Name: "张三", Age: 20}}
|
||||
|
||||
// ❌ 编译错误:cannot assign to struct field m[1].Name in map
|
||||
m[1].Name = "李四"
|
||||
|
||||
// ✅ 正确的替代写法
|
||||
temp:=m[1]
|
||||
temp.Name = "李四"
|
||||
m[1]=temp
|
||||
}
|
||||
```
|
||||
|
||||
> **可寻址(addressable)**:能通过 `&` 拿到它内存地址的操作数。
|
||||
>
|
||||
> **不可寻址(not addressable)**:不能通过 `&` 拿到地址的操作数。
|
||||
|
||||
## 句点引入
|
||||
|
||||
一个完整引入声明语句形式的引入名`importname`可以是一个句点(`.`)。 这样的引入称为句点引入。使用被句点引入的包中的导出代码要素时,限定标识符的前缀必须省略。
|
||||
|
||||
比如在下面这个例子中,`Println`和`Now`函数调用不需要带任何前缀。
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
. "fmt"
|
||||
. "time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
Println("Current time:", Now())
|
||||
}
|
||||
```
|
||||
|
||||
> 一般来说,句点引入不推荐使用,因为它们会导致较低的代码可读性
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
## 依赖与模块管理
|
||||
|
||||
| 命令 | 作用 | 示例 |
|
||||
| ----------------- | ---------------------------------------- | ---------------------------------------- |
|
||||
| `go mod init` | 初始化一个新模块(生成 go.mod) | `go mod init myapp` |
|
||||
| `go mod tidy` | 自动添加缺失的依赖、删除未使用的依赖 | `go mod tidy` |
|
||||
| `go get` | 为当前模块添加/升级/降级依赖 | `go get github.com/gin-gonic/gin@v1.9.0` |
|
||||
| `go mod download` | 预下载 go.mod 中声明的所有依赖到本地缓存 | `go mod download` |
|
||||
| `go mod vendor` | 将依赖复制到 vendor 目录(离线构建用) | `go mod vendor` |
|
||||
| `go list` | 列出包或模块信息 | `go list -m all`(查看所有依赖版本) |
|
||||
|
||||
## 运行与安装
|
||||
|
||||
| 命令 | 作用 | 示例 |
|
||||
| :----------- | :---------------------------------- | :----------------------------------- |
|
||||
| `go run` | 编译并直接运行程序(适合调试) | `go run .` |
|
||||
| `go install` | 编译并安装到 GOBIN 目录(全局可用) | `go install example.com/tool@latest` |
|
||||
|
||||
## 代码质量与格式化
|
||||
|
||||
| 命令 | 作用 | 示例 |
|
||||
| :------- | :--------------------------- | :------------- |
|
||||
| `go fmt` | 自动格式化代码,符合官方风格 | `go fmt ./...` |
|
||||
| `go vet` | 静态检查代码中潜在的错误 | `go vet ./...` |
|
||||
| `go fix` | 自动把旧版 API 更新为新 API | `go fix ./...` |
|
||||
|
||||
## 查看信息
|
||||
|
||||
| 命令 | 作用 | 示例 |
|
||||
| :----------- | :--------------------- | :------------------------------------- |
|
||||
| `go doc` | 查看包或函数的文档 | `go doc fmt.Println` |
|
||||
| `go env` | 查看/修改 Go 环境配置 | `go env -w GOPROXY=https://goproxy.cn` |
|
||||
| `go version` | 查看当前 Go 版本 | `go version` |
|
||||
| `go help` | 查看某个命令的详细帮助 | `go help build` |
|
||||
|
||||
## 其他进阶命令
|
||||
|
||||
| 命令 | 作用 | 说明 |
|
||||
| :------------ | :---------------------------------------------- | :------------------------------------------------------- |
|
||||
| `go clean` | 清除缓存和编译产物 | `go clean -cache`、`go clean -modcache` |
|
||||
| `go generate` | 运行源代码中的 `//go:generate` 指令自动生成代码 | 常用于生成代码模板 |
|
||||
| `go tool` | 调用 Go 自带工具 | `go tool pprof`(性能分析)、`go tool trace`(协程跟踪) |
|
||||
| `go work` | 多模块工作区管理 | `go work init`、`go work use ./xxx` |
|
||||
| `go bug` | 打开浏览器提交 bug 报告 | 会自动附带系统信息 |
|
||||
|
||||
## 交叉编译
|
||||
|
||||
### 基本用法
|
||||
|
||||
```bash
|
||||
# Linux/macOS
|
||||
GOOS=目标系统 GOARCH=目标架构 go build -o 输出文件名 源文件
|
||||
|
||||
# Windows (PowerShell)
|
||||
$env:GOOS="目标系统"; $env:GOARCH="目标架构"; go build -o 输出文件名 源文件
|
||||
|
||||
# Windows (CMD)
|
||||
set GOOS=目标系统 && set GOARCH=目标架构 && go build -o 输出文件名 源文件
|
||||
|
||||
```
|
||||
|
||||
查看所有支持的平台
|
||||
|
||||
```bash
|
||||
go tool dist list
|
||||
```
|
||||
|
||||
### 常用组合
|
||||
|
||||
| 目标平台 | GOOS | GOARCH |
|
||||
| ------------ | ------- | ------ |
|
||||
| Linux 64位 | linux | amd64 |
|
||||
| Linux ARM64 | linux | arm64 |
|
||||
| Windows 64位 | windows | amd64 |
|
||||
| macOS Intel | darwin | amd64 |
|
||||
| macOS M1/M2 | darwin | arm64 |
|
||||
|
||||
### 示例
|
||||
|
||||
```bash
|
||||
# Linux/Mac (Bash)
|
||||
# 编译 Linux 64位
|
||||
GOOS=linux GOARCH=amd64 go build -o app ./app/api/main.go
|
||||
# 编译 Windows 64位
|
||||
GOOS=windows GOARCH=amd64 go build -o app.exe ./app/api/main.go
|
||||
# 编译 macOS ARM64 (M1/M2)
|
||||
GOOS=darwin GOARCH=arm64 go build -o app ./app/api/main.go
|
||||
|
||||
# Windows (PowerShell)
|
||||
# 编译 Linux 64位
|
||||
$env:GOOS="linux"; $env:GOARCH="amd64"; go build -o app ./app/api/main.go
|
||||
# 编译 Windows 64位
|
||||
$env:GOOS="windows"; $env:GOARCH="amd64"; go build -o app.exe ./app/api/main.go
|
||||
# 编译 macOS ARM64 (M1/M2)
|
||||
$env:GOOS="darwin"; $env:GOARCH="arm64"; go build -o app ./app/api/main.go
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,200 @@
|
||||
|
||||
|
||||
### 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,73 @@
|
||||
> Go 1.24 起,`map` 的底层实现从传统的 `hmap` + `bmap` 哈希表,彻底切换到了 Swiss Table(瑞士表)
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
### Group内存布局图
|
||||
|
||||
你可以直接复制以下文本块到你的 Markdown 笔记中(使用 text 包裹):
|
||||
|
||||
codeText
|
||||
|
||||
|
||||
|
||||
```
|
||||
+-------------------------------------------------------------------------------+
|
||||
| 单 Group 内存布局 |
|
||||
+--------------------------+----------------------------+-----------------------+
|
||||
| 1. Metadata 控制字节区 | 2. 连续 Key 存储区 | 3. 连续 Value 存储区 |
|
||||
+--------------------------+----------------------------+-----------------------+
|
||||
| [M0][M1][M2]...[M7] | [K0][K1][K2]...[K7] | [V0][V1][V2]...[V7] |
|
||||
+--------------------------+----------------------------+-----------------------+
|
||||
| <- 固定 8 个字节 -> | <- 长度取决于 Key 类型 -> | <- 长度取决于 Val -> |
|
||||
| <- 一次性载入寄存器对比 -> | |
|
||||
```
|
||||
|
||||
**为什么这样设计?(设计优势):**
|
||||
|
||||
1.
|
||||
2. **避免内存对齐填充(Padding)**:如果采用 [Meta][Key][Value] 交叉排列,由于不同类型的 Key/Value 字节大小不同,为了对齐,Go 必须在它们之间插入大量空白填充字节,浪费空间。分块连续存储消除了组内的对齐损耗。
|
||||
3. **对 CPU 缓存友好**:8 字节的 Metadata 紧密相连,一次 Cache Line 填充就能把整组的控制信息全部读入,极大地提高了缓存命中率。
|
||||
|
||||
------
|
||||
|
||||
|
||||
|
||||
### 读取流程
|
||||
|
||||
以下为您整理的完整、正确的读取逻辑,您可以直接复制使用:
|
||||
|
||||
#### 1.哈希计算与分段 (Hash Splitting)
|
||||
|
||||
- 将 Key 传入哈希函数,计算出一个 **64 位的哈希值**。
|
||||
- 该哈希值被拆分为两部分:**H1(高 57 位)**:用于定位哈希表中的初始 Group 索引。**H2(低 7 位)**:作为特征值(指纹),存储在 Group 的 Metadata 中,用于快速匹配。
|
||||
|
||||
#### 2.定位初始 Group (Locate Group)
|
||||
|
||||
- 计算初始 Group 索引:group_index = H1 & (Group数量 - 1)。
|
||||
|
||||
> 可以理解与$2^B$进行取模运算,但是**位与运算**效率更高且等价。
|
||||
|
||||
#### 3.组内快速匹配 (Group-level SWAR Match)
|
||||
|
||||
- **一次性加载**:将目标 Group 的 8 字节 Metadata 一次性载入一个 64 位寄存器中。
|
||||
- **并行对比(SWAR 技术)**:利用位运算,将 7 位的 H2 同时与这 8 个 Metadata 字节进行并行对比,输出一个 8 位的位掩码(Bitmask)。位掩码中为 1 的位,代表该位置的 H2 与目标 H2 匹配成功。这一步极快,完全避免了循环遍历。
|
||||
|
||||
#### 4.冲突解决与探测循环 (Verification & Probing Loop)
|
||||
|
||||
根据位掩码的匹配结果,执行以下逻辑:
|
||||
|
||||
1. 依次根据匹配成功的索引,去 **Key 存储区** 找到对应的真实 Key。
|
||||
2. 进行完整的 Key 等值对比(如 if key == targetKey):**若匹配成功**:直接去 **Value 存储区** 对应偏移量取出 Value 并返回。**若匹配失败**:说明发生了 **H2 哈希冲突**(即不同的 Key 产生了相同的 H2),继续对比下一个匹配的槽位。
|
||||
3. 如果当前 Group 中所有匹配了 H2 的槽位都对比完了,仍未找到匹配的 Key,则**跳转至【情况 B】**。
|
||||
|
||||
此时需要检查当前 Group 的 Metadata 状态,决定是继续向后寻找还是停止:
|
||||
|
||||
- **若当前 Group 中存在“空位”(Empty,0xFF)**:
|
||||
|
||||
说明探测链在此处断开,该 Key 在 Map 中**一定不存在**,直接返回 nil 或零值。
|
||||
|
||||
- **若当前 Group 中无空位,但存在“墓碑”(Tombstone,0x80)或已满**:
|
||||
|
||||
虽然当前 Group 没找到,但可能因为历史删除操作(留下了墓碑)或哈希冲突,导致数据被存到了后续的 Group。不能停止!使用**二次探测算法(Quadratic Probing)**计算下一个 Group 的索引,跳转回 **【3.组内快速匹配】** 继续循环寻找。
|
||||
@@ -0,0 +1,17 @@
|
||||
## 命名规则
|
||||
|
||||
> [《布尔变量起名的艺术》](https://thatamazingprogrammer.com/posts/stop-naming-your-variables-flag-the-art-of-boolean-prefixes/)
|
||||
|
||||
### 使用正确的前缀
|
||||
|
||||
(1)**is**:描述事物的状态,后面跟形容词,比如 isActive,isDeleted,isEmpty。
|
||||
|
||||
(2)**has**:描述事物的所有权或包含关系,后面跟名词,比如 hasAccess,hasChildren,hasValidationErrors。
|
||||
|
||||
(3)**can**:描述事物的能力或权限,比如 canEdit,canDelete,canRetry。
|
||||
|
||||
(4)**should**:描述事物的意图或逻辑,比如 shouldRetry,shouldCacheResponse。
|
||||
|
||||
### 其他
|
||||
|
||||
永远不在布尔变量名中使用否定词,比如isDisabled,因为当使用在if语句中会让大脑进行双重否定运算
|
||||
@@ -0,0 +1,120 @@
|
||||
### URL设计规范
|
||||
|
||||
URL为统一资源定位器 ,接口属于服务端资源,通常一个完整的URL组成由以下几个部分构成:
|
||||
|
||||
```
|
||||
URI = scheme "://" host ":" port "/" path [ "?" query ][ "#" fragment ]
|
||||
```
|
||||
|
||||
- **scheme**: 指底层用的协议,如http、https、ftp
|
||||
- **host**: 服务器的IP地址或者域名
|
||||
- **port**: 端口,http默认为80端口
|
||||
- **path**: 访问资源的路径,就是各种web 框架中定义的route路由
|
||||
- **query**: 查询字符串,为发送给服务器的参数,在这里更多发送数据分页、排序等参数。
|
||||
- **fragment**: 锚点,定位到页面的资源
|
||||
|
||||
|
||||
|
||||
**通常一个RESTful API的path组成**如下:
|
||||
|
||||
```undefined
|
||||
/{version}/{resources}/{resource_id}
|
||||
```
|
||||
|
||||
- **version**:API版本号,有些版本号放置在头信息中也可以,通过控制版本号有利于应用迭代。
|
||||
- **resources**:资源,RESTful API推荐用小写英文单词的复数形式。
|
||||
- **resource_id**:资源的id,访问或操作该资源。
|
||||
|
||||
|
||||
|
||||
有时候可能资源级别较大,其下还可细分很多子资源也可以灵活设计URL的path,例如:
|
||||
|
||||
```undefined
|
||||
/{version}/{resources}/{resource_id}/{subresources}/{subresource_id}
|
||||
```
|
||||
|
||||
|
||||
|
||||
当增删改查无法满足业务要求,可以在URL末尾加上action,例如
|
||||
|
||||
```
|
||||
/{version}/{resources}/{resource_id}/action
|
||||
```
|
||||
|
||||
其中action就是对资源的操作。
|
||||
|
||||
|
||||
|
||||
**RESTful API的URL具体设计的规范**如下:
|
||||
|
||||
1. 不用大写字母,所有单词使用英文且小写。
|
||||
2. 连字符用中杠`"-"`而不用下杠`"_"`
|
||||
3. 正确使用 `"/"`表示层级关系,URL的层级不要过深,并且越靠前的层级应该相对越稳定
|
||||
4. 结尾不要包含正斜杠分隔符`"/"`
|
||||
5. URL中不出现动词,用请求方式表示动作
|
||||
6. 资源表示用复数不要用单数
|
||||
7. 不要使用文件扩展名
|
||||
|
||||
|
||||
|
||||
**[参考网址](https://www.cnblogs.com/bigsai/p/14099154.html)**
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
||||
### HTTP方法
|
||||
|
||||
- **GET**
|
||||
|
||||
GET请求会向数据库发索取数据的请求,从而来获取资源,该请求就像数据库的select操作一样,**只是用来查询数据,不会影响资源的内容**。无论进行多少次操作,结果都是一样的
|
||||
|
||||
- **POST**
|
||||
|
||||
POST请求向服务器发送数据,但是该请求**会改变数据的内容(新添)**,就像数据库的`insert`操作一样,会创建新的内容,且POST请求的请求参数都是请求体中,其大小是没有限制的
|
||||
|
||||
- **PUT**
|
||||
|
||||
PUT请求是向服务器端发送数据的, 与POST请求不同的是,PUT请求**侧重于数据的修改 ,就像数据库中update一样**,而POST请求侧重于数据的增加
|
||||
|
||||
- **DELETE**
|
||||
|
||||
用来**删除资源**,和数据库中`delete`相对应
|
||||
|
||||
|
||||
|
||||
### 参数填写位置
|
||||
|
||||
- **Params(Query 参数)**:用于 GET 请求或其他将参数**附加在 URL 中**的情况。
|
||||
|
||||
- **Body(请求体)**:用于 POST、PUT 等请求方法,将参数**包含在请求体**中。
|
||||
|
||||
|
||||
|
||||
### 常见的请求文件类型及其适用场景
|
||||
|
||||
- **application/json**
|
||||
|
||||
用途:用于传输结构化数据,如对象和数组。
|
||||
|
||||
场景:客户端与服务器之间交换数据时,尤其是在前后端分离的应用中。
|
||||
|
||||
- **multipart/form-data**
|
||||
|
||||
用途:用于上传文件或包含文件的表单数据。
|
||||
|
||||
场景:用户上传图片、视频、文档等文件。
|
||||
|
||||
- **application/x-www-form-urlencoded**
|
||||
|
||||
用途:用于提交简单的表单数据。
|
||||
|
||||
场景:传统的表单提交,数据量较小且不包含文件。
|
||||
|
||||
- **application/octet-stream**
|
||||
|
||||
用途:用于传输二进制数据。
|
||||
|
||||
场景:下载文件或通过 API 提供文件流。
|
||||
@@ -0,0 +1,92 @@
|
||||
## Cookie
|
||||
|
||||
| 维度 | 说明 |
|
||||
| :----------- | :----------------------------------------------------------- |
|
||||
| **本质** | 存储在浏览器端的一小段文本数据(通常 4KB 以内) |
|
||||
| **核心机制** | 浏览器在每次 HTTP 请求中**自动携带**符合域名/路径规则的 Cookie |
|
||||
| **关键属性** | `HttpOnly`(防 JS 读取)、`Secure`(仅 HTTPS)、`SameSite`(防 CSRF)、`Expires/Max-Age` |
|
||||
| **定位** | **传输载体,而非认证方案本身**——它负责把令牌(Session ID 或 Token)在客户端和服务器之间来回搬运 |
|
||||
|
||||
**优点**:
|
||||
|
||||
- 简单成熟,几乎所有 Web 框架默认集成
|
||||
- 服务端可随时销毁会话,控制力强
|
||||
- 敏感信息存在服务端,不暴露给客户端
|
||||
|
||||
**缺点**:
|
||||
|
||||
- 扩展性受限——多服务器需共享 Session(引入 Redis 等外部存储)
|
||||
- 依赖 Cookie,纯移动端/非浏览器环境不友好
|
||||
- 容易受到 CSRF 攻击(需额外防护)
|
||||
|
||||
## Session
|
||||
|
||||
| 维度 | 说明 |
|
||||
| :----------- | :----------------------------------------------------------- |
|
||||
| **本质** | 服务器端维护的用户会话数据(通常以 Map 结构存在内存、Redis 或数据库中) |
|
||||
| **认证流程** | 用户登录 → 服务器创建 Session → 将 Session ID 通过 `Set-Cookie` 返回 → 浏览器后续请求自动携带该 Cookie → 服务器通过 Session ID 查找会话数据验证身份 |
|
||||
| **核心特点** | **有状态**:服务器持有用户上下文,可随时主动吊销 |
|
||||
| **定位** | 经典的**服务端主导**的认证方式,适合传统 Web 应用和前后端未彻底分离的场景 |
|
||||
|
||||
**优点**:
|
||||
|
||||
- 简单成熟,几乎所有 Web 框架默认集成
|
||||
- 服务端可随时销毁会话,控制力强
|
||||
- 敏感信息存在服务端,不暴露给客户端
|
||||
|
||||
**缺点**:
|
||||
|
||||
- 扩展性受限——多服务器需共享 Session(引入 Redis 等外部存储)
|
||||
- 依赖 Cookie,纯移动端/非浏览器环境不友好
|
||||
- 容易受到 CSRF 攻击(需额外防护)
|
||||
|
||||
## JWT
|
||||
|
||||
| **本质** | 一段经过签名(或加密)的 JSON 数据,由 Header、Payload、Signature 三部分组成,以 Base64URL 编码 |
|
||||
| ------------ | ------------------------------------------------------------ |
|
||||
| **认证流程** | 用户登录 → 服务器验证凭据并签发 JWT → 客户端存储 Token → 后续请求在 `Authorization: Bearer <token>` 头中携带 → 服务器**仅验证签名和有效期**,无需查库 |
|
||||
| **核心特点** | **无状态**:Token 自身包含用户身份信息,服务器不需要维护会话 |
|
||||
| **定位** | 现代**前后端分离架构**和**多端(Web/iOS/Android)统一接入**的事实标准 |
|
||||
|
||||
**优点**:
|
||||
|
||||
- 天然无状态,服务端可随意水平扩展
|
||||
- 不依赖 Cookie,可用于移动端、小程序等非浏览器环境
|
||||
- 跨域友好(CORS 场景下比 Cookie 更灵活)
|
||||
- Payload 可携带少量业务数据,减少数据库查询
|
||||
|
||||
**缺点**:
|
||||
|
||||
- Token 一旦签发,在过期前无法主动撤销(需引入黑名单/版本号等机制)
|
||||
- Payload 仅 Base64 编码(非加密),敏感信息绝对不能放入
|
||||
- Token 体积比 Session ID 大,每次请求都传输有一定带宽开销
|
||||
|
||||
## SSO
|
||||
|
||||
| 维度 | 说明 |
|
||||
| :----------- | :----------------------------------------------------------- |
|
||||
| **本质** | 一种架构模式,通过**统一的认证中心(IdP)**,让用户在多个独立系统间共享登录状态 |
|
||||
| **认证流程** | 用户访问应用A → 重定向到 SSO 认证中心 → 登录并获取令牌 → 带回令牌访问应用A → 再访问应用B时,认证中心识别已登录状态,直接放行 |
|
||||
| **常见实现** | CAS、SAML、OAuth 2.0 + OpenID Connect |
|
||||
| **定位** | **企业级、多系统生态**的统一身份认证方案 |
|
||||
|
||||
**优点**:
|
||||
|
||||
- 用户体验极佳:一次登录,访问所有关联系统
|
||||
- 集中管理:统一账号体系、权限策略、安全审计
|
||||
- 安全性提升:减少密码泄露面,可配合多因素认证(MFA)
|
||||
|
||||
**缺点**:
|
||||
|
||||
- **单点故障风险**:认证中心一旦宕机,所有系统登录受影响
|
||||
- 实现复杂:涉及协议配置、令牌传递、跨域重定向等
|
||||
- 对认证中心的高可用要求极高
|
||||
|
||||
## OAuth 2.0
|
||||
|
||||
| 维度 | 说明 |
|
||||
| :----------- | :----------------------------------------------------------- |
|
||||
| **本质** | 一种**授权框架**(而非纯粹的认证协议),允许用户授权第三方应用在**不暴露自己密码**的情况下访问受保护资源 |
|
||||
| **核心角色** | 资源拥有者(用户)、客户端(第三方应用)、授权服务器、资源服务器 |
|
||||
| **核心流程** | 用户授权 → 第三方获取授权码 → 换取 Access Token → 持 Token 访问资源 |
|
||||
| **定位** | **第三方接入**和**开放平台**的标准授权方案(微信登录、Google 登录等) |
|
||||
@@ -0,0 +1,22 @@
|
||||
## `errors.AsType`
|
||||
|
||||
> *用泛型把* `errors.As` *的“先声明变量再传指针”简化为“直接返回* `(E, bool)`*”,是* `errors.As` *的语法糖升级*
|
||||
|
||||
**使用场景:**
|
||||
|
||||
- 与 `errors.As` 完全相同(按类型提取错误),但写起来更简洁
|
||||
- 由于类型参数 `E` 必须是 `error` 接口的实现类型,**能在编译期避免 `errors.As` 那种“传错指针类型导致运行时 panic”的问题**
|
||||
|
||||
```go
|
||||
// 传统 errors.As:需要先声明变量
|
||||
var mysqlErr *mysql.MySQLError
|
||||
if errors.As(err, &mysqlErr) {
|
||||
return true, mysqlErr.Number
|
||||
}
|
||||
|
||||
// errors.AsType(Go 1.26+):无需预先声明变量
|
||||
if mysqlErr, ok := errors.AsType[*mysql.MySQLError](err); ok {
|
||||
return true, mysqlErr.Number
|
||||
}
|
||||
```
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
### 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,23 @@
|
||||
# MySQL 字符集的终极巨坑 —— `utf8` vs `utf8mb4`
|
||||
|
||||
### 一句话总结
|
||||
|
||||
MySQL 中的 `utf8` 是个“假货”(最多只支持 3 字节),`utf8mb4` 才是真正的 UTF-8(支持 4 字节)。
|
||||
|
||||
### 为什么会有这个坑
|
||||
|
||||
- **历史遗留**:早期 MySQL 开发时,Unicode 字符确实只用 3 个字节就能存下。
|
||||
- **现象**:一旦用户输入 **Emoji 表情**(如 😀)或部分**生僻字**(如 𰻝),数据库会直接抛出错误:`Incorrect string value`。
|
||||
|
||||
### 📊 核心对比
|
||||
|
||||
| 字符集 | 真实身份 | 最大字节数 | 能否存 Emoji | 状态 |
|
||||
| :------------ | :----------------- | :--------- | :----------- | :--------- |
|
||||
| **`utf8`** | `utf8mb3` (阉割版) | 3 字节 | ❌ 报错 | ⚠️ 已废弃 |
|
||||
| **`utf8mb4`** | 真正的 UTF-8 | 4 字节 | ✅ 随便存 | ✅ 推荐使用 |
|
||||
|
||||
### 🛠️ 各版本现状与未来
|
||||
|
||||
- **MySQL 5.7 及以前**:默认字符集是 `latin1`,`utf8` 是重灾区。
|
||||
- **MySQL 8.0 及以后**:**默认字符集已改为 `utf8mb4`**(新项目安全系数提升)。
|
||||
- **官方态度**:目前 `utf8`(即 `utf8mb3`)已被标记为 Deprecated(废弃)。官方计划在**未来的某个大版本中彻底删除 `utf8mb3`**,并让 `utf8` 这个名字正式指向 `utf8mb4`。
|
||||
@@ -0,0 +1,476 @@
|
||||
> 感觉还是不如typora好使啊,以后买个正版吧
|
||||
|
||||
```css
|
||||
/**
|
||||
* MarkText One Light Dark 主题 (仿 Typora onelight-dark:https://github.com/caolib/typora-onelight-theme)
|
||||
*
|
||||
* 使用方法:
|
||||
* 在 MarkText 中打开 设置 → 主题 → 自定义 CSS,
|
||||
* 将本文件全部内容粘贴进去,保存即可。
|
||||
*
|
||||
* 配色来源: Typora onelight-dark theme by caolib
|
||||
* 兼容: MarkText v0.19.x (旧引擎) / v0.20+ (新引擎)
|
||||
*/
|
||||
|
||||
/* ====================================================================
|
||||
* CSS 变量 — 使用 camelCase 命名 (旧引擎 v0.19.x 兼容)
|
||||
* ==================================================================== */
|
||||
:root {
|
||||
/* —— 编辑器核心 —— */
|
||||
--themeColor: #5c99f3;
|
||||
--themeColor90: rgba(92, 153, 243, 0.9);
|
||||
--themeColor80: rgba(92, 153, 243, 0.8);
|
||||
--themeColor70: rgba(92, 153, 243, 0.7);
|
||||
--themeColor60: rgba(92, 153, 243, 0.6);
|
||||
--themeColor50: rgba(92, 153, 243, 0.5);
|
||||
--themeColor40: rgba(92, 153, 243, 0.4);
|
||||
--themeColor30: rgba(92, 153, 243, 0.3);
|
||||
--themeColor20: rgba(92, 153, 243, 0.2);
|
||||
--themeColor10: rgba(92, 153, 243, 0.1);
|
||||
--highlightThemeColor: #e94ab9;
|
||||
|
||||
--highlightColor: #ffffff10;
|
||||
--selectionColor: #3266d0;
|
||||
--editorColor: #c3c3c3;
|
||||
--editorColor80: #c3c3c3;
|
||||
--editorColor60: rgba(195, 195, 195, 0.6);
|
||||
--editorColor50: rgba(195, 195, 195, 0.5);
|
||||
--editorColor40: rgba(195, 195, 195, 0.4);
|
||||
--editorColor30: rgba(195, 195, 195, 0.3);
|
||||
--editorColor10: rgba(195, 195, 195, 0.1);
|
||||
--editorColor04: rgba(195, 195, 195, 0.04);
|
||||
--editorBgColor: #1f1f1e;
|
||||
--editorAreaWidth: 900px;
|
||||
--deleteColor: #ff6b6b;
|
||||
--iconColor: rgba(195, 195, 195, 0.56);
|
||||
--codeBgColor: #282c34;
|
||||
--codeBlockBgColor: #282c34;
|
||||
--footnoteBgColor: rgba(51, 51, 51, 0.5);
|
||||
--inputBgColor: #3d404e;
|
||||
--focusColor: #5c99f3;
|
||||
|
||||
/* —— 按钮 —— */
|
||||
--buttonFontColor: #c3c3c3;
|
||||
--buttonBgColor: #333333;
|
||||
--buttonBorder: 1px solid #353a45;
|
||||
--buttonShadow: none;
|
||||
--buttonFontColorHover: #ffffff;
|
||||
--buttonBgColorHover: #414855;
|
||||
--buttonBorderHover: var(--buttonBorder);
|
||||
--buttonFontColorActive: var(--buttonFontColor);
|
||||
--buttonBgColorActive: #2a2d35;
|
||||
--buttonBorderActive: var(--buttonBorder);
|
||||
--buttonFocusBorder: 1px solid #5c99f3;
|
||||
--buttonFocusShadow: 0 0 0 1px rgba(92, 153, 243, 0.3);
|
||||
|
||||
--buttonPrimaryFontColor: #ffffff;
|
||||
--buttonPrimaryBgColor: #5c99f3;
|
||||
--buttonPrimaryBorder: 1px solid #4a8ad9;
|
||||
--buttonPrimaryShadow: none;
|
||||
--buttonPrimaryFontColorHover: var(--buttonPrimaryFontColor);
|
||||
--buttonPrimaryBgColorHover: #4a8ad9;
|
||||
--buttonPrimaryBorderHover: var(--buttonPrimaryBorder);
|
||||
--buttonPrimaryFontColorActive: var(--buttonPrimaryFontColor);
|
||||
--buttonPrimaryBgColorActive: #3d7bc4;
|
||||
--buttonPrimaryBorderActive: var(--buttonPrimaryBorder);
|
||||
--buttonPrimaryFocusBorder: none;
|
||||
--buttonPrimaryFocusShadow: inset 0 0 0 1px rgba(24, 26, 31, 0.5), 0 0 0 1px #5c99f3;
|
||||
|
||||
/* —— Markdown 元素 —— */
|
||||
--headingColor: #c3c3c3;
|
||||
--h1Color: #c3c3c3;
|
||||
--h2Color: #c3c3c3;
|
||||
--h3Color: #c3c3c3;
|
||||
--h4Color: #ffffff;
|
||||
--h5Color: #c3c3c3;
|
||||
--h6Color: #c3c3c3;
|
||||
--blockquoteTextColor: rgba(195, 195, 195, 0.8);
|
||||
--blockquoteBorderColor: #5c99f3;
|
||||
--hrColor: #5c99f3;
|
||||
--linkColor: #e94ab9;
|
||||
--strongColor: #5c99f3;
|
||||
--emColor: #e94ab9;
|
||||
--listMarkerColor: #5c99f3;
|
||||
--tableBorderColor: #393e49;
|
||||
|
||||
/* —— 侧边栏 —— */
|
||||
--sideBarColor: #c3c3c3;
|
||||
--sideBarIconColor: var(--iconColor);
|
||||
--sideBarTitleColor: #c3c3c3;
|
||||
--sideBarTextColor: #7a7a7a;
|
||||
--sideBarBgColor: #1f1f1e;
|
||||
--sideBarItemHoverBgColor: #333333;
|
||||
--itemBgColor: #333333;
|
||||
|
||||
/* —— 浮动层 —— */
|
||||
--floatFontColor: #c3c3c3;
|
||||
--floatBgColor: #353a45;
|
||||
--floatHoverColor: #414855;
|
||||
--floatBorderColor: #353a45;
|
||||
--floatShadow: rgba(0, 0, 0, 0.3);
|
||||
--maskColor: rgba(0, 0, 0, 0.7);
|
||||
}
|
||||
|
||||
/* ====================================================================
|
||||
* 全局背景 — 统一为 Typora onelight-dark 底色 #1f1f1e
|
||||
* ==================================================================== */
|
||||
html, body {
|
||||
background-color: #1f1f1e;
|
||||
}
|
||||
.editor-wrapper {
|
||||
background-color: #1f1f1e;
|
||||
}
|
||||
.editor-component {
|
||||
background-color: #1f1f1e;
|
||||
}
|
||||
#ag-editor-id {
|
||||
background-color: #1f1f1e;
|
||||
}
|
||||
|
||||
/* ====================================================================
|
||||
* 滚动条
|
||||
* ==================================================================== */
|
||||
::-webkit-scrollbar,
|
||||
::-webkit-scrollbar-corner {
|
||||
background: var(--editorBgColor);
|
||||
}
|
||||
::-webkit-scrollbar:vertical { width: 8px; }
|
||||
::-webkit-scrollbar:vertical:hover { width: 10px; }
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: rgba(134, 134, 134, 0.27);
|
||||
border-radius: 4px;
|
||||
}
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
/* ====================================================================
|
||||
* 弹窗 (Element UI)
|
||||
* ==================================================================== */
|
||||
.v-modal { background: #08090a !important; opacity: 0.65 !important; }
|
||||
.el-dialog,
|
||||
.el-dialog.ag-dialog-table {
|
||||
border-radius: 6px !important;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3) !important;
|
||||
border: 1px solid #353a45 !important;
|
||||
background: #2a2a29 !important;
|
||||
}
|
||||
.el-button:focus { color: var(--buttonFontColor); }
|
||||
.el-button--primary:focus {
|
||||
color: var(--buttonPrimaryFontColor);
|
||||
background: var(--buttonPrimaryBgColor);
|
||||
}
|
||||
|
||||
/* ====================================================================
|
||||
* 标题栏 / 浮动菜单
|
||||
* ==================================================================== */
|
||||
.title-bar .frameless-titlebar-button > div > svg { fill: #c3c3c3; }
|
||||
.title-bar .frameless-titlebar-minimize:hover,
|
||||
.title-bar .frameless-titlebar-toggle:hover {
|
||||
background-color: rgba(195, 195, 195, 0.05) !important;
|
||||
}
|
||||
.input-wrapper {
|
||||
border-radius: 5px !important;
|
||||
border: 1px solid #353a45 !important;
|
||||
background: #2a2a29 !important;
|
||||
}
|
||||
.ag-front-menu,
|
||||
.ag-float-wrapper {
|
||||
box-shadow: 0 4px 8px 0 var(--floatShadow) !important;
|
||||
}
|
||||
|
||||
/* ====================================================================
|
||||
* 侧边栏
|
||||
* ==================================================================== */
|
||||
.side-bar { border-right: 1px solid #333333 !important; }
|
||||
.left-column ul > li > svg { fill: rgba(195, 195, 195, 0.6) !important; }
|
||||
.left-column ul > li:hover > svg { fill: #ffffff !important; }
|
||||
.left-column ul > li.active > svg { fill: #5c99f3 !important; }
|
||||
|
||||
/* ====================================================================
|
||||
* 标签栏
|
||||
* ==================================================================== */
|
||||
.editor-tabs {
|
||||
box-shadow: none !important;
|
||||
background: var(--editorBgColor) !important;
|
||||
}
|
||||
.editor-tabs:after {
|
||||
position: absolute;
|
||||
content: '';
|
||||
border-bottom: 1px solid #333333;
|
||||
bottom: 0; left: 0; right: 0; z-index: 1;
|
||||
}
|
||||
.editor-tabs ul.tabs-container:after {
|
||||
position: absolute;
|
||||
content: '';
|
||||
border-bottom: 1px solid #333333;
|
||||
bottom: 0; left: 0; right: 0; z-index: 2;
|
||||
}
|
||||
.tabs-container > li,
|
||||
.tabs-container > li.active { background: var(--editorBgColor) !important; }
|
||||
.tabs-container > li.active {
|
||||
border: 1px solid #333333;
|
||||
border-bottom: none;
|
||||
}
|
||||
.tabs-container > li.active:after {
|
||||
top: 0 !important; right: auto !important;
|
||||
width: 2px !important; height: auto !important;
|
||||
background: #5c99f3 !important;
|
||||
}
|
||||
.tabs-container svg.close-icon #unsaved-circle-icon { fill: #5c99f3; }
|
||||
|
||||
/* ====================================================================
|
||||
* 编辑器内容区 (v0.19.x 旧引擎: 容器是 #ag-editor-id)
|
||||
* ==================================================================== */
|
||||
|
||||
/* ---- 标题 ---- */
|
||||
#ag-editor-id h1 {
|
||||
text-align: center;
|
||||
padding-bottom: 0.3em;
|
||||
}
|
||||
#ag-editor-id h2 {
|
||||
border-bottom: 2px solid var(--themeColor);
|
||||
}
|
||||
#ag-editor-id h3 {
|
||||
border-left: 4px solid var(--themeColor);
|
||||
padding-left: 8px;
|
||||
}
|
||||
#ag-editor-id h4 {
|
||||
color: #ffffff !important;
|
||||
background: var(--themeColor);
|
||||
border-radius: 5px;
|
||||
width: fit-content;
|
||||
padding: 2px 12px;
|
||||
}
|
||||
|
||||
/* ---- 粗体 / 斜体 ---- */
|
||||
#ag-editor-id strong { color: var(--strongColor); font-weight: 700; }
|
||||
#ag-editor-id em { color: var(--emColor); }
|
||||
|
||||
/* ---- 链接 ---- */
|
||||
#ag-editor-id a {
|
||||
color: var(--linkColor);
|
||||
font-weight: 700;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* ---- 行内代码 ---- */
|
||||
#ag-editor-id code {
|
||||
background-color: var(--codeBlockBgColor);
|
||||
color: #eb4c37;
|
||||
border-radius: 3px;
|
||||
padding: 2px 4px;
|
||||
}
|
||||
|
||||
/* ---- 代码块 ---- */
|
||||
#ag-editor-id :not(pre) > code[class*='language-'],
|
||||
#ag-editor-id pre:not(.CodeMirror-line),
|
||||
#ag-editor-id pre[class*='language-'],
|
||||
#ag-editor-id pre.ag-paragraph {
|
||||
background: var(--codeBlockBgColor) !important;
|
||||
border: none !important;
|
||||
border-radius: 5px !important;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
||||
transition: box-shadow 0.3s ease;
|
||||
}
|
||||
|
||||
/* ---- 引用块 (四面边框 + 左侧加粗) ---- */
|
||||
#ag-editor-id blockquote {
|
||||
padding: 10px 10px 10px 20px;
|
||||
font-size: 0.9em;
|
||||
border: 2px solid var(--blockquoteBorderColor);
|
||||
border-left: 5px solid var(--blockquoteBorderColor);
|
||||
border-radius: 0 5px 5px 0;
|
||||
background: transparent;
|
||||
overflow: auto;
|
||||
transition: background-color 0.3s ease, box-shadow 0.3s ease;
|
||||
}
|
||||
#ag-editor-id blockquote:hover {
|
||||
background: var(--editorBgColor);
|
||||
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
/* 隐藏引擎默认的 ::before 竖条,改用 border-left */
|
||||
#ag-editor-id blockquote::before {
|
||||
content: none;
|
||||
}
|
||||
|
||||
/* ---- 分割线 ---- */
|
||||
#ag-editor-id hr {
|
||||
height: 2px;
|
||||
background-color: var(--hrColor);
|
||||
}
|
||||
|
||||
/* ---- 表格 ---- */
|
||||
#ag-editor-id table tr th::before,
|
||||
#ag-editor-id table tr td::before {
|
||||
border-color: var(--tableBorderColor);
|
||||
}
|
||||
|
||||
/* ---- 图片 ---- */
|
||||
#ag-editor-id img {
|
||||
border-radius: 10px;
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
/* ---- 任务列表复选框 ---- */
|
||||
#ag-editor-id li.ag-task-list-item {
|
||||
list-style-type: none;
|
||||
position: relative;
|
||||
}
|
||||
#ag-editor-id li.ag-task-list-item > input[type='checkbox'] {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
width: 16px; height: 16px; top: 0.1em;
|
||||
margin: 0; left: -24px;
|
||||
transform-origin: center;
|
||||
transition: all 0.2s ease;
|
||||
appearance: none;
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
#ag-editor-id li.ag-task-list-item > input[type='checkbox']::before {
|
||||
content: '';
|
||||
width: 16px; height: 16px;
|
||||
box-sizing: border-box;
|
||||
display: inline-block;
|
||||
border: 2px solid var(--editorColor50);
|
||||
border-radius: 2px;
|
||||
background-color: var(--editorBgColor);
|
||||
position: absolute; top: 0; left: 0;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
#ag-editor-id li.ag-task-list-item > input[type='checkbox']:checked::before {
|
||||
border-color: transparent;
|
||||
background-color: var(--themeColor);
|
||||
}
|
||||
#ag-editor-id li.ag-task-list-item > input[type='checkbox']::after {
|
||||
content: '';
|
||||
transform: rotate(-45deg) scale(0);
|
||||
width: 9px; height: 5px;
|
||||
border: 2px solid #fff;
|
||||
border-top: none; border-right: none;
|
||||
position: absolute; display: inline-block;
|
||||
top: 1px; left: 5px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
#ag-editor-id li.ag-task-list-item > input[type='checkbox']:checked::after {
|
||||
transform: rotate(-45deg) scale(1);
|
||||
}
|
||||
|
||||
/* ---- 段落悬停 ---- */
|
||||
#ag-editor-id p.ag-paragraph {
|
||||
padding: 5px;
|
||||
margin: 0;
|
||||
border-radius: 5px;
|
||||
transition: background-color 0.3s ease, box-shadow 0.3s ease;
|
||||
}
|
||||
#ag-editor-id p.ag-paragraph:hover {
|
||||
background: #141414;
|
||||
box-shadow: 0 4px 4px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
/* ---- 已完成任务文字 ---- */
|
||||
#ag-editor-id li.ag-task-list-item > input.ag-checkbox-checked ~ * {
|
||||
color: var(--editorColor50);
|
||||
}
|
||||
|
||||
/* ---- 列表标记 ---- */
|
||||
#ag-editor-id ul > li::marker,
|
||||
#ag-editor-id ol > li::marker {
|
||||
color: var(--listMarkerColor);
|
||||
}
|
||||
|
||||
/* ---- 搜索高亮 ---- */
|
||||
.ag-highlight {
|
||||
background: #ff0 !important;
|
||||
color: red !important;
|
||||
border-radius: 3px;
|
||||
padding: 0 2px;
|
||||
height: auto !important;
|
||||
line-height: inherit !important;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
/* ---- 表格表头 ---- */
|
||||
#ag-editor-id table tr th {
|
||||
background: var(--editorColor04);
|
||||
}
|
||||
#ag-editor-id table tr:first-child td {
|
||||
background: var(--editorColor04);
|
||||
}
|
||||
|
||||
/* ---- 脚注 ---- */
|
||||
#ag-editor-id sup.md-footnote {
|
||||
color: var(--strongColor);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
/* ---- 前置图标 ---- */
|
||||
.ag-front-icon {
|
||||
fill: var(--editorColor30);
|
||||
}
|
||||
|
||||
/* ====================================================================
|
||||
* CodeMirror 源码模式 (v0.19.x 旧引擎)
|
||||
* ==================================================================== */
|
||||
.source-code {
|
||||
background: var(--editorBgColor);
|
||||
}
|
||||
.source-code .CodeMirror {
|
||||
color: var(--editorColor);
|
||||
background: transparent;
|
||||
}
|
||||
.source-code .CodeMirror-gutters {
|
||||
background: transparent;
|
||||
border-right-color: #333333;
|
||||
}
|
||||
.source-code .CodeMirror-linenumber {
|
||||
color: var(--editorColor30);
|
||||
}
|
||||
.source-code .CodeMirror-cursor {
|
||||
border-left-color: var(--themeColor);
|
||||
}
|
||||
.source-code .CodeMirror-activeline-background {
|
||||
background: var(--editorColor04);
|
||||
}
|
||||
.source-code .CodeMirror-selected {
|
||||
background: var(--selectionColor) !important;
|
||||
}
|
||||
|
||||
/* CodeMirror 语法高亮色 — 参考 onelight 配色 */
|
||||
.cm-s-one-dark .cm-comment { color: #7f848e; font-style: italic; }
|
||||
.cm-s-one-dark .cm-keyword { color: #c678dd; }
|
||||
.cm-s-one-dark .cm-operator { color: #56b6c2; }
|
||||
.cm-s-one-dark .cm-builtin { color: #e5c07b; }
|
||||
.cm-s-one-dark .cm-atom { color: #d19a66; }
|
||||
.cm-s-one-dark .cm-number { color: #d19a66; }
|
||||
.cm-s-one-dark .cm-def { color: #61afef; }
|
||||
.cm-s-one-dark .cm-string { color: #98c379; }
|
||||
.cm-s-one-dark .cm-string-2 { color: #e06c75; }
|
||||
.cm-s-one-dark .cm-variable { color: #e06c75; }
|
||||
.cm-s-one-dark .cm-variable-2 { color: #e5c07b; }
|
||||
.cm-s-one-dark .cm-variable-3 { color: #d19a66; }
|
||||
.cm-s-one-dark .cm-type { color: #e5c07b; }
|
||||
.cm-s-one-dark .cm-meta { color: #e06c75; }
|
||||
.cm-s-one-dark .cm-attribute { color: #d19a66; }
|
||||
.cm-s-one-dark .cm-property { color: #e06c75; }
|
||||
.cm-s-one-dark .cm-qualifier { color: #e5c07b; }
|
||||
.cm-s-one-dark .cm-tag { color: #e06c75; }
|
||||
.cm-s-one-dark .cm-link { color: #c678dd; }
|
||||
.cm-s-one-dark .cm-header { color: #61afef; }
|
||||
.cm-s-one-dark .cm-quote { color: #98c379; }
|
||||
|
||||
/* ====================================================================
|
||||
* Mermaid/图表 SVG 暗色适配
|
||||
* ==================================================================== */
|
||||
figure[data-role] svg rect[fill='#ffffff'] { fill: var(--editorBgColor); }
|
||||
figure[data-role] svg rect[stroke='#000000'] { stroke: var(--editorColor); }
|
||||
figure[data-role] svg text[fill='#000000'] { fill: var(--editorColor); }
|
||||
figure[data-role] svg path[stroke='#000000'] { stroke: var(--editorColor); }
|
||||
figure[data-role] svg path[fill='#ffffff'] { fill: var(--editorBgColor); }
|
||||
figure[data-role] svg path[fill='#000000'] { fill: var(--editorColor); }
|
||||
figure[data-role] svg use[fill='black'] { fill: var(--editorColor); }
|
||||
figure[data-role] svg use[fill='#000000'] { fill: var(--editorColor); }
|
||||
|
||||
```
|
||||
@@ -0,0 +1,64 @@
|
||||
# 正则表达式
|
||||
|
||||
正则表达式是一种用于匹配和操作文本的强大工具,用于描述要匹配的文本模式。
|
||||
|
||||
## 匹配字符
|
||||
|
||||
### 普通匹配字符
|
||||
|
||||
| 字符 | 描述 |
|
||||
| :------- | :----------------------------------------------------------- |
|
||||
| `[xyz]` | 匹配 `[xyz]` 中列出的所有字符。例如 `[xyz]`,能够匹配目标的所有 x、y、z 字符。 |
|
||||
| `[^xyz]` | 匹配 `[^xyz]` 中列出的字符外的所有字符。例如 `[^xyz]`,能够匹配目标的除去 x、y、z 外的所有字符。 |
|
||||
| `[A-Z]` | `[A-Z]` 表示一个区间,匹配所有大写字母;`[a-z]` 匹配所有小写字母;`[0-9]` 匹配所有数字。 |
|
||||
| `[\s\S]` | 匹配所有字符(包括换行符)。`\s` 匹配所有空白符(包括换行),`\S` 匹配所有非空白符(不包括换行),两者合并即可匹配任意字符。 |
|
||||
| `.` | 匹配除换行符 `\n`、`\r` 之外的任何单个字符,等价于 `[^\n\r]`。 |
|
||||
| `\w` | 匹配字母、数字、下划线,等价于 `[A-Za-z0-9_]`。 |
|
||||
| `\d` | 匹配任意一个阿拉伯数字(0 到 9),等价于 `[0-9]`。 |
|
||||
|
||||
### 非打印字符
|
||||
|
||||
| 字符 | 描述 |
|
||||
| :---- | :----------------------------------------------------------- |
|
||||
| `\cx` | 匹配由 x 指明的控制字符。例如,`\cM` 匹配一个 Control-M 或回车符。x 的值必须为 A-Z 或 a-z 之一,否则将 c 视为一个原义的 'c' 字符。 |
|
||||
| `\f` | 匹配一个换页符,等价于 `\x0c` 和 `\cL`。 |
|
||||
| `\n` | 匹配一个换行符,等价于 `\x0a` 和 `\cJ`。 |
|
||||
| `\r` | 匹配一个回车符,等价于 `\x0d` 和 `\cM`。 |
|
||||
| `\s` | 匹配任何空白字符,包括空格、制表符、换页符等,等价于 `[ \f\n\r\t\v]`。注意 Unicode 正则表达式会匹配全角空格符。 |
|
||||
| `\S` | 匹配任何非空白字符,等价于 `[^ \f\n\r\t\v]`。 |
|
||||
| `\t` | 匹配一个制表符,等价于 `\x09` 和 `\cI`。 |
|
||||
| `\v` | 匹配一个垂直制表符,等价于 `\x0b` 和 `\cK`。 |
|
||||
|
||||
### 特殊字符
|
||||
|
||||
| 特殊字符 | 描述 |
|
||||
| :------- | :----------------------------------------------------------- |
|
||||
| `$` | 匹配输入字符串的结尾位置。如果设置了 RegExp 对象的 Multiline 属性,则 `$` 也匹配 `\n` 或 `\r`。要匹配 `$` 字符本身,请使用 `\$`。 |
|
||||
| `( )` | 标记一个子表达式的开始和结束位置。子表达式可以获取供以后使用。要匹配这些字符,请使用 `\(` 和 `\)`。 |
|
||||
| `*` | 匹配前面的子表达式零次或多次。要匹配 `*` 字符本身,请使用 `\*`。 |
|
||||
| `+` | 匹配前面的子表达式一次或多次。要匹配 `+` 字符本身,请使用 `\+`。 |
|
||||
| `.` | 匹配除换行符 `\n` 之外的任何单字符。要匹配 `.` 本身,请使用 `\.`。 |
|
||||
| `[` | 标记一个中括号表达式的开始。要匹配 `[`,请使用 `\[`。 |
|
||||
| `?` | 匹配前面的子表达式零次或一次,等价于 `{0,1}`。要匹配 `?` 字符本身,请使用 `\?`。例如,`do(es)?` 可以匹配 "do" 和 "does",但不能匹配 "dog"。 |
|
||||
| `\` | 将下一个字符标记为特殊字符、原义字符、向后引用或八进制转义符。例如,`\\` 匹配 `\`。 |
|
||||
| `^` | 匹配输入字符串的开始位置。在方括号表达式中使用时,表示不接受方括号内的字符集合(取反)。要匹配 `^` 字符本身,请使用 `\^`。 |
|
||||
| `{` | 标记限定符表达式的开始。要匹配 `{`,请使用 `\{`。 |
|
||||
| `|` | 指明两项之间的一个选择。要匹配 `|`,请使用 `\|`。 |
|
||||
|
||||
### 限定符
|
||||
|
||||
| 字符 | 描述 |
|
||||
| :------ | :----------------------------------------------------------- |
|
||||
| `{n}` | n 是一个非负整数,匹配确定的 n 次。例如,`o{2}` 不能匹配 "Bob" 中的 o,但是能匹配 "food" 中的两个 o。 |
|
||||
| `{n,}` | n 是一个非负整数,至少匹配 n 次。例如,`o{2,}` 不能匹配 "Bob" 中的 o,但能匹配 "foooood" 中的所有 o。`o{1,}` 等价于 `o+`,`o{0,}` 等价于 `o*`。 |
|
||||
| `{n,m}` | m 和 n 均为非负整数,其中 n <= m。最少匹配 n 次且最多匹配 m 次。例如,`o{1,3}` 将匹配 "fooooood" 中的前三个 o。`o{0,1}` 等价于 `o?`。 |
|
||||
|
||||
## 常用正则表达式
|
||||
|
||||
| 正则表达式 | 匹配内容 |
|
||||
| ------------------------------------------------ | ------------ |
|
||||
| `[\u4e00-\u9fa5]` | 中文字符 |
|
||||
| `^1[3-9]\d{9}$` | 国内手机号 |
|
||||
| `[1-9]\d{5}(?!\d)` | 国内邮政编码 |
|
||||
| `^(\d{6})(\d{4})(\d{2})(\d{2})(\d{3})([0-9]|X)$` | 18位身份证号 |
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
[Typora onelight 主题](https://github.com/caolib/typora-onelight-theme?tab=readme-ov-file#4%E5%85%B3%E4%BA%8E%E5%AD%97%E4%BD%93)
|
||||
|
||||
[Typora自定义主题](https://theme.typora.io/doc/zh/Write-Custom-Theme/)
|
||||
|
||||
> 通过自定义的CSS变量来实现
|
||||
|
||||
[CSDN增强助手](https://chromewebstore.google.com/detail/csdn%E5%A2%9E%E5%BC%BA%E5%8A%A9%E6%89%8B/fpgaealcfgjbodghojppfmohfcoajled)
|
||||
|
||||
> 能够智能屏蔽CSDN搜索结果、屏蔽其广告、自动展开代码
|
||||
@@ -0,0 +1,32 @@
|
||||
# TypeScript / JavaScript 练习区
|
||||
|
||||
本目录是 TS/JS 的学习 / 练手代码。与 Go 那边共用一个 module 不同,这里**每个实验是独立的 npm 项目**(各自的 `package.json`、lock 文件)。
|
||||
|
||||
## 约定
|
||||
|
||||
- **每个叶子目录 = 一个独立实验**:自带 `package.json`,依赖自行管理。
|
||||
- 多个实验并存互不冲突,框架和版本可以各不相同(一个 Astro 4、一个 Astro 5 都行)。
|
||||
- `node_modules/`、构建产物(`dist/`、`.astro/` 等)已在根 `.gitignore` 忽略,不会提交。
|
||||
|
||||
## 主题目录
|
||||
|
||||
| 目录 | 放什么 |
|
||||
|---|---|
|
||||
| `basics/` | 语法基础、类型系统、ES 新特性 |
|
||||
| `web/` | 前端框架:Astro、Vite 等 |
|
||||
| `node/` | Node.js 后端练习(http、fs、CLI…) |
|
||||
| `testing/` | 测试机制(Vitest、Playwright…) |
|
||||
| `project/` | 完整小项目(多文件) |
|
||||
|
||||
> 主题目录可随时增删。写新实验时挑最贴的主题,在里面建一个新子目录即可。
|
||||
|
||||
## 新建一个 Astro 实验
|
||||
|
||||
```bash
|
||||
cd ts/web/astro
|
||||
npm create astro@latest my-lab -- --template basics --no-git --install
|
||||
cd my-lab
|
||||
npm run dev
|
||||
```
|
||||
|
||||
> `my-lab` 换成实验名;想完全交互式地选模板/TypeScript 严格度,去掉 `--` 后面的参数即可。
|
||||
@@ -0,0 +1,31 @@
|
||||
# Polaris(北极星)
|
||||
|
||||
个人导航网站(Personal Navigation / 个人主页 + 导航站)。
|
||||
|
||||
## 项目背景(记录于开发启动前)
|
||||
|
||||
- **项目名**:polaris
|
||||
- **定位**:个人导航网站 —— 作为个人门户,聚合常用链接 / 站点导航 / 个人介绍等。
|
||||
- **当前状态**:设计尚未完成,仅完成并存放了 Logo(`logo.svg`)。
|
||||
- **Logo 风格**:青蓝渐变(`#00F2FE → #3B82F6`)的"极客魔法阵 + 机甲指针"罗盘图形,整体偏深色科技感。
|
||||
|
||||
## 技术选型(待定)
|
||||
|
||||
- **候选主力框架**:Astro(静态优先、岛屿架构,适合导航站这类以内容/链接为主的站点)。
|
||||
- 其余(样式方案、部署平台、数据源等)**尚未确定**。
|
||||
|
||||
## 待办 / 未决事项
|
||||
|
||||
- [ ] 完成整体设计(页面结构、视觉风格、配色是否沿用 Logo 的深色青蓝科技风)
|
||||
- [ ] 确定技术栈细节:Astro 版本、样式方案(如 Tailwind / UnoCSS)、是否引入 UI 组件
|
||||
- [ ] 确定内容与数据组织:导航链接的存放方式(Markdown / JSON / 内容集合)
|
||||
- [ ] 确定部署方式(如 Vercel / Netlify / Cloudflare Pages / 自有服务器)
|
||||
- [ ] 搭建项目骨架并接入现有 Logo
|
||||
|
||||
## 目录
|
||||
|
||||
```
|
||||
polaris/
|
||||
├── logo.svg # 项目 Logo(唯一现有资产)
|
||||
└── README.md # 本项目说明(本文件)
|
||||
```
|
||||
@@ -0,0 +1,111 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="100%" height="100%">
|
||||
<!-- 背景透明 -->
|
||||
|
||||
<defs>
|
||||
<!-- 渐变:高亮发光青蓝 到 科技蓝 -->
|
||||
<linearGradient id="magicCyan" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" stop-color="#00F2FE" />
|
||||
<stop offset="100%" stop-color="#3B82F6" />
|
||||
</linearGradient>
|
||||
|
||||
<!-- 渐变:深邃蓝 (指针反向切面与暗部) -->
|
||||
<linearGradient id="magicDark" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" stop-color="#1E293B" />
|
||||
<stop offset="100%" stop-color="#0F172A" />
|
||||
</linearGradient>
|
||||
|
||||
<!-- 霓虹发光 Glow 滤镜 -->
|
||||
<filter id="magicGlow" x="-20%" y="-20%" width="140%" height="140%">
|
||||
<feGaussianBlur stdDeviation="4" result="blur" />
|
||||
<feComposite in="SourceGraphic" in2="blur" operator="over" />
|
||||
</filter>
|
||||
</defs>
|
||||
|
||||
<!-- ==================== 丰富高精度的极客魔法阵 (Geek Magic Array) ==================== -->
|
||||
|
||||
<!-- 1. 最外层:外边界细环与四角法术引线节点 -->
|
||||
<circle cx="256" cy="256" r="242" fill="none" stroke="#00F2FE" stroke-width="1.5" stroke-opacity="0.5" />
|
||||
<circle cx="256" cy="256" r="236" fill="none" stroke="#3B82F6" stroke-width="2.5" stroke-opacity="0.8" />
|
||||
|
||||
<!-- 外圈四极极客指示顶点 -->
|
||||
<path d="M 256 8 L 266 26 L 246 26 Z" fill="#00F2FE" filter="url(#magicGlow)" />
|
||||
<path d="M 256 504 L 266 486 L 246 486 Z" fill="#2563EB" opacity="0.6" />
|
||||
<path d="M 8 256 L 26 246 L 26 266 Z" fill="#2563EB" opacity="0.6" />
|
||||
<path d="M 504 256 L 486 246 L 486 266 Z" fill="#2563EB" opacity="0.6" />
|
||||
|
||||
<!-- 2. 精密仪表与符文刻度带 (外环与内环间) -->
|
||||
<circle cx="256" cy="256" r="226" fill="none" stroke="#00F2FE" stroke-width="6" stroke-dasharray="2 6" stroke-opacity="0.75" />
|
||||
<circle cx="256" cy="256" r="216" fill="none" stroke="#3B82F6" stroke-width="1.5" stroke-opacity="0.4" />
|
||||
|
||||
<!-- 精算刻度环:6个长粗虚线的中心点完全对齐正六边形与角点的 60° 几何角 -->
|
||||
<circle cx="256" cy="256" r="210" fill="none" stroke="#00F2FE" stroke-width="4.5"
|
||||
stroke-dasharray="25 8 6 15.977872" stroke-dashoffset="-12.5" stroke-opacity="0.85" filter="url(#magicGlow)" />
|
||||
|
||||
<!-- 3. 魔法阵三重嵌套多边形结界 (正三角形 + 正六边形 + 12角星线拓扑) -->
|
||||
<g fill="none" stroke="#3B82F6" stroke-width="1.5" stroke-opacity="0.5">
|
||||
<!-- 正六边形 A (顶角精确落于 r=210 粗虚线长段) -->
|
||||
<polygon points="256,46 437.86,151 437.86,361 256,466 74.14,361 74.14,151" stroke="#00F2FE" stroke-width="1.8" stroke-opacity="0.7" />
|
||||
<!-- 正六边形 B (旋转30度) -->
|
||||
<polygon points="361,74.14 466,256 361,437.86 151,437.86 46,256 151,74.14" />
|
||||
<!-- 正三角形 C (向上) -->
|
||||
<polygon points="256,46 437.86,361 74.14,361" stroke="#00F2FE" stroke-width="1" stroke-opacity="0.4" />
|
||||
<!-- 正三角形 D (向下) -->
|
||||
<polygon points="256,466 74.14,151 437.86,151" stroke="#00F2FE" stroke-width="1" stroke-opacity="0.4" />
|
||||
</g>
|
||||
|
||||
<!-- 4. 六方位魔法法阵节点圈 (在多边形顶点上的极客元素) -->
|
||||
<g fill="#0F172A" stroke="#00F2FE" stroke-width="2" opacity="0.95">
|
||||
<circle cx="256" cy="46" r="10" />
|
||||
<circle cx="437.86" cy="151" r="10" />
|
||||
<circle cx="437.86" cy="361" r="10" />
|
||||
<circle cx="256" cy="466" r="10" />
|
||||
<circle cx="74.14" cy="361" r="10" />
|
||||
<circle cx="74.14" cy="151" r="10" />
|
||||
</g>
|
||||
|
||||
<!-- 节点内发光核 -->
|
||||
<g fill="#00F2FE" filter="url(#magicGlow)">
|
||||
<circle cx="256" cy="46" r="4" />
|
||||
<circle cx="437.86" cy="151" r="4" />
|
||||
<circle cx="437.86" cy="361" r="4" />
|
||||
<circle cx="256" cy="466" r="4" />
|
||||
<circle cx="74.14" cy="361" r="4" />
|
||||
<circle cx="74.14" cy="151" r="4" />
|
||||
</g>
|
||||
|
||||
<!-- 5. 中阶魔法阵环与细分刻度 -->
|
||||
<circle cx="256" cy="256" r="186" fill="none" stroke="#00F2FE" stroke-width="2" stroke-opacity="0.8" filter="url(#magicGlow)" />
|
||||
<circle cx="256" cy="256" r="176" fill="none" stroke="#2563EB" stroke-width="3" stroke-dasharray="12 6 4 6" stroke-opacity="0.6" />
|
||||
<circle cx="256" cy="256" r="166" fill="none" stroke="#00F2FE" stroke-width="1" stroke-dasharray="2 4" stroke-opacity="0.5" />
|
||||
|
||||
<!-- 6. 魔法阵放射状流向线 (16等分星射线) -->
|
||||
<g stroke="#00F2FE" stroke-width="1" stroke-opacity="0.2">
|
||||
<line x1="256" y1="26" x2="256" y2="486" />
|
||||
<line x1="26" y1="256" x2="486" y2="256" />
|
||||
<line x1="93" y1="93" x2="419" y2="419" />
|
||||
<line x1="419" y1="93" x2="93" y2="419" />
|
||||
</g>
|
||||
|
||||
<!-- ==================== 中心:硬核极客机甲指针 (中间区域干净无符号) ==================== -->
|
||||
|
||||
<g transform="rotate(28, 256, 256)">
|
||||
<!-- 北向主指针(立体几何亮蓝切面) -->
|
||||
<path d="M 256 70 L 306 226 L 256 256 Z" fill="url(#magicCyan)" filter="url(#magicGlow)" />
|
||||
<!-- 北向副切面(深蓝阴影) -->
|
||||
<path d="M 256 70 L 206 226 L 256 256 Z" fill="#1D4ED8" />
|
||||
|
||||
<!-- 指针侧翼机甲组件 -->
|
||||
<path d="M 312 236 L 332 266 L 282 256 Z" fill="#00F2FE" opacity="0.7" />
|
||||
<path d="M 200 236 L 180 266 L 230 256 Z" fill="#2563EB" opacity="0.7" />
|
||||
|
||||
<!-- 南向反向指针(深色金属质感) -->
|
||||
<path d="M 256 442 L 220 286 L 256 256 Z" fill="url(#magicDark)" />
|
||||
<path d="M 256 442 L 292 286 L 256 256 Z" fill="#334155" />
|
||||
</g>
|
||||
|
||||
<!-- 中心核心:反应堆式同心轴 -->
|
||||
<circle cx="256" cy="256" r="32" fill="#0F172A" stroke="#00F2FE" stroke-width="3" />
|
||||
<circle cx="256" cy="256" r="18" fill="none" stroke="#3B82F6" stroke-width="2" stroke-dasharray="6 3" />
|
||||
<circle cx="256" cy="256" r="8" fill="#00F2FE" filter="url(#magicGlow)" />
|
||||
<circle cx="256" cy="256" r="3" fill="#FFFFFF" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.6 KiB |
Reference in New Issue
Block a user