812 lines
23 KiB
Markdown
812 lines
23 KiB
Markdown
---
|
||
tags: [exam, gin, go, http, jwt, cors, middleware, session, cookie, web]
|
||
create time: 2026-05-14 18:30
|
||
---
|
||
|
||
# 金山考试 - 武科大服务端 - 第三次课
|
||
|
||
## 概述
|
||
|
||
武科大服务端第三次课的考试错题整理与知识点拓展。共 30 道单选题,满分 150 分,涉及 HTTP 协议基础、Gin 框架中间件、Cookie/Session/JWT 认证、CORS 跨域、Nginx 反向代理等核心后端开发知识。
|
||
|
||
**得分:145/150(仅第 12 题出错)**
|
||
|
||
---
|
||
|
||
## 我的错题
|
||
|
||
### 第 12 题 — Gin 全局注册中间件
|
||
|
||
**你的作答:** D · **正确答案:** A
|
||
|
||
```go
|
||
// 错误答案 ❌
|
||
router.Middleware(middleware)
|
||
|
||
// 正确答案 ✔
|
||
router.Use(middleware)
|
||
```
|
||
|
||
> [!tip] 考点解析
|
||
>
|
||
> Gin 使用 `Use()` 方法注册**全局中间件**,参数接受一个或多个 `HandlerFunc`:
|
||
>
|
||
> ```go
|
||
> router := gin.Default()
|
||
> // 全局注册:所有路由都会经过此中间件
|
||
> router.Use(GinLogger())
|
||
> router.Use(GinRecovery())
|
||
>
|
||
> // 组级注册:只对 /v1 组生效
|
||
> v1 := router.Group("/v1")
|
||
> v1.Use(AuthMiddleware())
|
||
> {
|
||
> v1.GET("/user", getUserHandler)
|
||
> }
|
||
> ```
|
||
>
|
||
> **记忆要点:** `Use` 是链式的,中间件按注册顺序执行。
|
||
>
|
||
> | 方法 | 作用范围 | 说明 |
|
||
> |------|---------|------|
|
||
> | `router.Use()` | 全局中间件 | 所有路由生效 |
|
||
> | `group.Use()` | 组中间件 | 仅该 Group 下路由生效 |
|
||
|
||
---
|
||
|
||
## 正文
|
||
|
||
### 一、HTTP 协议基础
|
||
|
||
> [!abstract] 本节覆盖:第 1、5、29 题
|
||
>
|
||
> HTTP 是 Web 的基石。理解它的无状态特性、方法语义和端口规范,是后续学习所有后端技术的前提。
|
||
|
||
#### 第 1 题
|
||
|
||
【单选题】HTTP请求中的 GET 和 POST 有什么主要区别?(5分)
|
||
|
||
A. GET请求通常用于请求数据,POST请求只用于提交数据。
|
||
|
||
B. GET请求的数据会附加在URL之后,POST请求的数据放在请求体内。
|
||
|
||
C. GET请求可以获取新的资源,POST请求无法获取资源。
|
||
|
||
D. GET请求不安全,POST请求比GET请求安全。
|
||
|
||
**正确答案:** B
|
||
|
||
**作答答案:** B
|
||
|
||
> [!tip] 深度理解
|
||
>
|
||
> - **URL vs Body**:GET 的参数拼在 URL 后(如 `/api/user?id=1`),受长度限制且会出现在浏览器历史记录中;POST 的数据放在 Request Body 中,没有长度限制。
|
||
> - **幂等性**:GET 应该是幂等的(多次请求结果一致),POST 不是。
|
||
> - **安全性误区**:POST != 安全!两者都是明文传输(除非配合 HTTPS)。敏感数据应该用 HTTPS,而非依赖 POST。
|
||
|
||
#### 第 5 题
|
||
|
||
【单选题】下列哪一项是 HTTP 的默认端口号?(5分)
|
||
|
||
A. 21
|
||
|
||
B. 80
|
||
|
||
C. 443
|
||
|
||
D. 8080
|
||
|
||
**正确答案:** B
|
||
|
||
**作答答案:** B
|
||
|
||
> [!note] 常见端口速查
|
||
>
|
||
> | 端口 | 协议 | 用途 |
|
||
> |------|------|------|
|
||
> | 80 | HTTP | 标准 Web 服务 |
|
||
> | 443 | HTTPS | 加密 Web 服务 |
|
||
> | 21 | FTP | 文件传输 |
|
||
> | 3306 | MySQL | 数据库 |
|
||
> | 8080 | HTTP | 常用开发/替代端口 |
|
||
|
||
#### 第 29 题
|
||
|
||
【单选题】HTTP协议被称为"无状态 (Stateless)"协议,这到底意味着什么?(5分)
|
||
|
||
A. 服务器无法一次性处理多个状态的请求
|
||
|
||
B. 每次请求都是独立的,服务器默认不会记住之前的请求信息
|
||
|
||
C. 数据在传输过程中没有加密状态
|
||
|
||
D. 客户端不需要等待服务器的响应即可发送下一个请求
|
||
|
||
**正确答案:** B
|
||
|
||
**作答答案:** B
|
||
|
||
> [!example] 为什么需要 Cookie/Session/JWT?
|
||
>
|
||
> 因为 HTTP 是无状态的——每次请求都是一次"失忆"的新对话。为了维持用户登录态,我们需要额外的机制来"记住"用户是谁:
|
||
>
|
||
> ```mermaid
|
||
> sequenceDiagram
|
||
> participant Browser as 浏览器
|
||
> participant Server as 服务端
|
||
> Browser->>Server: 1. POST /login (账号密码)
|
||
> Server->>Browser: 2. Set-Cookie: sessionId=abc123
|
||
> Browser->>Server: 3. GET /profile (携带 Cookie)
|
||
> Server->>Server: 4. 根据 sessionId 查 Session 数据
|
||
> Server->>Browser: 5. 返回用户个人信息
|
||
> ```
|
||
|
||
---
|
||
|
||
### 二、缓存控制
|
||
|
||
> [!abstract] 本节覆盖:第 2、3、6 题
|
||
>
|
||
> 缓存是 Web 性能优化的核心手段。理解 Cache-Control 和 304 状态码,能让你的应用快得多。
|
||
|
||
#### 第 2 题
|
||
|
||
【单选题】以下哪个 HTTP 头字段可以用于管理缓存的过期时间?(5分)
|
||
|
||
A. Content-Type
|
||
|
||
B. Cache-Control
|
||
|
||
C. Authorization
|
||
|
||
D. Location
|
||
|
||
**正确答案:** B
|
||
|
||
**作答答案:** B
|
||
|
||
#### 第 3 题
|
||
|
||
【单选题】哪种 HTTP 状态码代表"未修改"?(5分)
|
||
|
||
A. 200 OK
|
||
|
||
B. 304 Not Modified
|
||
|
||
C. 404 Not Found
|
||
|
||
D. 500 Internal Server Error
|
||
|
||
**正确答案:** B
|
||
|
||
**作答答案:** B
|
||
|
||
#### 第 6 题
|
||
|
||
【单选题】HTTP协议中哪个状态码表示请求的资源未被修改?(5分)
|
||
|
||
A. 200
|
||
|
||
B. 403
|
||
|
||
C. 304
|
||
|
||
D. 500
|
||
|
||
**正确答案:** C
|
||
|
||
**作答答案:** C
|
||
|
||
> [!tip] Cache-Control + 304 组合拳
|
||
>
|
||
> 两种缓存策略各有分工:
|
||
>
|
||
> | 策略 | 原理 | 关键头 |
|
||
> |------|------|--------|
|
||
> | **强缓存** | 不发起请求,直接读本地缓存 | `Cache-Control: max-age=3600` |
|
||
> | **协商缓存** | 发请求问服务端"我缓存过期的没" | `ETag` + `If-None-Match` |
|
||
>
|
||
> **完整流程:**
|
||
>
|
||
> ```mermaid
|
||
> sequenceDiagram
|
||
> participant 浏览器
|
||
> participant 服务端
|
||
> 浏览器->>服务端: GET /data (带 If-None-Match: "abc123")
|
||
> 服务端->>服务端: 检查资源是否变化
|
||
> alt 资源未变化
|
||
> 服务端-->>浏览器: 304 Not Modified
|
||
> 浏览器->>浏览器: 使用本地缓存
|
||
> else 资源已变化
|
||
> 服务端-->>浏览器: 200 + 新内容 + 新 ETag
|
||
> end
|
||
> ```
|
||
>
|
||
> **两个关键头**:
|
||
> - **ETag**:服务器生成的资源指纹(哈希值),如 `"abc123"`
|
||
> - **If-None-Match**:浏览器下次请求时把上次的 ETag 传回来让服务端比对
|
||
|
||
---
|
||
|
||
### 三、Gin 框架核心
|
||
|
||
> [!abstract] 本节覆盖:第 8、13~18、20、27 题
|
||
>
|
||
> Gin 是 Go 生态最流行的 Web 框架。掌握 Context、路由、绑定和中间件,你就掌握了 Gin 的命脉。
|
||
|
||
#### 第 8 题
|
||
|
||
【单选题】在Go language HTTP server中,如何从HTTP请求中解析查询参数?(5分)
|
||
|
||
A. 使用req.URL.Query()方法
|
||
|
||
B. 调用req.ParseForm()然后使用req.Form
|
||
|
||
C. 使用req.GetHeader('Query')方法
|
||
|
||
D. 手动解析req.Body流中的内容
|
||
|
||
**正确答案:** A
|
||
|
||
**作答答案:** A
|
||
|
||
> [!code Go] 查询参数解析对比
|
||
>
|
||
> ```go
|
||
> // 方式1:net/http 原生
|
||
> values := req.URL.Query() // map[string][]string
|
||
> id := values.Get("id") // string
|
||
>
|
||
> // 方式2:Gin 封装(更简洁)
|
||
> id := c.Query("id") // string(单值)
|
||
> ids := c.QueryArray("id") // []string(多值)
|
||
> fid := c.DefaultQuery("id", "1") // 带默认值
|
||
> ```
|
||
|
||
#### 第 13 题
|
||
|
||
【单选题】编写一个Gin中间件(比如计算请求耗时)时,哪个方法用于暂停当前中间件的执行,先去执行后续的业务逻辑(Handler),等业务执行完再回来继续执行中间件剩余的代码?(5分)
|
||
|
||
A. c.Next()
|
||
|
||
B. c.Abort()
|
||
|
||
C. c.Stop()
|
||
|
||
D. c.Return()
|
||
|
||
**正确答案:** A
|
||
|
||
**作答答案:** A
|
||
|
||
> [!important] Next() vs Abort() — 中间件的双向控制
|
||
>
|
||
> ```go
|
||
> func Logger() gin.HandlerFunc {
|
||
> start := time.Now() // 记录开始时间
|
||
> c.Next() // ▶ 放行给后续中间件/handler
|
||
> // ◀ 这里会继续执行(像洋葱剥开又合上)
|
||
> log.Printf("%s %s %v", c.Writer.Status(), c.Request.URL.Path, time.Since(start))
|
||
> }
|
||
>
|
||
> func Auth() gin.HandlerFunc {
|
||
> token := c.GetHeader("Authorization")
|
||
> if token == "" {
|
||
> c.Abort() // 中断链条,不再执行后续中间件
|
||
> c.JSON(401, gin.H{"error": "缺少认证"})
|
||
> return
|
||
> }
|
||
> c.Next() // 验证通过,放行
|
||
> }
|
||
> ```
|
||
|
||
#### 第 14 题
|
||
|
||
【单选题】当前端发送了一段JSON数据,后端想将其解析并映射到Go的一个结构体(Struct)上,推荐使用哪个方法?(5分)
|
||
|
||
A. c.Query()
|
||
|
||
B. c.PostForm()
|
||
|
||
C. c.ShouldBindJSON()
|
||
|
||
D. c.GetString()
|
||
|
||
**正确答案:** C
|
||
|
||
**作答答案:** C
|
||
|
||
> [!code Go] Gin 常用绑定方法
|
||
>
|
||
> ```go
|
||
> type User struct {
|
||
> Name string `json:"name" binding:"required"`
|
||
> Age int `json:"age" binding:"gte=0,lte=150"`
|
||
> }
|
||
>
|
||
> func handler(c *gin.Context) {
|
||
> var u User
|
||
> _ = c.ShouldBindJSON(&u) // 绑定 JSON body
|
||
> // 其他绑定方式:
|
||
> c.ShouldBindQuery(&u) // 绑定 ?name=x&age=18
|
||
> c.ShouldBindUri(&u) // 绑定 /users/123
|
||
> c.ShouldBind(&u) // 自动识别 content-type
|
||
> }
|
||
> ```
|
||
|
||
#### 第 15 题
|
||
|
||
【单选题】在Gin中,你想设计一个路由来获取特定ID的用户信息(例如从 /user/123 中提取出 123),正确的动态路由定义方式是什么?(5分)
|
||
|
||
A. router.GET("/user/?id", handler)
|
||
|
||
B. router.GET("/user/:id", handler)
|
||
|
||
C. router.GET("/user/*id", handler)
|
||
|
||
D. router.GET("/user/{id}", handler)
|
||
|
||
**正确答案:** B
|
||
|
||
**作答答案:** B
|
||
|
||
> [!note] Gin 路由语法速记
|
||
>
|
||
> | 模式 | 含义 | 示例 |
|
||
> |------|------|------|
|
||
> | `/user/:id` | 单变量 | `/user/123` → id="123" |
|
||
> | `/user/*path` | 通配符 | `/user/a/b/c` → path="/a/b/c" |
|
||
> | `/user/search` | 固定路径 | 精确匹配 |
|
||
|
||
#### 第 16 题
|
||
|
||
【单选题】在Gin框架中,中间件的用途是什么?(5分)
|
||
|
||
A. 在处理请求和发送响应之间执行额外的逻辑。
|
||
|
||
B. 生成项目的文档。
|
||
|
||
C. 处理数据库连接。
|
||
|
||
D. 定义项目配置。
|
||
|
||
**正确答案:** A
|
||
|
||
**作答答案:** A
|
||
|
||
#### 第 17 题
|
||
|
||
【单选题】在Gin框架中,若需要在中间件之间共享数据,应使用哪种方式?(5分)
|
||
|
||
A. 利用c.Set存储数据,并在另一个中间件中通过c.Get获取。
|
||
|
||
B. 直接将数据作为参数传递至下一个中间件。
|
||
|
||
C. 使用全局变量来存储和共享数据。
|
||
|
||
D. 创建一个新的结构体来在中间件之间传递数据。
|
||
|
||
**正确答案:** A
|
||
|
||
**作答答案:** A
|
||
|
||
> [!code Go] c.Set / c.Get 在中间间共享数据
|
||
>
|
||
> ```go
|
||
> func UserInfoExtractor() gin.HandlerFunc {
|
||
> user := fetchUserFromToken(c.GetHeader("Authorization"))
|
||
> c.Set("userID", user.ID) // 存入上下文
|
||
> c.Set("username", user.Name)
|
||
> c.Next()
|
||
> }
|
||
>
|
||
> func GetProfile(c *gin.Context) {
|
||
> // 直接从上下文中取,无需层层传参
|
||
> id, _ := c.Get("userID")
|
||
> name, _ := c.Get("username")
|
||
> c.JSON(200, gin.H{"id": id, "name": name})
|
||
> }
|
||
> ```
|
||
|
||
#### 第 18 题
|
||
|
||
【单选题】在Gin框架中,如何使用JWT验证中间件?(5分)
|
||
|
||
A. 通过router组添加中间件进行验证
|
||
|
||
B. 使用静态文件服务
|
||
|
||
C. 直接在handler函数中编写JWT验证代码
|
||
|
||
D. 使用外部服务进行验证
|
||
|
||
**正确答案:** A
|
||
|
||
**作答答案:** A
|
||
|
||
> [!code Go] JWT 中间件实战模板
|
||
>
|
||
> ```go
|
||
> func JWTAuth() gin.HandlerFunc {
|
||
> return func(c *gin.Context) {
|
||
> tokenStr := c.GetHeader("Authorization")
|
||
> claims, err := parseJWT(tokenStr)
|
||
> if err != nil {
|
||
> c.AbortWithStatusJSON(401, gin.H{"error": "无效令牌"})
|
||
> return
|
||
> }
|
||
> c.Set("userID", claims.UserID) // 存入上下文供后续使用
|
||
> c.Next()
|
||
> }
|
||
> }
|
||
>
|
||
> // 使用:只对需要认证的路由组生效
|
||
> admin := r.Group("/admin")
|
||
> admin.Use(JWTAuth())
|
||
> {
|
||
> admin.GET("/users", listUsers)
|
||
> }
|
||
> ```
|
||
|
||
#### 第 20 题
|
||
|
||
【单选题】在Gin框架中,gin.Context 是出现频率最高的参数。它的核心作用是什么?(5分)
|
||
|
||
A. 仅仅用于连接数据库的上下文
|
||
|
||
B. 专门用来渲染HTML模板的对象
|
||
|
||
C. 封装了HTTP的请求(Request)和响应(ResponseWriter),并在中间件之间传递数据
|
||
|
||
D. 用于启动Gin的底层HTTP服务器
|
||
|
||
**正确答案:** C
|
||
|
||
**作答答案:** C
|
||
|
||
> [!note] gin.Context 一图看懂
|
||
>
|
||
> ```mermaid
|
||
> graph TD
|
||
> A["gin.Context"] --> B["Request 读取<br/>Query/Header/Body"]
|
||
> A --> C["Response 写入<br/>JSON/String/File"]
|
||
> A --> D["中间件通信<br/>c.Set / c.Get / c.Next"]
|
||
> A --> E["生命周期管理<br/>Abort / Deadline"]
|
||
> ```
|
||
>
|
||
> Context 贯穿整个请求生命周期——从接收到响应,它是最核心的桥梁。
|
||
|
||
#### 第 27 题
|
||
|
||
【单选题】后端处理完逻辑后,想给前端返回一个标准的JSON格式响应(包含状态码200),以下哪句代码是正确的?(5分)
|
||
|
||
A. c.String(200, "{\"status\": \"success\"}")
|
||
|
||
B. c.JSON(http.StatusOK, gin.H{"message": "success", "data": myData})
|
||
|
||
C. c.XML(200, myData)
|
||
|
||
D. c.HTML(http.StatusOK, "index.html", nil)
|
||
|
||
**正确答案:** B
|
||
|
||
**作答答案:** B
|
||
|
||
> [!code Go] Gin 响应方法一览
|
||
>
|
||
> | 方法 | 适用场景 | Content-Type |
|
||
> |------|---------|-------------|
|
||
> | `c.JSON()` | JSON 响应 | application/json |
|
||
> | `c.String()` | 纯文本 | text/plain |
|
||
> | `c.XML()` | XML 响应 | application/xml |
|
||
> | `c.Data()` | 自定义类型 | 可指定任意类型 |
|
||
> | `c.File()` | 下载文件 | 自动推断 |
|
||
|
||
---
|
||
|
||
### 四、Cookie、Session 与 JWT 认证
|
||
|
||
> [!abstract] 本节覆盖:第 7、9、10、11、21 题
|
||
>
|
||
> 这三种方案解决了同一个问题——在**无状态**的 HTTP 上建立**有状态**的会话。各有优劣,适用于不同场景。
|
||
|
||
#### 第 7 题
|
||
|
||
【单选题】在使用 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
|
||
|
||
> [!tip] Cookie 安全属性对照表
|
||
>
|
||
> | 属性 | 作用 | 推荐生产环境值 |
|
||
> |------|------|---------------|
|
||
> | `HttpOnly` | JS 无法读取,防 XSS | `true` |
|
||
> | `Secure` | 仅 HTTPS 传输,防抓包 | `true` |
|
||
> | `SameSite` | 防 CSRF,限制第三方携带 | `Strict` 或 `Lax` |
|
||
> | `Max-Age` | 有效期(秒),0=删除 | 按需设置 |
|
||
|
||
#### 第 9 题
|
||
|
||
【单选题】关于 Gin 框架中 Session 的运用,以下说法正确的是?(5分)
|
||
|
||
Session 的本质是服务器在后端存储了用户状态,只把一个无意义的、随机的 SessionID 通过 Cookie 发给前端保存。Gin 核心库为了保持轻量,并没有内置 Session,推荐使用第三方中间件。
|
||
|
||
A. Gin 官方核心库自带了完善的 Session 管理器,无需引入第三方包。
|
||
|
||
B. Session 数据默认以明文形式保存在客户端的浏览器中。
|
||
|
||
C. Session 的本质是服务器在后端存储用户状态,只把 SessionID 发给前端。
|
||
|
||
D. 只要用户关闭浏览器页面,服务器端的 Session 数据就会被自动删除。
|
||
|
||
**正确答案:** C
|
||
|
||
**作答答案:** C
|
||
|
||
#### 第 10 题
|
||
|
||
【单选题】JWT 解决了传统 Session 架构的什么核心痛点?(5分)
|
||
|
||
JWT 是"无状态"的,服务端不需要存储会话记录就能验证其有效性,从而解决了传统 Session 在服务器集群环境下的共享与同步问题。
|
||
|
||
A. JWT 对用户密码进行了极强的加密,即便被黑客拦截也绝对安全。
|
||
|
||
B. 传统 Session 强依赖于服务端内存或集中存储,JWT 是"无状态"的。
|
||
|
||
C. JWT 生成的字符串非常短,比传统的 SessionID 更省网络带宽。
|
||
|
||
D. JWT 只能在 Go 语言和前端之间解析,跨语言安全性极高。
|
||
|
||
**正确答案:** B
|
||
|
||
**作答答案:** B
|
||
|
||
#### 第 11 题
|
||
|
||
【单选题】当中间件成功解析并验证了前端请求头中的 JWT 令牌后,最佳的代码实践是?(5分)
|
||
|
||
中间件是查票员,查验完 JWT 后,通过 c.Set() 把乘客信息存储在当前 HTTP 请求的生命周期上下文中,后续函数可以安全、隔离地拿到用户信息。
|
||
|
||
A. 将解析出的用户 ID 保存到全局变量中。
|
||
|
||
B. 使用 c.Redirect 将请求重定向,并在 URL 参数里带上用户 ID。
|
||
|
||
C. 使用 c.Set("userID", id) 将用户信息存储在上下文中。
|
||
|
||
D. 将用户信息打包成 JSON 写回到响应体中,结束请求。
|
||
|
||
**正确答案:** C
|
||
|
||
**作答答案:** C
|
||
|
||
#### 第 21 题
|
||
|
||
【单选题】当用户首次访问网站时,服务器通常会在HTTP响应中使用哪个字段来向客户端存储session ID?(5分)
|
||
|
||
A. Cache-Control
|
||
|
||
B. Set-Cookie
|
||
|
||
C. Content-Length
|
||
|
||
D. User-Agent
|
||
|
||
**正确答案:** B
|
||
|
||
**作答答案:** B
|
||
|
||
> [!summary] 认证方案横向对比
|
||
>
|
||
> | 维度 | Session (Cookie) | JWT |
|
||
> |------|-----------------|-----|
|
||
> | 存储位置 | 服务端(内存/Redis) | 客户端(自行保管) |
|
||
> | 服务端压力 | 需维护会话,集群需共享 | 无状态,天然分布式友好 |
|
||
> | 主动注销 | 可从服务端删除 ✅ | 需黑名单/缩短有效期 ⚠️ |
|
||
> | token 大小 | Cookie 仅传 SessionID(很小) | JWT 本身较长(含 payload) |
|
||
> | 适用场景 | 后台管理系统、需要频繁踢人 | 移动端 API、微服务、SSO |
|
||
|
||
---
|
||
|
||
### 五、CORS 跨域
|
||
|
||
> [!abstract] 本节覆盖:第 22~26 题
|
||
>
|
||
> 跨域是现代全栈开发的"必修课"。理解同源策略、预检请求和各类解决方案,才能游刃有余地应对生产环境。
|
||
|
||
#### 第 22 题
|
||
|
||
【单选题】当你的项目正式上线:前端部署在 www.myweb.com,Go 后端 API 部署在 api.myweb.com,此时前端又碰到了跨域问题。生产环境下,业界最标准、最推荐的解决方式是什么?(5分)
|
||
|
||
A. 在 Nginx 配置中开启反向代理,让前端和后端走同一个域名,彻底消灭跨域。或者在 Go 后端通过代码严格配置 CORS 中间件,允许指定域名的访问。
|
||
|
||
B. 要求所有用户在浏览器中安装一个允许跨域的插件。
|
||
|
||
C. 把 Vue/React 前端代码编译进 Go 语言的二进制文件里,打包成一个单体服务,从而避免跨域。
|
||
|
||
D. 在前端代码中使用 JSONP 的方式来绕过浏览器的同源策略。
|
||
|
||
**正确答案:** A
|
||
|
||
**作答答案:** A
|
||
|
||
#### 第 23 题
|
||
|
||
【单选题】当我们的全栈项目要部署到生产环境时,通常会在最外层挡一个 Nginx,并将外部请求"反向代理"给内部的 Go 应用程序。为什么要多此一举,而不是直接让 Go 监听 80/443 端口对外提供服务?(5分)
|
||
|
||
A. 因为 Go 语言标准库的 net/http 性能太差,无法承受高并发,必须靠 Nginx 提速。
|
||
|
||
B. 因为 Go 程序无法处理 HTTPS 证书的加密解密。
|
||
|
||
C. Nginx 专精于静态文件的高效分发,并且能够作为统一入口隐藏后端真实 IP,还能轻松实现请求路由分发、负载均衡和限流,极大提升了整体架构的健壮性和安全性。
|
||
|
||
D. 这是操作系统的强制要求,所有的 Web 流量必须先经过 Nginx 才能到达用户态程序。
|
||
|
||
**正确答案:** C
|
||
|
||
**作答答案:** C
|
||
|
||
#### 第 24 题
|
||
|
||
【单选题】当你在前端用 axios 发送一个携带了自定义请求头的 POST 请求时,浏览器实际上发了两次请求。这个 OPTIONS 请求是做什么的?(5分)
|
||
|
||
A. 它是用来试探后端服务器是否死机的心跳检测。
|
||
|
||
B. 这是浏览器的 CORS"预检请求 (Preflight)"。因为带有自定义头或非普通类型的请求属于"复杂请求",浏览器必须先发个 OPTIONS 问问后端:"我带了特殊的头,你可以接受吗?"
|
||
|
||
C. 这是 axios 库本身的 bug 导致的重复请求。
|
||
|
||
D. 它是用来提前把数据进行加密的握手请求。
|
||
|
||
**正确答案:** B
|
||
|
||
**作答答案:** B
|
||
|
||
#### 第 25 题
|
||
|
||
【单选题】在前端开发阶段,我们通常会在 vite.config.js 或 vue.config.js 中配置 proxy 代理来解决跨域问题。这个 Proxy 底层的运作逻辑是什么?(5分)
|
||
|
||
A. 它修改了浏览器的安全设置,让浏览器临时关闭了同源策略。
|
||
|
||
B. 它修改了前端发出的 HTTP 请求的 Origin,伪装成和后端同源。
|
||
|
||
C. 前端代码实际上把请求发给了 Vite 自带的本地 Node.js 开发服务器,开发服务器再把请求转发给真正的 Go 后端,最后把结果原样返回给前端。
|
||
|
||
D. 它自动为后端的 Go 代码注入了处理跨域的中间件。
|
||
|
||
**正确答案:** C
|
||
|
||
**作答答案:** C
|
||
|
||
#### 第 26 题
|
||
|
||
【单选题】前后端分离开发时,前端调用后端经常会遇到可怕的 CORS 报错。导致跨域报错的根本原因是什么?它为了解决什么痛点而存在?(5分)
|
||
|
||
A. 因为 HTTP 协议不允许不同端口之间通信,这是底层网络协议的限制。
|
||
|
||
B. 因为后端的 Go 代码默认开启了防火墙,拦截了来自其他端口的请求。
|
||
|
||
C. 这是浏览器的"同源策略 (Same-Origin Policy)"导致的,为了防止恶意网站窃取用户的敏感数据(如 Cookie),浏览器强制拦截了跨域的响应。
|
||
|
||
D. 因为前端框架(如 Vue/React)的安全机制,阻止了发起非同源的请求。
|
||
|
||
**正确答案:** C
|
||
|
||
**作答答案:** C
|
||
|
||
> [!tip] 跨域三大解决方案全景图
|
||
>
|
||
> ```mermaid
|
||
> graph LR
|
||
> A["跨域问题"] --> B["开发环境"]
|
||
> A --> C["生产环境"]
|
||
> B --> B1["Vite Proxy<br/>Node 中转"]
|
||
> C --> C1["Nginx 反向代理<br/>同域名免跨域"]
|
||
> C --> C2["CORS 中间件<br/>服务端放白名单"]
|
||
> ```
|
||
>
|
||
> **Go 后端 CORS 中间件示例:**
|
||
>
|
||
> ```go
|
||
> import "github.com/gin-contrib/cors"
|
||
>
|
||
> r := gin.New()
|
||
> r.Use(cors.New(cors.Config{
|
||
> AllowOrigins: []string{"https://www.myweb.com"},
|
||
> AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
|
||
> AllowHeaders: []string{"Origin", "Content-Type", "Authorization"},
|
||
> AllowCredentials: true,
|
||
> MaxAge: 12 * time.Hour,
|
||
> }))
|
||
> ```
|
||
|
||
---
|
||
|
||
### 六、响应与状态码
|
||
|
||
> [!abstract] 本节覆盖:第 28、29 题(续)
|
||
>
|
||
> 正确使用 HTTP 状态码是专业后端的基本素养。
|
||
|
||
#### 第 28 题
|
||
|
||
【单选题】前端向你的Go后端发送了一个请求,但忘了带上认证Token。作为标准化的后端,你应该返回哪个状态码来明确告知他"你没有权限/未认证"?(5分)
|
||
|
||
A. 400 Bad Request
|
||
|
||
B. 401 Unauthorized
|
||
|
||
C. 404 Not Found
|
||
|
||
D. 500 Internal Server Error
|
||
|
||
**正确答案:** B
|
||
|
||
**作答答案:** B
|
||
|
||
> [!note] 常用 HTTP 状态码速查
|
||
>
|
||
> | 状态码 | 含义 | 何时使用 |
|
||
> |--------|------|---------|
|
||
> | **200** | OK | 请求成功 |
|
||
> | **201** | Created | 资源创建成功 |
|
||
> | **301** | Moved Permanently | 永久重定向 |
|
||
> | **304** | Not Modified | 资源未变化(缓存) |
|
||
> | **400** | Bad Request | 请求参数错误 |
|
||
> | **401** | Unauthorized | 未认证/Token失效 |
|
||
> | **403** | Forbidden | 已认证但无权限 |
|
||
> | **404** | Not Found | 资源不存在 |
|
||
> | **429** | Too Many Requests | 触发限流 |
|
||
> | **500** | Internal Server Error | 服务端内部错误 |
|
||
|
||
---
|
||
|
||
### 七、Git 版本控制
|
||
|
||
#### 第 30 题
|
||
|
||
【单选题】在Git中,如何临时保存当前工作进度以便切换到其他分支进行工作?(5分)
|
||
|
||
A. 使用 git stash apply 命令
|
||
|
||
B. 使用 git commit 命令
|
||
|
||
C. 使用 git stash 命令
|
||
|
||
D. 使用 git merge 命令
|
||
|
||
**正确答案:** C
|
||
|
||
**作答答案:** C
|
||
|
||
> [!code Bash] git stash 常用命令
|
||
>
|
||
> ```bash
|
||
> git stash save "WIP: 修复bug" # 保存当前改动
|
||
> git stash list # 查看保存列表
|
||
> git stash pop stash@{0} # 恢复并删除栈顶记录
|
||
> git stash apply stash@{0} # 恢复但不删除(保留记录)
|
||
> git stash drop stash@{0} # 删除指定记录
|
||
> git stash drop # 删除最近一条(快捷写法)
|
||
> ```
|
||
|
||
---
|
||
|
||
## 关联笔记
|
||
|
||
- [[hhs/EXAM/]]
|
||
- [[Go/Gin框架/中间件]]
|
||
- [[Web/CORS跨域]]
|
||
- [[Web/JWT认证]]
|