--- id: backend.gin.interview title: Gin 面试题 type: interview domain: backend stack: [gin] tags: [Gin, Web, 面试] level: intermediate related: [backend.gin.routing-binding, backend.gin.middleware-auth, backend.gin.scenario.unified-api-response] week: [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 怎么获取路径参数、查询参数和请求体? **参考答案**:路径参数 `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`。 ### 【基础】RESTful 接口的状态码怎么选? **参考答案**:查询成功 200、创建成功 201、删除成功 204、参数错误 400、未认证 401、已认证但无权限 403、不存在 404、状态冲突(如重复创建)409、请求过多 429、服务器错误 500、下游不可用 502/503。 ## 进阶 ### 【进阶】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)传给后台任务。 ### 【进阶】如何设计统一的错误处理? **参考答案**:定义业务错误类型(HTTP 状态 + 业务码 + 用户提示),处理函数通过 `c.Error(err)` 记录错误并返回,由一个错误中间件统一转换成标准响应;未知错误和 panic 统一返回通用提示,详细信息带请求 ID 写日志。详见本技术栈的场景文档。 ### 【进阶】Gin 的路由为什么快? **参考答案**:Gin 使用 httprouter 风格的前缀树(radix tree),每个 HTTP 方法一棵树,按路径公共前缀压缩存储,匹配时间与路径长度相关,基本不受路由数量影响;同时 `Context` 用对象池减少内存分配。 ## 场景题 ### 【场景题】接口被恶意刷,怎么在 Gin 里做限流? **参考答案**:单实例用令牌桶(`golang.org/x/time/rate`),按用户 ID 或 IP 维护一个限流器 map(注意定期清理);多实例部署需要全局限流,用 Redis(固定窗口 `INCR+EXPIRE` 或滑动窗口 ZSet、Lua 脚本保证原子)。超限返回 429 并带 `Retry-After`。更外层可以在 Nginx 或网关做 IP 级限流,应用层做用户级、接口级限流。本计划第 3 周项目一实现过。 ### 【场景题】一个上传接口偶尔导致服务内存暴涨,怎么处理? **参考答案**:限制请求体大小(`http.MaxBytesReader` 或 Gin 的 `MaxMultipartMemory`,Nginx 的 `client_max_body_size`);大文件流式处理而不是一次读入内存;校验文件类型和大小;上传改成前端直传对象存储(预签名 URL),后端只接收回调。 ### 【场景题】怎么给 Gin 服务写接口测试? **参考答案**:把路由组装放在一个函数里(如 `New(deps) *gin.Engine`),测试时注入内存版依赖;用 `httptest.NewRecorder()` 和 `r.ServeHTTP(w, req)` 直接调用,不启动端口;用表格驱动覆盖成功、参数错误、未登录、无权限、不存在、内部错误等情况;数据库相关的用集成测试单独跑。示例见 [examples/unified-api](examples/unified-api/)。