Files
2026-05-24 11:42:38 +08:00

37 KiB
Raw Permalink Blame History

tags, create time
tags create time
MySQL
Docker
Gin
JWT
Protobuf
Nginx
CORS
Exam Review
2026-05-14 10:30

金山考试 - 武汉科技大学第5次考试复习笔记_26/04/16

概述

本文档是 武汉科技大学第5次考试 的完整题目回顾与知识点解析。本次考试涵盖 8 大技术领域:Docker、MySQL、Gin 框架、Go 并发编程、JWT & Session、Protobuf、Nginx & CDN、CORS。以下内容将原始 40 道单选题和多选题按知识模块重新组织,每道题保留原题和答案,并补充教学讲解、代码示例和图表。

[!NOTE] 使用建议

  • 先快速浏览所有错题标记(作答答案 ≠ 正确答案),确定薄弱模块
  • 重点阅读每个模块的 核心概念图 和 易错点提示
  • 代码块均经过精简注释,核心逻辑一目了然

一、Docker 基础命令

核心概念图

graph LR
  A[Dockerfile] -->|docker build| B[镜像 Image]
  B -->|docker run| C[容器 Container]
  C -->|docker stop| D[已停止]
  C -->|docker pause| E[已暂停]
  B -->|docker push| F[仓库 Registry]

[!TIP] Docker 核心工作流 Dockerfile → 构建镜像 → 运行容器 → 推送仓库,这是最完整的开发到生产流程。

原题回顾

第1题:停止容器

[!EXAMPLE] 易错提醒 docker pause 是"暂停"而非"停止"——它通过 cgroup 将容器进程挂起,内存中数据仍在;docker stop 则是发送 SIGTERM 信号后优雅终止。

【单选题】以下哪个命令用于在Docker中停止一个正在运行的容器?(5分)

A. docker stop

B. docker pause

C. docker end

D. docker halt

正确答案: A
作答答案: A ✅


第2题:构建镜像

[!EXAMPLE] 关键字记忆 build 对应构建镜像,类似 "builder" → "built image"。记住 Dockerfile + docker build 的组合。

【单选题】哪个命令是用于在Docker中构建镜像的?(5分)

A. docker compose

B. docker build

C. docker make

D. docker assemble

正确答案: B
作答答案: B ✅


第3题:容器与镜像的关系

[!INFO] 类比理解 镜像 = 类(Class),容器 = 实例(Instance)。就像你可以从同一个类创建多个对象实例一样,你也可以从同一个镜像启动多个容器。

【单选题】关于Docker容器和镜像的关系正确的是?(5分)

A. 容器是镜像的一个实例

B. 镜像可以包含多个容器

C. 容器和镜像是两个没有联系的独立组件

D. 容器比镜像更早出现于Docker技术中

正确答案: A
作答答案: A ✅


第4题:查看运行容器

【单选题】在Docker中,通过哪条命令可以查看所有当前运行的容器?(5分)

A. docker ps

B. docker view

C. docker list

D. docker show

正确答案: A
作答答案: A ✅

[!NOTE] docker ps vs docker ps -a

  • docker ps:仅显示正在运行的容器
  • docker ps -a:显示所有容器(包括已停止的)

第5题:创建并启动容器

【单选题】Docker 中创建并启动一个容器的正确命令是什么?(5分)

A. docker build -t myimage .

B. docker create --name mycontainer myimage

C. docker run --name mycontainer myimage

D. docker start mycontainer

正确答案: C
作答答案: C ✅

[!WARNING] create vs run docker create 仅创建容器但不启动;docker run 等价于 create + start 的组合。考试常考这个细微差别。


第1题(多选):Docker 与虚拟机对比

[!INFO] Docker vs 虚拟机核心对比

维度 Docker 容器 虚拟机 (VM)
启动速度 秒级 分钟级
资源占用 共享宿主内核,轻量 独立 Guest OS,臃肿
隔离性 进程级隔离 硬件级完全隔离
镜像大小 MB 级别 GB 级别
性能损耗 几乎无 虚拟化层开销

【多选题】Docker与虚拟机的优劣势对比中,下列哪些是正确的?(5分)

A. Docker容器启动比虚拟机更迅速

B. 虚拟机提供了更高的隔离性和安全性

C. Docker比虚拟机需要更多的硬件资源

D. Docker比虚拟机具有更快的性能提升潜力

正确答案: ABD
作答答案: ABD ✅


二、MySQL 索引与查询优化

覆盖索引流程图

flowchart TD
  Q[SQL 查询] --> C{SELECT 字段是否都在索引中?}
  C -->|是| Cover[✅ 覆盖索引 — 只需扫描索引树]
  C -->|否| NoCover[❌ 需要回表 — 先查索引再查主表]
  Cover --> Fast[🚀 性能高 — 减少 IO]
  NoCover --> Slow[⏱️ 性能低 — 额外 IO 操作]

函数导致索引失效

flowchart LR
  A["WHERE YEAR(create_time) = 2023"] -->|对字段做运算| B["💥 索引失效 — 全表扫描"]
  C["WHERE create_time >= '2023-01-01'"] -->|范围比较,字段无修改| D["✅ 索引命中 — 范围扫描"]

[!CRITICAL] ⚠️ 索引失效的核心规则 对索引字段做任何运算(函数、表达式)都会导致索引失效。因为索引存储的是原始值,无法匹配计算后的结果。

深分页对比

flowchart LR
  A["LIMIT 100000, 10"] -->|传统写法| B["MySQL: 扫描 100010 行 → 丢弃前 100000 行"]
  C["WHERE id > last_id LIMIT 10"] -->|游标翻页| D["MySQL: 直接定位到目标位置 → 只读 10 行"]

原题回顾

第8题:索引失效的场景

[!CAUTION] 高频考点 这类题出现在几乎所有后端面试中。记住口诀:字段不做函数运算,不等于号慎用,LIKE 不以 % 开头才可走索引。

【单选题】下列哪个WHERE子句中的条件最有可能导致create_time字段上的索引失效?(5分)

A. WHERE YEAR(create_time) = 2023

B. WHERE create_time >= '2023-01-01' AND create_time < '2023-02-01'

C. WHERE create_time BETWEEN '2023-01-01' AND '2023-12-31'

D. WHERE create_time = '2023-06-01'

正确答案: A
作答答案: B ❌

[!THINK] 💡 为什么选 A? YEAR() 是对 create_time 字段的函数运算,MySQL 在匹配时无法直接使用 B+ 树索引来加速查找,必须逐行计算判断。而 B、C、D 都是直接用原始值进行比较,可以正常走索引。


第9题:深分页优化写法

【单选题】在大数据表进行LIMIT深分页时,下列哪种SQL写法具有优化作用?(5分)

A. SELECT * FROM table WHERE id > (SELECT id FROM table ORDER BY id LIMIT 100000, 1) LIMIT 10

B. SELECT * FROM table LIMIT 100000, 10

C. SELECT * FROM table WHERE name LIKE '%abc%' LIMIT 10

D. SELECT * FROM table ORDER BY RAND() LIMIT 10

正确答案: A
作答答案: A ✅

[!NOTE] A 选项的本质 先用子查询找到第 100001 行的 id 值,然后用 WHERE id > xxx LIMIT 10 直接定位到目标页,避免扫描大量无用数据。注意:这只适用于有序且无删除空洞的场景。


第10题:深分页优化技术

【单选题】在优化MySQL的深分页查询(如 LIMIT 100000, 10)时,推荐使用哪种技术以显著减少扫描的行数?(5分)

A. 基于索引的子查询配合主键筛选

B. 添加更多字段到SELECT查询列表

C. 在ORDER BY后对LIMIT增加OFFSET参数

D. 增加连接数以提升并发执行

正确答案: A
作答答案: A ✅


第11题:覆盖索引的实际应用

[!INFO] 什么是覆盖索引? 当 SQL 查询的所有字段都能从索引中直接获取,而不需要再去主表中查数据(即"回表"),就是使用了覆盖索引。

【单选题】如果有如下联合索引KEY(idx_col1_col2) (col1, col2),执行 SELECT col1 FROM table WHERE col1=5 时,下列哪项说法正确?(5分)

A. 该查询可以使用覆盖索引,并且只扫描索引

B. 该查询不能使用索引

C. 该查询必须回表查询主表数据

D. 该查询会进行全表扫描

正确答案: A
作答答案: A ✅

[!THINK] 💡 为什么能用覆盖索引? 联合索引 (col1, col2) 的 B+ 树叶子节点上存的就是 (col1, col2) 的值。既然只需要 col1,那直接在索引树上找到即可,无需回表。但如果查询的是 SELECT col2 WHERE col1=5,同样也能覆盖索引——因为联合索引包含了两个字段。


第12题:覆盖索引的概念

[!CAUTION] 错题回顾 这道题你做错了,说明对覆盖索引的核心价值还不够清晰——它的本质是 "只访问索引,不访问表"。

【单选题】以下关于MySQL覆盖索引的说法,哪项是正确的?(5分)

A. 覆盖索引可以减少IO,因为查询只需访问索引

B. 覆盖索引只能用于InnoDB引擎

C. 覆盖索引不能用于联合索引

D. 覆盖索引使用时必须回表查询原始数据

正确答案: A
作答答案: D ❌


第13题:使用覆盖索引的条件

【单选题】在MySQL中,什么情况下可以使用覆盖索引来优化查询?(5分)

A. 查询的字段全部被所用的索引覆盖,无需回表查询主表数据

B. 查询字段必须是聚合函数的字段

C. 只有当表中没有主键时才能使用覆盖索引

D. 只有当表有唯一索引时才能使用覆盖索引

正确答案: A
作答答案: A ✅


三、MySQL 慢查询综合优化

优化策略金字塔

quadrantChart
    title "MySQL 慢查询优化策略优先级"
    x-axis "低实施难度" --> "高实施难度"
    y-axis "高效益" --> "低效益"
    "SQL改写": [0.15, 0.9]
    "加索引": [0.3, 0.8]
    "EXPLAIN分析": [0.1, 0.85]
    "缓存层(Redis)": [0.5, 0.7]
    "硬件升级": [0.9, 0.3]
    "分库分表": [0.95, 0.6]

原题回顾

第3题(多选):慢查询措施

【多选题】在优化MySQL数据库时,如果遇到慢查询,应该考虑采取哪些措施?(5分)

A. 分析执行计划

B. 调整数据库系统的硬件配置

C. 优化程序的业务逻辑

D. 使用存储过程简化复杂查询

正确答案: ABCD
作答答案: ACD ❌

[!THINK] 💡 为什么不排除 B 和 D?

  • B 是合理的:如果 SQL 已经无可优化空间,硬件升级(如换 SSD、加内存)确实能改善性能
  • D 在某些场景有效:存储过程可以减少网络往返次数,适合批量处理场景
  • 这是一个多选题,题目问的是"哪些应该考虑",而非"最佳方案是什么"

第4题(多选):提高查询性能的措施

【多选题】在进行MySQL性能优化时,可以采取哪些措施来提高查询性能?(5分)

A. 使用索引来加速查询

B. 禁用所有外键以减少约束检查

C. 选择合适的存储引擎

D. 定期进行表碎片整理

正确答案: ACD
作答答案: ABCD ❌

[!WARNING] 为什么排除 B? 禁用所有外键 是错误的优化思路。外键保证数据完整性,盲目禁用会导致脏数据。如果性能瓶颈确实在外键检查,应该考虑架构层面的解耦(如用应用层校验替代数据库外键约束),而不是粗暴地"禁用所有"。


第5题(多选):慢查询优化方式

【多选题】在Mysql中,针对慢查询优化有哪些有效方式?(5分)

A. 可以通过为查询字段建立合适的索引以减少全表扫描

B. 查询时尽量避免使用 SELECT *,只查需要的字段有助于提升性能

C. 增加表的数据量可以显著提高查询效率

D. 可以通过分析慢查询日志定位并优化低效的SQL语句

正确答案: ABD
作答答案: ABD ✅


第7题(多选):优化查询语句的方法

【多选题】优化MySQL查询语句的方法(5分)

A. 使用正确的索引

B. 避免 SELECT *,只获取必需的列

C. 使用 GORM 可以提升查询性能

D. 使用 EXPLAIN 查看查询计划,然后针对性优化

正确答案: ABD
作答答案: ABCD ❌

[!CAUTION] 为什么排除 C? GORM 是一个 ORM 框架,它能简化开发但不会自动提升查询性能——如果写出的 SQL 本身有问题(比如 N+1 查询),GORM 反而会生成更差的 SQL。性能优化取决于你写的查询语句本身。


第8题(多选):索引优化的考虑因素

【多选题】关于MySQL数据库索引优化的考虑因素有哪些?(5分)

A. 考虑为那些经常作为查询条件的列添加索引

B. 为经常要排序、分组和联合操作的字段建立索引

C. 为经常更新的字段添加索引

D. 避免为表中的每个列都建立索引

正确答案: ABD
作答答案: ABCD ❌

[!CAUTION] 为什么排除 C? 索引不是免费的! 每次 INSERT/UPDATE 都需要更新索引树,频繁更新的字段加索引会显著拖慢写入性能。索引的原则是"多读少写的字段优先建索引"。


四、MySQL 多选型错题精讲

第6题(多选):Docker 数据持久化

[!CAUTION] 错题回顾 你对 Docker Volume 和 Bind Mount 的区别还需要理清。

【多选题】在使用Docker时,如何实现数据的持久化存储?(5分)

A. 通过配置 tmpfs 挂载

B. 使用 Docker Volume

C. 创建 Bind Mounts

D. 在容器内部使用外部存储设备

正确答案: BC
作答答案: BD ❌

[!THINK] 💡 tmpfs vs Volume vs Bind Mount

  • tmpfs 挂载:存在宿主机内存中,容器重启后数据丢失 → ❌ 不是持久化
  • Docker Volume:由 Docker 管理,存储在 /var/lib/docker/volumes/,容器删除后数据还在 → ✅
  • Bind Mounts:绑定宿主机目录到容器,生命周期一致 → ✅
  • 选 B 和 C

第9题(多选):Protobuf 优势

[!INFO] Protobuf 核心特性速查表

特性 JSON Protobuf
大小 较大(文本格式) 小(二进制编码)
解析速度 中等 快
人类可读 ✅ ❌(需 .proto 文件)
向后兼容 ✅ ✅(靠 field number)
跨语言 ✅ ✅

【多选题】当需要在不同的应用程序之间传输数据时,使用 Protobuf 有哪些优势?(5分)

A. 数据压缩率高,节省存储空间

B. 直接可读,无需反序列化

C. 支持多种编程语言

D. 序列化过程速度快,对性能影响小

正确答案: ACD
作答答案: AD ❌

[!CAUTION] 为什么选 A 不选 B?

  • A 是对的:Protobuf 二进制编码比 JSON 文本紧凑很多,尤其是数值类型用 varint 编码后更小
  • B 是错的:Protobuf 是二进制格式,对人不可读,必须先反序列化才能得到结构化数据

第10题(多选):Protobuf 序列化基础

[!INFO] Protobuf 版本兼容性机制 Protobuf 通过 field number(字段编号)而非字段名来识别数据。添加新字段不影响旧客户端解析(向前兼容),删除旧字段不影响新客户端读取已有字段(向后兼容)。

【多选题】在使用 Protobuf 进行序列化和反序列化时,以下哪些说法是正确的?(5分)

A. Protobuf 是一种轻量级的数据交换格式

B. Protobuf 只能用于 C++ 语言

C. Protobuf 版本支持向后兼容性和向前兼容性

D. Protobuf 的序列化格式比 JSON 小

正确答案: ACD
作答答案: ACD ✅


第20题:Protobuf varint 编码

[!INFO] Varint 编码原理 Protobuf 使用可变长度整数编码(varint),每个字节只用 7 位存储数据,最高位表示"是否还有后续字节"。小整数只需 1 个字节,大整数可能需要 2-5 个字节。相比固定长度的 int32/int64,在大多数场景下更省空间。

【单选题】在解析 Protobuf 的数据时,为什么需要使用 varint 编码的整数?(5分)

A. 因为 varint 编码更节省空间,可以表示任意大小的整数。

B. 因为 varint 编码使整数的编码更简单。

C. 因为 varint 编码使数据解析更快。

D. 因为 varint 编码适合网络传输的不定长整数编码。

正确答案: A
作答答案: D ❌


五、Go 并发编程

Goroutine 调度模型

flowchart TB
  G[goroutines] --> S[Scheduler 调度器]
  S --> M[M threads]
  M --> W1["OS Thread 1 → g1, g3"]
  M --> W2["OS Thread 2 → g2, g4"]
  S -->|"协程切换开销 ~0.2μs"| P["⚡ 远轻于线程"]

[!INFO] goroutine vs 线程 goroutine 初始栈仅 2KB(可动态伸缩),而系统线程通常是几 MB。Go 的 M:N 调度器在少量 OS 线程上复用成千上万 goroutine,因此并发成本极低。

原题回顾

第7题:goroutine 输出顺序

【单选题】下列代码的输出结果是什么?(5分)

package main

import (
	"fmt"
	"sync"
)

func main() {
	var wg sync.WaitGroup
	for i := 0; i < 3; i++ {
		wg.Add(1)
		go func(i int) {
			defer wg.Done()
			fmt.Println(i)
		}(i) // 注意:传参确保每个 goroutine 拿到独立的 i
	}
	wg.Wait()
}

A. 打印 0 1 2,顺序不定

B. 打印三次 2

C. 编译错误

D. 打印顺序一定是 0 1 2

正确答案: A
作答答案: A ✅

[!THINK] 💡 关键细节 虽然传了 (i) 参数避免了闭包共享变量问题(所以不会全打印 2),但三个 goroutine 是并发执行的,谁先抢到 CPU 时间片不确定,所以打印顺序不定。wg.Wait() 只保证等所有 goroutine 执行完,不规定顺序。


六、Gin 框架

Gin 上下文流转

flowchart LR
  Client[客户端请求] --> Handler[Handler 处理]
  Handler -->|"c.Set(key, val)"| Context["Gin Context\n(c.Get / c.ShouldBind)"]
  Context --> Next[中间件 / 下一个 Handler]
  Next --> Response[返回响应]

原题回顾

第6题:获取路由参数

[!INFO] Gin 参数获取速查

方法 用途 示例 URL
c.Param("id") 路由参数 /users/:id
c.Query("page") Query 参数 /users?page=1
c.DefaultQuery("page", "1") Query + 默认值 /users
c.PostForm("name") Form 表单 POST body

【单选题】在Gin框架中如何获取路由参数?(5分)

A. c.Param("param_name")

B. c.GetQuery("param_name")

C. c.Params("param_name")

D. c.Query("param_name")

正确答案: A
作答答案: A ✅


第18题:设置 JSON 响应

【单选题】在Gin中如何设置响应格式为JSON?(5分)

A. c.JSON(200, gin.H{"message": "ok"})

B. c.SetJSON(200, gin.H{"message": "ok"})

C. c.Response(200, "application/json", gin.H{"message": "ok"})

D. c.SendJSON(200, gin.H{"message": "ok"})

正确答案: A
作答答案: A ✅

[!NOTE] Gin.H 的本质 gin.H 就是 map[string]interface{} 的类型别名,简洁写法。底层调用 c.Status(code).Render(200, h) 自动设置 Content-Type: application/json。


七、Docker Compose & 工程实践

Dockerfile 层缓存优化

flowchart LR
  L1["COPY go.mod go.sum"] -->|"命中缓存 🟢"| L2["go mod download"]
  L2 -->|"源码变了 ✏️"| L3["COPY . ."]
  L3 -->|"编译 🔨"| L4["go build"]
  L1 -.|-| L2b["未命中 🟡\n依赖文件变了,重建缓存"]
  L2b --> L3b["COPY . ."]

[!TIP] Dockerfile 分层缓存的黄金法则 变化越少的层放前面,变化多的放后面。这样大部分构建步骤可以直接复用缓存层,大幅提升构建速度。

多阶段构建对比

flowchart LR
  subgraph Before["单阶段构建 ❌"]
    B1["Go SDK + src + deps"] --> B2["Binary"]
    B_end["最终镜像 ≈ 800MB"]
  end
  subgraph After["多阶段构建 ✅"]
    A1["Builder Stage: Go SDK + src + deps"] --> A2["Binary"]
    A_end["Final Stage: Binary only\n≈ 20MB"]
  end

原题回顾

第14题:Dockerfile 指令顺序

【单选题】在Go服务的Dockerfile中,如何安排COPY指令和依赖安装指令顺序以最大程度利用缓存?(5分)

A. 先 COPY go.mod 和 go.sum,再执行依赖安装,然后再 COPY 源码

B. 全部文件一次性 COPY 到镜像后,统一安装依赖

C. 先安装依赖,再 COPY go.mod 和 go.sum

D. 先 COPY 源码,再 COPY go.mod 和 go.sum,然后安装依赖

正确答案: A
作答答案: A ✅


第15题:多阶段构建适用场景

【单选题】在下列哪种场景下最推荐在 Dockerfile 中使用多阶段构建?(5分)

A. 需要从源代码构建应用,并且只需最终运行文件进镜像

B. 仅需将静态资源服务器部署到镜像

C. 部署单一配置文件到镜像,且无需编译

D. 基础镜像极小,且无需进一步处理

正确答案: A
作答答案: A ✅


第16题:多阶段构建的好处

【单选题】在编写 Dockerfile 时,使用多阶段构建最主要的好处是什么?(5分)

A. 可以显著减小最终镜像的体积,避免无关构建文件被包含

B. 可以自动部署更新到生产环境

C. 能够让 Dockerfile 支持热重载代码

D. 可以让 Docker 镜像自动支持跨平台运行

正确答案: A
作答答案: A ✅


第21题:Docker Compose 容器通信

[!INFO] Docker Compose 内置 DNS Compose 会自动为同一网络中的服务创建一个内部 DNS 服务器。服务名就是主机名,内部端口就是连接端口。

【单选题】在同一个 docker-compose.yml 文件中定义了一个前端 nginx 服务和一个后端 go-api 服务(均未特别配置外部网络)。如果前端容器内的服务想要调用后端接口,最正确、最符合容器化工程实践的通信地址应该怎么写?(5分)

A. 必须通过宿主机的内网 IP 加上被映射出来的端口进行调用。

B. 必须在前端服务中使用 links: - go-api 指令后才能通信。

C. 直接使用后端服务在 YAML 中定义的服务名(即 go-api)作为域名,加上其容器内部端口号即可调用。

D. 前后端容器相互隔离,必须通过配置 Nginx 将后端暴露到外网后才能互相访问。

正确答案: C
作答答案: C ✅


第22题:depends_on 指令

[!WARNING] depends_on 的限制 depends_on 只保证启动顺序,不保证就绪状态。Redis 容器启动了 ≠ Redis 服务已准备好接受连接。如果需要等待就绪,应结合 wait-for-it.sh 或 Healthcheck 机制。

【单选题】在一个包含 Go Web 服务和 Redis 缓存的微服务架构中,为了保证 Redis 容器在 Go 服务之前启动,应该在 Go 服务的配置中使用哪个指令?(5分)

A. network_mode: redis

B. wait_for: redis

C. depends_on: - redis

D. links: - redis

正确答案: C
作答答案: B ❌


第23题:Docker Compose 的核心作用

【单选题】关于 Docker Compose 的主要作用和核心机制,以下说法最准确的是?(5分)

A. 它是一个用于打包单一 Docker 镜像的构建工具,类似于更高级的 Dockerfile。

B. 它是一个容器编排工具,主要用于定义和运行多容器的 Docker 应用程序,默认配置文件名为 docker-compose.yml。

C. 它是 Docker 官方提供的跨主机集群管理工具,专门用于生产环境的负载均衡。

D. 它只能用于管理无状态服务,无法处理需要挂载数据卷的数据库容器。

正确答案: B
作答答案: B ✅


第2题(多选):加快镜像构建

[!TIP] Docker 镜像构建提速清单

  1. ✅ 使用 .dockerignore 排除不必要的文件
  2. ✅ 合并 RUN 指令减少层级
  3. ✅ 利用缓存:将不变的依赖放在前面
  4. ✅ 多阶段构建减小最终体积
  5. ✅ 使用 --mount=type=cache 挂载 Go/npm 缓存目录

【多选题】为了加快Docker镜像的构建速度,以下哪些操作是推荐的?(5分)

A. 使用多阶段构建来优化镜像

B. 在构建过程中,频繁变换基础镜像

C. 最大限度地利用已有层的缓存

D. 将不变的依赖放在Dockerfile的前面

正确答案: ACD
作答答案: AD ❌

[!THINK] 💡 为什么漏选 A? 多阶段构建虽然主要目的是减小镜像体积,但也间接提升了构建效率——因为最终的产物更精简,构建步骤也更清晰。B 明显错误(频繁换基础镜像完全违背了缓存原则)。


八、Nginx & CDN

Nginx 反向代理角色

sequenceDiagram
    participant User as 用户浏览器
    participant Nginx as Nginx 反向代理
    participant Go as Go 后端
    participant CDN as CDN 边缘节点
    User->>Nginx: HTTPS 请求 :80/443
    alt 静态资源
        Nginx->>CDN: 转发到最近边缘
        CDN-->>User: 静态内容
    else API 请求
        Nginx->>Go: 反向代理到内部端口
        Go-->>Nginx: JSON 响应
        Nginx-->>User: 返回响应
    end

[!INFO] 为什么前端要用 Nginx 挡一层?

  • 统一入口:SSL 终止、请求过滤、路由分发在一处完成
  • 静态服务:Nginx 处理静态文件的性能远超业务框架
  • 安全:隐藏后端真实 IP 和端口,便于集中做限流和黑白名单

原题回顾

第17题:CDN 的工作原理

【单选题】内容分发网络(CDN)如何帮助提高网站的加载速度?(5分)

A. 通过将网站流量全部重定向到一个特定的服务器节点

B. 通过在地理上分布的网站服务器上缓存内容来降低延迟

C. 通过增加网站的图像和视频质量

D. 通过限制用户访问内容的次数

正确答案: B
作答答案: B ✅


第24题:Nginx 反向代理的意义

【单选题】当我们的全栈项目要部署到生产环境时,通常会在最外层挡一个 Nginx,并将外部请求"反向代理"给内部的 Go 应用程序。为什么要多此一举,而不是直接让 Go 监听 80/443 端口对外提供服务?(5分)

A. 因为 Go 语言标准库的 net/http 性能太差,无法承受高并发,必须靠 Nginx 提速。

B. 因为 Go 程序无法处理 HTTPS 证书的加密解密。

C. Nginx 专精于静态文件的高效分发,并且能够作为统一入口隐藏后端真实 IP,还能轻松实现请求路由分发、负载均衡和限流,极大提升了整体架构的健壮性和安全性。

D. 这是操作系统的强制要求,所有的 Web 流量必须先经过 Nginx 才能到达用户态程序。

正确答案: C
作答答案: C ✅

[!NOTE] Go vs Nginx 的性能对比 Go 的 net/http 性能非常强(Go 1.21 压测可达百万级 QPS),不需要 Nginx 来"提速"。Nginx 在这里的价值在于架构层面的多路复用、静态处理和安全管理,而非纯吞吐量。


九、HTTP 协议 & CORS

CORS 工作流程

flowchart TD
    A[前端页面 http://app.com:3000] -->|"普通请求 → 带 Origin 头"| B[后端 API http://api.com:8080]
    B -->|"同源 ✅ | 跨域 → 预检请求"| C{CORS 检查}
    C -->|"Access-Control-Allow-Origin 匹配"| D["✅ 允许,返回数据"]
    C -->|"不匹配 / 缺失"| E["❌ 浏览器拦截\nConsole 报 CORS error"]

[!CRITICAL] CORS 的本质 CORS 是浏览器的安全机制,不是服务器端的限制。服务器完全可以收到跨域请求并返回数据——只是浏览器看到缺少正确的 CORS 头就会拒绝把响应交给 JS。

原题回顾

第19题:HTTP 状态码

[!INFO] 常用 HTTP 状态码速记

状态码 含义 场景
200 OK 请求成功
304 Not Modified 缓存命中,无需重新下载
301 / 302 Moved / Found 永久/临时重定向
401 Unauthorized 未认证
403 Forbidden 已认证但无权
404 Not Found 资源不存在
500 Internal Server Error 服务端错误

【单选题】哪种HTTP状态码代表"未修改"?(5分)

A. 200 OK

B. 304 Not Modified

C. 404 Not Found

D. 500 Internal Server Error

正确答案: B
作答答案: B ✅


第25题:Proxy 代理原理

【单选题】在前端开发阶段,我们通常会在 vite.config.js 或 vue.config.js 中配置 proxy 代理来解决跨域问题。这个 Proxy 底层的运作逻辑是什么?(5分)

A. 它修改了浏览器的安全设置,让浏览器临时关闭了同源策略。

B. 它修改了前端发出的 HTTP 请求的 Origin,伪装成和后端同源。

C. 前端代码实际上把请求发给了 Vite 自带的本地 Node.js 开发服务器,开发服务器再把请求转发给真正的 Go 后端,最后把结果原样返回给前端。

D. 它自动为后端的 Go 代码注入了处理跨域的中间件。

正确答案: C
作答答案: C ✅


第26题:CORS 报错的根本原因

[!CAUTION] 高频面试考点 CORS 是前后端分离开发中最常见的痛点之一,也是面试必考题。记住关键词:"同源策略 → 浏览器安全机制 → 阻止跨域响应读取"。

【单选题】前后端分离开发时,前端调用后端经常会遇到可怕的 CORS 报错。导致跨域报错的根本原因是什么?它为了解决什么痛点而存在?(5分)

A. 因为 HTTP 协议不允许不同端口之间通信,这是底层网络协议的限制。

B. 因为后端的 Go 代码默认开启了防火墙,拦截了来自其他端口的请求。

C. 这是浏览器的"同源策略 (Same-Origin Policy)"导致的,为了防止恶意网站窃取用户的敏感数据(如 Cookie),浏览器强制拦截了跨域的响应。

D. 因为前端框架(如 Vue/React)的安全机制,阻止了发起非同源的请求。

正确答案: C
作答答案: C ✅

[!THINK] 💡 如何在后端解决 CORS?

import "github.com/gin-contrib/cors"

r.Use(cors.New(cors.Config{
    AllowOrigins:     []string{"http://localhost:3000"}, // 允许的前端地址
    AllowMethods:     []string{"GET", "POST", "PUT", "DELETE"},
    AllowHeaders:     []string{"Origin", "Content-Type", "Authorization"},
    AllowCredentials: true,                            // 携带 Cookie 时必须设为 true
    MaxAge:           12 * time.Hour,                  // 预检请求缓存时间
}))

十、JWT & Session

Session vs JWT 对比

flowchart LR
    subgraph SessionAuth["Session 认证"]
        S1["客户端: 保存 SessionID (Cookie)"]
        S2["服务端: 存储用户数据<br/>Redis / 内存 / DB"]
        S3["⚠️ 集群环境下需要 Session 共享"]
    end
    subgraph JWTAuth["JWT 认证"]
        J1["客户端: 保存 JWT Token"]
        J2["服务端: 用私钥验证签名<br/>无需存储会话"]
        J3["✅ 天然无状态,水平扩展友好"]
    end

Gin 中 JWT 信息传递流程

flowchart TB
    Middleware["JWT 中间件\nParseString(bearer token)"] --> Valid{"Token 有效?"}
    Valid -->|否| Reject["返回 401 Unauthorized"]
    Valid -->|是| Set["c.Set claims.UserID / c.Set claims.Role"]
    Set --> Handler["业务 Handler\nc.Get claims.UserID"]

[!TIP] Gin Context 的正确用法 c.Set / c.Get 是基于 context.Context 的键值存储,生命周期限定在当前请求范围内,线程安全,是 Gin 中跨中间件和 Handler 传递信息的标准做法。

原题回顾

第27题:JWT 解析后的最佳实践

[!CAUTION] 错题回顾 不要用 c.Redirect 来传递用户信息!Redirect 会中断当前请求流程,把用户带到另一个页面,这与"把用户信息传给后续 Handler"的需求完全不符。

【单选题】当中间件成功解析并验证了前端请求头中的 JWT 令牌后,最佳的代码实践是?(5分)

中间件是查票员,查验完 JWT 后,通过 c.Set() 把乘客信息存储在当前 HTTP 请求的生命周期上下文中,后续函数可以安全、隔离地拿到用户信息。

A. 将解析出的用户 ID 保存到全局变量中。

B. 使用 c.Redirect 将请求重定向,并在 URL 参数里带上用户 ID。

C. 使用 c.Set("userID", id) 将用户信息存储在上下文中。

D. 将用户信息打包成 JSON 写回到响应体中,结束请求。

正确答案: C
作答答案: B ❌


第28题:JWT 解决的痛点

【单选题】JWT 解决了传统 Session 架构的什么核心痛点?(5分)

JWT 是"无状态"的,服务端不需要存储会话记录就能验证其有效性,从而解决了传统 Session 在服务器集群环境下的共享与同步问题。

A. JWT 对用户密码进行了极强的加密,即便被黑客拦截也绝对安全。

B. 传统 Session 强依赖于服务端内存或集中存储,JWT 是"无状态"的。

C. JWT 生成的字符串非常短,比传统的 SessionID 更省网络带宽。

D. JWT 只能在 Go 语言和前端之间解析,跨语言安全性极高。

正确答案: B
作答答案: B ✅

[!NOTE] JWT ≠ 加密 JWT 默认是 Base64 编码(可读的),不包含加密。即使加了签名防止篡改,Token 内容本身仍然是明文的。因此不要在 JWT 中存放敏感信息(如密码、身份证号)。


[!TIP] Cookie 安全防护体系

属性 作用 防御目标
HttpOnly JavaScript 无法读取 XSS 攻击
Secure 仅 HTTPS 传输 中间人窃听
SameSite 控制跨站发送 CSRF 攻击
maxAge 过期时间 长期驻留风险

【单选题】在使用 Gin 设置 Cookie 时,为了防止前端恶意脚本通过 JavaScript 读取到包含用户登录凭证的敏感 Cookie,你应该将哪个参数设置为什么值?(5分)

httpOnly 是防御 XSS 攻击的利器。当它设为 true 时,浏览器会把这个 Cookie 藏好,只有在向后端发送 HTTP 请求时才会默默带上,前端的任何 JS 代码都无权读取。顺便提一下,secure 也很重要,它表示该 Cookie 只能在 HTTPS 加密连接下传输,用来防止网络中间人窃听。

A. maxAge 设置为 0

B. secure 设置为 true

C. httpOnly 设置为 true

D. domain 设置为 "localhost"

正确答案: C
作答答案: C ✅


第30题:Gin 中 Session 的使用

【单选题】关于 Gin 框架中 Session 的运用,以下说法正确的是?(5分)

Session 的本质是服务器在后端存储了用户状态,只把一个无意义的、随机的 SessionID 通过 Cookie 发给前端保存。Gin 核心库为了保持轻量,并没有内置 Session,推荐使用第三方中间件。

A. Gin 官方核心库自带了完善的 Session 管理器,无需引入第三方包。

B. Session 数据默认以明文形式保存在客户端的浏览器中。

C. Session 的本质是服务器在后端存储用户状态,只把 SessionID 发给前端。

D. 只要用户关闭浏览器页面,服务器端的 Session 数据就会被自动删除。

正确答案: C
作答答案: C ✅

[!INFO] Gin + Session 常见组合

// 常用第三方 Session 中间件
import "github.com/gin-contrib/sessions"
import "github.com/gin-contrib/sessions/redis"

store, _ := redis.NewStore(10, "tcp", "localhost:6379", "", []byte("secret"))
r.Use(sessions.Sessions("session_name", store))
// 后续可通过 session.Get("user_id") 读取

十一、多选题错题汇总

将所有做错的多选题汇总在此,方便集中复习:

题号 知识点 你的答案 正确答案 失分原因
单选-8 索引失效 B A 未能识别函数对索引的影响
多选-2 Docker 构建加速 AD ACD 漏选多阶段构建
多选-3 慢查询措施 ACD ABCD 排除了合理选项(硬件/存储过程)
多选-4 查询性能优化 ABCD ACD 未排除"禁用外键"的错误选项
多选-6 Docker 持久化 BD BC 混淆了 Volume/Bind Mount/tmfs
多选-7 查询优化方法 ABCD ABD 误认为 ORM 能自动提升性能
多选-8 索引优化因素 ABCD ABD 忽略了索引对写入性能的影响
多选-9 Protobuf 优势 AD ACD 未选"支持多种语言"
单选-22 depends_on B C 拼写了不存在的指令
单选-27 JWT 中间件 B C 混淆了重定向与上下文传递
单选-20 Protobuf varint D A 理解不够精准

[!SUCCESS] 💪 复习建议 从上面的表格可以看出,你的薄弱环节集中在:

  1. MySQL 索引原理(函数导致索引失效、索引对写入的影响)
  2. Docker 概念辨析(Volume vs tmpfs、depends_on 的作用)
  3. Protobuf 特性(多语言支持) 下次考试前重点回顾这些模块的核心概念图。

关联笔记