5.3 KiB
id, title, type, domain, stack, tags, level, related, week
| id | title | type | domain | stack | tags | level | related | week | |||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| backend.gin.interview | Gin 面试题 | interview | backend |
|
|
intermediate |
|
|
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。