vault backup: 2026-04-28 12:34:34

This commit is contained in:
2026-04-28 12:34:34 +08:00
parent 8b07798991
commit 50e982c522
9 changed files with 1490 additions and 392 deletions
+141 -237
View File
@@ -28,13 +28,10 @@ B. Logger + Recovery
C. Recovery + JWT Auth
D. Logger + RateLimit
<details><summary>点击查看答案</summary>
**答案:B**。`gin.Default()` = `gin.New()` + `Use(Logger())` + `Use(Recovery())`。
> 来源:`[[GIN/1-gin-architecture]]` §4
</details>
> [!note]- Q1 答案
> **答案:B**。`gin.Default()` = `gin.New()` + `Use(Logger())` + `Use(Recovery())`。
>
> > 来源:`[[GIN/1-gin-architecture]]` §4
---
@@ -45,13 +42,10 @@ B. `http.Handler`
C. `http.ResponseWriter`
D. `http.ServeMux`
<details><summary>点击查看答案</summary>
**答案:B**。`ServeHTTP(ResponseWriter, *Request)` 签名匹配。
> 来源:`[[GIN/1-gin-architecture/engine-handler]]` §1
</details>
> [!note]- Q2 答案
> **答案:B**。`ServeHTTP(ResponseWriter, *Request)` 签名匹配。
>
> > 来源:`[[GIN/1-gin-architecture/engine-handler]]` §1
---
@@ -62,13 +56,10 @@ B. Radix Tree(基数树/压缩前缀树)
C. AVL Tree(平衡二叉树)
D. Trie(朴素前缀树,未压缩)
<details><summary>点击查看答案</summary>
**答案:B**。Radix Tree 通过前缀共享实现 O(d) 匹配,d 为 URL 深度。
> 来源:`[[GIN/2-routing]]` §3;`[[GIN/2-routing-complexity-comparison]]`
</details>
> [!note]- Q3 答案
> **答案:B**。Radix Tree 通过前缀共享实现 O(d) 匹配,d 为 URL 深度。
>
> > 来源:`[[GIN/2-routing]]` §3;`[[GIN/2-routing-complexity-comparison]]`
---
@@ -79,13 +70,10 @@ B. `/users/:id` — 静态优先于动态
C. `/users/*path` — 通配符是兜底规则
D. 取决于注册顺序
<details><summary>点击查看答案</summary>
**答案:B**。优先级:静态 > 动态参数 > 通配符,与注册顺序无关。
> 来源:`[[GIN/2-routing]]` §4
</details>
> [!note]- Q4 答案
> **答案:B**。优先级:静态 > 动态参数 > 通配符,与注册顺序无关。
>
> > 来源:`[[GIN/2-routing]]` §4
---
@@ -104,13 +92,10 @@ B. `/api/v1/users/` (`:id` 被忽略,因为放在 Group path 里)
C. `/api/v1/users/:id` — 正确
D. `/:id` — 只取最后一段
<details><summary>点击查看答案</summary>
**答案:C**。`basePath` 逐层累加:`"" → "/api" → "/api/v1" → "/api/v1/users/:id"`。
> 来源:`[[GIN/2-routing]]` §5
</details>
> [!note]- Q5 答案
> **答案:C**。`basePath` 逐层累加:`"" → "/api" → "/api/v1" → "/api/v1/users/:id"`。
>
> > 来源:`[[GIN/2-routing]]` §5
---
@@ -121,13 +106,10 @@ B. Gin: ~4 步,ServeMux: ~10000 次比较
C. Gin: ~10000 步,ServeMux: ~4 步
D. Gin: ~4 步,ServeMux: ~4 步
<details><summary>点击查看答案</summary>
**答案:B**。Gin 复杂度 O(d),与路由总数 R 无关;ServeMux O(R×L)。
> 来源:`[[GIN/2-routing-complexity-comparison]]` §3
</details>
> [!note]- Q6 答案
> **答案:B**。Gin 复杂度 O(d),与路由总数 R 无关;ServeMux O(R×L)。
>
> > 来源:`[[GIN/2-routing-complexity-comparison]]` §3
---
@@ -144,13 +126,10 @@ B. A-before → B-before → handler → A-after → B-after
C. A-before → handler → A-after → B-before → B-after
D. handler → A-before → B-before → A-after → B-after
<details><summary>点击查看答案</summary>
**答案:A**。前置按注册顺序,后置按逆序——栈式行为。
> 来源:`[[GIN/3-middleware]]` §2
</details>
> [!note]- Q7 答案
> **答案:A**。前置按注册顺序,后置按逆序——栈式行为。
>
> > 来源:`[[GIN/3-middleware]]` §2
---
@@ -161,13 +140,10 @@ B. `func(http.ResponseWriter, *http.Request)`
C. `func(http.Handler) http.Handler`
D. `func(*http.Server) http.Handler`
<details><summary>点击查看答案</summary>
**答案:C**。这是装饰器模式,层层嵌套包装。
> 来源:`[[GIN/gin-vs-std]]` §1
</details>
> [!note]- Q8 答案
> **答案:C**。这是装饰器模式,层层嵌套包装。
>
> > 来源:`[[GIN/gin-vs-std]]` §1
---
@@ -178,13 +154,10 @@ B. 后续中间件跳过,handler 执行
C. 整个链(包括后续中间件、handler、A 的所有剩余代码)都不再执行
D. 只有同一路由的中间件被跳过,其他路由不受影响
<details><summary>点击查看答案</summary>
**答案:C**。`c.Abort()` 将 index 设为链长度,`c.Next()` 循环条件立即不满足。Abort 后必须紧跟 return。
> 来源:`[[GIN/middleware-abort]]`
</details>
> [!note]- Q9 答案
> **答案:C**。`c.Abort()` 将 index 设为链长度,`c.Next()` 循环条件立即不满足。Abort 后必须紧跟 return。
>
> > 来源:`[[GIN/middleware-abort]]`
---
@@ -195,13 +168,10 @@ B. 先用 `c.Copy()` 创建副本,在 goroutine 中使用副本
C. 把 `c` 保存到全局变量,在 goroutine 中读取
D. 使用 `context.WithCancel` 取消原 context
<details><summary>点击查看答案</summary>
**答案:B**。`c.Copy()` 创建独立副本,goroutine 中只能读不能写响应。
> 来源:`[[GIN/3-middleware]]` §5
</details>
> [!note]- Q10 答案
> **答案:B**。`c.Copy()` 创建独立副本,goroutine 中只能读不能写响应。
>
> > 来源:`[[GIN/3-middleware]]` §5
---
@@ -212,13 +182,10 @@ B. 可能读到任意并发请求正在使用的数据,造成数据错乱
C. Go 运行时会 panic,因为存在数据竞争
D. Context 会自动深拷贝,所以没问题
<details><summary>点击查看答案</summary>
**答案:B**。sync.Pool 复用对象,字段被 reset 覆盖,全局引用指向的是被新请求改写后的同一个内存地址。
> 来源:`[[GIN/context-pool]]` §2
</details>
> [!note]- Q11 答案
> **答案:B**。sync.Pool 复用对象,字段被 reset 覆盖,全局引用指向的是被新请求改写后的同一个内存地址。
>
> > 来源:`[[GIN/context-pool]]` §2
---
@@ -229,13 +196,10 @@ B. `-1` — 表示还未开始执行,第一次 `c.Next()` 走到 index+1=0
C. `-1` — 表示无效值,需要用 -1 做判断
D. `nil` — 空表示未初始化
<details><summary>点击查看答案</summary>
**答案:B**。`-1` 表示还未开始,`c.Next()` 先 `index++` 到 0,再执行 `handlers[0]`。
> 来源:`[[GIN/4-context-lifecycle]]` §3
</details>
> [!note]- Q12 答案
> **答案:B**。`-1` 表示还未开始,`c.Next()` 先 `index++` 到 0,再执行 `handlers[0]`。
>
> > 来源:`[[GIN/4-context-lifecycle]]` §3
---
@@ -246,13 +210,10 @@ B. 没有,应在 `http.Server` 层配置 `ReadTimeout`/`WriteTimeout`
C. 没有,但可用 `c.WithTimeout()` 设置
D. 有,在 `gin.Default()` 内部已经设置了
<details><summary>点击查看答案</summary>
**答案:B**。Gin 本身不内置超时,需在 `http.Server{ReadTimeout: ...}` 配置。
> 来源:`[[GIN/4-context-lifecycle]]` §7
</details>
> [!note]- Q13 答案
> **答案:B**。Gin 本身不内置超时,需在 `http.Server{ReadTimeout: ...}` 配置。
>
> > 来源:`[[GIN/4-context-lifecycle]]` §7
---
@@ -263,13 +224,10 @@ B. query string → form data → JSON
C. JSON → form data → query string
D. 根据 Content-Type 头直接判定,不按顺序
<details><summary>点击查看答案</summary>
**答案:C**。先试 JSON(检查 Content-Type),再试 form,最后试 query。
> 来源:`[[GIN/5-binding-validation]]` §1
</details>
> [!note]- Q14 答案
> **答案:C**。先试 JSON(检查 Content-Type),再试 form,最后试 query。
>
> > 来源:`[[GIN/5-binding-validation]]` §1
---
@@ -280,13 +238,10 @@ B. 静默忽略,值为零值
C. 抛出 panic
D. 打印警告日志但仍继续
<details><summary>点击查看答案</summary>
**答案:B**。底层用 `encoding/json.Unmarshal`,对多余字段静默丢弃。
> 来源:`[[GIN/unknown-fields]]` §1
</details>
> [!note]- Q15 答案
> **答案:B**。底层用 `encoding/json.Unmarshal`,对多余字段静默丢弃。
>
> > 来源:`[[GIN/unknown-fields]]` §1
---
@@ -294,97 +249,73 @@ D. 打印警告日志但仍继续
### Q16 【路由优先级】Gin 路由匹配的优先级从高到低依次是:________ > ________ > ________。
<details><summary>点击查看答案</summary>
**答案:静态字符串 > 动态参数 (`:param`) > 通配符 (`*rest`)**
> 来源:`[[GIN/2-routing]]` §4
</details>
> [!note]- Q16 答案
> **答案:静态字符串 > 动态参数 (`:param`) > 通配符 (`*rest`)**
>
> > 来源:`[[GIN/2-routing]]` §4
---
### Q17 【Engine 结构】`*gin.Engine` 嵌入了 `RouterGroup`,这意味着 Engine 本身就是一颗最大的 RouterGroup,可以直接调用 `.GET()`、`.Use()` 等方法。Engine 中还包含一个 `trees` 字段,其类型是 `methodTrees`(本质是 `[*tree]`),它的作用是:_________________________。
<details><summary>点击查看答案</summary>
**答案:每种 HTTP 方法维护一棵独立的 Radix Tree(例如 GET 一棵、POST 一棵)**
> 来源:`[[GIN/1-gin-architecture]]` §1
</details>
> [!note]- Q17 答案
> **答案:每种 HTTP 方法维护一棵独立的 Radix Tree(例如 GET 一棵、POST 一棵)**
>
> > 来源:`[[GIN/1-gin-architecture]]` §1
---
### Q18 【中间件作用域】Gin 中间件的三级作用域分别是:________、________、________。执行顺序为:________ → ________ → ________ → handler。
<details><summary>点击查看答案</summary>
**答案:全局(Engine 级) / 分组(RouterGroup 级) / 路由(单条路由);全局 → 分组 → 路由 → handler**
> 来源:`[[GIN/3-middleware]]` §2
</details>
> [!note]- Q18 答案
> **答案:全局(Engine 级) / 分组(RouterGroup 级) / 路由(单条路由);全局 → 分组 → 路由 → handler**
>
> > 来源:`[[GIN/3-middleware]]` §2
---
### Q19 【中间件设计模式】Go 标准库 `net/http` 中间件使用 ________ 模式,而 Gin 中间件使用 ________ 模式。
<details><summary>点击查看答案</summary>
**答案:装饰器(Decorator)/ 责任链(Chain of Responsibility)**
> 来源:`[[GIN/gin-vs-std]]` §1
</details>
> [!note]- Q19 答案
> **答案:装饰器(Decorator)/ 责任链(Chain of Responsibility)**
>
> > 来源:`[[GIN/gin-vs-std]]` §1
---
### Q20 【c.Abort() 系列方法】Gin 提供了三种 Abort 相关方法:`c.Abort()`、`c.AbortWithStatus(code)`、`_______________`(Abort 同时写入 JSON 响应体)。
<details><summary>点击查看答案</summary>
**答案:`c.AbortWithStatusJSON(code, json)`**
> 来源:`[[GIN/middleware-abort]]` §4
</details>
> [!note]- Q20 答案
> **答案:`c.AbortWithStatusJSON(code, json)`**
>
> > 来源:`[[GIN/middleware-abort]]` §4
---
### Q21 【c.Copy() 限制】在通过 `c.Copy()` 创建的 goroutine 副本中,________(能/不能)调用 `c.JSON()` 写入响应,但可以读取 `c.Request` 和 `c.Keys`。
<details><summary>点击查看答案</summary>
**答案:不能**。`c.Writer` 无法复制,Copy 出的 goroutine 只能读不能写。
> 来源:`[[GIN/3-middleware]]` §5
</details>
> [!note]- Q21 答案
> **答案:不能**。`c.Writer` 无法复制,Copy 出的 goroutine 只能读不能写。
>
> > 来源:`[[GIN/3-middleware]]` §5
---
### Q22 【绑定方法速记】Gin 提供了多种绑定方法:`c.ShouldBindJSON` 绑定 JSON body,`_______________` 绑定 URL 查询参数,`c.ShouldBindUri` 绑定 URI 路径参数,`c.ShouldBindHeader` 绑定 HTTP 请求头。
<details><summary>点击查看答案</summary>
**答案:`c.ShouldBindQuery`**
> 来源:`[[GIN/5-binding-validation]]` §1
</details>
> [!note]- Q22 答案
> **答案:`c.ShouldBindQuery`**
>
> > 来源:`[[GIN/5-binding-validation]]` §1
---
### Q23 【ShouldBindBodyWith】标准库的 `io.ReadCloser` 类型的 body 只能读取一次。当需要在同一个 handler 中对不同结构体多次解析 body 时,应使用 `_______________` 来缓存 body,避免二次读取报错。
<details><summary>点击查看答案</summary>
**答案:`c.ShouldBindBodyWith(&obj, binding.JSON)`**
> 来源:`[[GIN/5-binding-validation]]` §9
</details>
> [!note]- Q23 答案
> **答案:`c.ShouldBindBodyWith(&obj, binding.JSON)`**
>
> > 来源:`[[GIN/5-binding-validation]]` §9
---
@@ -413,15 +344,12 @@ func cors() gin.HandlerFunc {
}
```
<details><summary>点击查看答案</summary>
**答案:`return`**
CORS 中间件中 OPTIONS 预检请求处理后必须 `return`,否则会继续执行 `c.Next()` 并可能触发下游 handler。
> 来源:`[[GIN/3-middleware]]` §4
</details>
> [!note]- Q24 答案
> **答案:`return`**
>
> CORS 中间件中 OPTIONS 预检请求处理后必须 `return`,否则会继续执行 `c.Next()` 并可能触发下游 handler。
>
> > 来源:`[[GIN/3-middleware]]` §4
---
@@ -452,15 +380,12 @@ func jwtAuth() gin.HandlerFunc {
}
```
<details><summary>点击查看答案</summary>
**答案:两行 `return`,最后一行 `c.Next()`**
核心规则:`c.Abort()` 之后必须紧跟 `return`;认证成功后调用 `c.Next()` 推进链。
> 来源:`[[GIN/3-middleware]]` §4
</details>
> [!note]- Q25 答案
> **答案:两行 `return`,最后一行 `c.Next()`**
>
> 核心规则:`c.Abort()` 之后必须紧跟 `return`;认证成功后调用 `c.Next()` 推进链。
>
> > 来源:`[[GIN/3-middleware]]` §4
---
@@ -497,13 +422,10 @@ func safeAsyncProcessor() gin.HandlerFunc {
}
```
<details><summary>查看要点</summary>
关键改动:① `c.Copy()` 创建副本;② goroutine 中使用 `copy` 而非 `c`。
> 来源:`[[GIN/3-middleware]]` §5
</details>
> [!note]- Q26 答案要点
> **关键改动**:① `c.Copy()` 创建副本;② goroutine 中使用 `copy` 而非 `c`。
>
> > 来源:`[[GIN/3-middleware]]` §5
---
@@ -531,17 +453,14 @@ func (c *Context) reset(w http.ResponseWriter) {
4. `c.errors = ______________`
5. `c.Keys = ______________`
<details><summary>点击查看答案</summary>
1. `c.Params[:0]`
2. `nil`
3. `-1`
4. `c.errors[:0]`
5. `nil`
> 来源:`[[GIN/4-context-lifecycle]]` §3
</details>
> [!note]- Q27 答案
> 1. `c.Params = c.Params[:0]`
> 2. `nil`
> 3. `-1`
> 4. `c.errors = c.errors[:0]`
> 5. `nil`
>
> > 来源:`[[GIN/4-context-lifecycle]]` §3
---
@@ -559,15 +478,12 @@ func init() {
}
```
<details><summary>点击查看答案</summary>
**答案:`^[a-zA-Z0-9_]+$`**
完整正则确保只允许字母、数字和下划线。
> 来源:`[[GIN/5-binding-validation]]` §6
</details>
> [!note]- Q28 答案
> **答案:`^[a-zA-Z0-9_]+$`**
>
> 完整正则确保只允许字母、数字和下划线。
>
> > 来源:`[[GIN/5-binding-validation]]` §6
---
@@ -592,13 +508,10 @@ srv := &http.Server{
_______ // ← 补全第三行的启动调用
```
<details><summary>点击查看答案</summary>
三个空依次为:`r`、`r`、`srv.ListenAndServe()`
> 来源:`[[GIN/engine-handler]]` §2
</details>
> [!note]- Q29 答案
> **三个空依次为**:`r`、`r`、`srv.ListenAndServe()`
>
> > 来源:`[[GIN/engine-handler]]` §2
---
@@ -623,13 +536,10 @@ func strictJSONHandler(c *gin.Context) {
}
```
<details><summary>点击查看答案</summary>
两个空依次为:`DisallowUnknownFields()`、`return`
> 来源:`[[GIN/unknown-fields]]` §3
</details>
> [!note]- Q30 答案
> **两个空依次为**:`DisallowUnknownFields()`、`return`
>
> > 来源:`[[GIN/unknown-fields]]` §3
---
@@ -652,16 +562,13 @@ func backgroundWorker() {
请问这段代码可能引发什么问题?应该如何修复?
<details><summary>点击查看答案</summary>
**问题:** `savedUserID = c.GetString("user_id")` 虽然是拷贝值,但如果改为 `globalC = c`(保存 Context 引用),则会导致数据错乱——因为 Context 被 sync.Pool 复用,另一个请求的 reset() 会清空该对象的 Keys。即使拷贝值,在全局变量中也会有并发写的竞态。
**修复方案:**
1. 不要使用全局变量保存请求相关数据
2. 如果确实需要异步使用数据,用 `c.Copy()` 创建副本,或在 goroutine 中只传基本类型值
3. 参考:`[[GIN/context-pool]]` §2 和 `[[GIN/context-pool-safety]]`
</details>
> [!note]- Q31 答案
> **问题:** `savedUserID = c.GetString("user_id")` 虽然是拷贝值,但如果改为 `globalC = c`(保存 Context 引用),则会导致数据错乱——因为 Context 被 sync.Pool 复用,另一个请求的 reset() 会清空该对象的 Keys。即使拷贝值,在全局变量中也会有并发写的竞态。
>
> **修复方案:**
> 1. 不要使用全局变量保存请求相关数据
> 2. 如果确实需要异步使用数据,用 `c.Copy()` 创建副本,或在 goroutine 中只传基本类型值
> 3. 参考:`[[GIN/context-pool]]` §2 和 `[[GIN/context-pool-safety]]`
---
@@ -676,13 +583,10 @@ r.GET("/users/list", listAll) // 后注册静态路由
当客户端请求 `GET /users/list` 时,会命中哪个 handler?为什么?注册顺序会影响结果吗?
<details><summary>点击查看答案</summary>
会命中 `listAll`(`/users/list`)。**注册顺序不影响匹配结果**。Gin 的 Radix Tree 按优先级决定匹配:静态路由优先级高于动态参数,无论谁先注册,`/users/list` 都是精确匹配静态字符串,必优于 `/users/:id` 的动态参数匹配。
> 来源:`[[GIN/2-routing]]` §4 — "注意:如果有两条同类型的路由,Gin 注册时会 panic——不允许重复。" 不同类型的优先级由 Radix Tree 结构保证,与注册顺序无关。
</details>
> [!note]- Q32 答案
> 会命中 `listAll`(`/users/list`)。**注册顺序不影响匹配结果**。Gin 的 Radix Tree 按优先级决定匹配:静态路由优先级高于动态参数,无论谁先注册,`/users/list` 都是精确匹配静态字符串,必优于 `/users/:id` 的动态参数匹配。
>
> > 来源:`[[GIN/2-routing]]` §4 — "注意:如果有两条同类型的路由,Gin 注册时会 panic——不允许重复。" 不同类型的优先级由 Radix Tree 结构保证,与注册顺序无关。
---