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

23 KiB
Raw Permalink Blame History

tags, create time
tags create time
exam
gin
go
http
jwt
cors
middleware
session
cookie
web
2026-05-14 18:30

金山考试 - 武科大服务端 - 第三次课

概述

武科大服务端第三次课的考试错题整理与知识点拓展。共 30 道单选题,满分 150 分,涉及 HTTP 协议基础、Gin 框架中间件、Cookie/Session/JWT 认证、CORS 跨域、Nginx 反向代理等核心后端开发知识。

得分:145/150(仅第 12 题出错)


我的错题

第 12 题 — Gin 全局注册中间件

你的作答: D · 正确答案: A

// 错误答案 ❌
router.Middleware(middleware)

// 正确答案 ✔
router.Use(middleware)

[!tip] 考点解析

Gin 使用 Use() 方法注册全局中间件,参数接受一个或多个 HandlerFunc:

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 是无状态的——每次请求都是一次"失忆"的新对话。为了维持用户登录态,我们需要额外的机制来"记住"用户是谁:

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

完整流程:

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] 查询参数解析对比

// 方式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() — 中间件的双向控制

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 常用绑定方法

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 在中间间共享数据

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 中间件实战模板

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 一图看懂

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] 跨域三大解决方案全景图

graph LR
  A["跨域问题"] --> B["开发环境"]
  A --> C["生产环境"]
  B --> B1["Vite Proxy<br/>Node 中转"]
  C --> C1["Nginx 反向代理<br/>同域名免跨域"]
  C --> C2["CORS 中间件<br/>服务端放白名单"]

Go 后端 CORS 中间件示例:

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 常用命令

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                   # 删除最近一条(快捷写法)

关联笔记