Files
zhuangang/topics/backend/gin/interview.md
T

7.3 KiB
Raw Blame History

id, title, type, domain, stack, tags, level, related, week
id title type domain stack tags level related week
backend.gin.interview Gin 面试题 interview backend
gin
Gin
Web
面试
intermediate
backend.gin.routing-binding
backend.gin.middleware-auth
backend.gin.scenario.unified-api-response
1
2
3
16

Gin 面试题

在本计划里的体现:todo-api(第 1~2 周)、task-board(第 3 周)。

基础

【基础】Gin 和标准库 net/http 是什么关系?

参考答案:Gin 的 Engine 实现了 http.Handler 接口,底层仍然由 net/http 负责监听和连接管理。Gin 增加了基于前缀树的高性能路由、gin.Context(封装请求和响应、参数获取、绑定、中间件间传值)、中间件链和常用工具(JSON 渲染、校验)。所以 Gin 可以和任何标准库中间件、httptest 一起使用。

追问:Gin 的 *gin.Context 和标准库的 context.Context 是什么关系?→ gin.Context 是 Gin 自己的请求上下文(参数、响应、中间件链),它也实现了 context.Context 接口;向下游传递取消信号时,用 c.Request.Context() 更明确。

【基础】Gin 怎么获取路径参数、查询参数和请求体?

参考答案:路径参数 c.Param("id");查询参数 c.Query("k") / c.DefaultQuery("k", "默认");请求体用绑定 c.ShouldBindJSON(&req),查询参数也可以 c.ShouldBindQuery(&q) 绑定到结构体。绑定时根据结构体的 binding 标签做校验。

追问:ShouldBind 和 Bind 的区别?→ Bind* 失败时自动返回 400 并中止,ShouldBind* 只返回错误、由你决定如何响应,一般统一用后者。

【基础】中间件的执行顺序是怎样的?c.Next() 和 c.Abort() 做什么?

参考答案:按注册顺序依次执行,形成洋葱模型:c.Next() 之前的代码按顺序执行,之后的代码按相反顺序执行。c.Next() 立即执行后续的处理函数;c.Abort() 阻止后续处理函数执行,但当前函数会继续执行完,所以通常 Abort 后紧跟 return。

追问:在中间件里 c.Abort() 之后还需要 return 吗?→ 需要。Abort 只是阻止后续处理函数执行,当前函数后面的代码仍会继续执行。

【基础】RESTful 接口的状态码怎么选?

参考答案:查询成功 200、创建成功 201、删除成功 204、参数错误 400、未认证 401、已认证但无权限 403、不存在 404、状态冲突(如重复创建)409、请求过多 429、服务器错误 500、下游不可用 502/503。

追问:参数校验失败用 400 还是 422?→ 两种都常见,团队统一即可;关键是错误响应格式统一、带上具体的字段错误信息。

进阶

【进阶】JWT 和 Session 有什么区别?JWT 怎么实现退出登录?

参考答案:Session 把登录状态存在服务端(内存或 Redis),客户端只拿一个 Session ID,服务端可以随时让它失效,但需要共享存储。JWT 把用户信息签名后交给客户端,服务端只验签,无状态、易扩展,但签发后在过期前一直有效。退出登录 / 改密码的方案:短有效期访问令牌(如 15 分钟)+ 刷新令牌;服务端维护用户的令牌版本号或黑名单(存 Redis),校验时比对。

追问:令牌存 localStorage 还是 Cookie?→ localStorage 怕 XSS 窃取;httpOnly Cookie 防 XSS 但要防 CSRF(SameSite、CSRF Token)。

【进阶】为什么在 goroutine 里使用 gin.Context 要先 c.Copy()?

参考答案:Gin 用对象池复用 Context,请求处理结束后它会被重置给下一个请求使用。如果后台 goroutine 还持有原来的 c,读到的可能是别的请求的数据。c.Copy() 生成只读副本。更好的做法是只把需要的值(用户 ID、请求 ID、c.Request.Context() 派生的 context)传给后台任务。

追问:goroutine 里需要请求的取消信号怎么办?→ 后台任务通常不应该跟随请求取消(请求结束就被取消),用 context.WithoutCancel(c.Request.Context()) 或新的 context 并设置独立超时。

【进阶】如何设计统一的错误处理?

参考答案:定义业务错误类型(HTTP 状态 + 业务码 + 用户提示),处理函数通过 c.Error(err) 记录错误并返回,由一个错误中间件统一转换成标准响应;未知错误和 panic 统一返回通用提示,详细信息带请求 ID 写日志。详见本技术栈的场景文档。

追问:错误码和 HTTP 状态码是什么关系?→ HTTP 状态码表达大类(客户端错误 / 服务端错误),业务错误码表达具体原因,方便前端展示和排查;不要所有错误都返回 200。

【进阶】Gin 的路由为什么快?

参考答案:Gin 使用 httprouter 风格的前缀树(radix tree),每个 HTTP 方法一棵树,按路径公共前缀压缩存储,匹配时间与路径长度相关,基本不受路由数量影响;同时 Context 用对象池减少内存分配。

追问:路由冲突(例如 /users/:id 和 /users/new)会怎样?→ 新版本的 Gin 已经支持静态路由和参数路由共存,静态路由优先;旧版本会在注册时 panic。以使用的版本为准,写测试验证。

场景题

【场景题】接口被恶意刷,怎么在 Gin 里做限流?

参考答案:单实例用令牌桶(golang.org/x/time/rate),按用户 ID 或 IP 维护一个限流器 map(注意定期清理);多实例部署需要全局限流,用 Redis(固定窗口 INCR+EXPIRE 或滑动窗口 ZSet、Lua 脚本保证原子)。超限返回 429 并带 Retry-After。更外层可以在 Nginx 或网关做 IP 级限流,应用层做用户级、接口级限流。本计划第 3 周项目一实现过。

追问:多实例部署时限流怎么做?→ 单机限流(x/time/rate)只能限制单个实例;全局限流要用 Redis(滑动窗口 / 令牌桶 + Lua 原子执行)或在网关层统一做。

【场景题】一个上传接口偶尔导致服务内存暴涨,怎么处理?

参考答案:限制请求体大小(http.MaxBytesReader 或 Gin 的 MaxMultipartMemory,Nginx 的 client_max_body_size);大文件流式处理而不是一次读入内存;校验文件类型和大小;上传改成前端直传对象存储(预签名 URL),后端只接收回调。

追问:大文件上传怎么做更好?→ 前端直传对象存储(服务端签发预签名 URL),服务端只处理元数据;需要服务端处理时用流式读取,不要整个读进内存。

【场景题】怎么给 Gin 服务写接口测试?

参考答案:把路由组装放在一个函数里(如 New(deps) *gin.Engine),测试时注入内存版依赖;用 httptest.NewRecorder() 和 r.ServeHTTP(w, req) 直接调用,不启动端口;用表格驱动覆盖成功、参数错误、未登录、无权限、不存在、内部错误等情况;数据库相关的用集成测试单独跑。示例见 examples/unified-api。

追问:测试需要数据库怎么办?→ 业务逻辑通过接口依赖存储层,单元测试用假实现;集成测试用真实数据库(测试库或容器),每个测试使用独立的数据并清理。