好特卖-2025实习-Golang面经
十一点开始面试,先甩了我一个word来做笔试题,写了快一个小时才开始面试。
都是Ai整理的,可能有错误。
- 为什么要写高性能网关?
- 问:你为什么会去写高性能的网关系统?这是你自己的项目吗?
- 答:想把学到的技术落到一个“完整且有意义”的产品化开源项目上。观察到 Nginx 性能强但链路和配置较复杂,于是用 Go 在高并发场景做一个可复用的高性能网关。属于自研,参考了常见架构模式后自己实现。
- JWT / OAuth 相关
- 问:你用的 JWT 是哪个版本?(面试官口误/写法混乱,实际意图:所用 JWT 库版本)
- 答:具体库版本记不清。系统里实现的是无状态鉴权(候选人口中混用了“JWT / OAuth 2.0”的说法)。流程:每个租户有唯一密钥;客户端发起请求时在 HTTP Header 写入密钥;请求到网关后,网关调用鉴权模块,从数据库读取用户信息核验;校验通过后生成并返回 Token;后续所有代理请求在 Header 附带该 Token,网关侧都会先鉴权再转发。
- 实习动机与现状
- 问:你现在实习想获得什么?你现在的实习状态是?
- 答:想多接触“高并发业务开发”,不仅做“轮子”。目前仍在职,但准备离开(对方无转正通道),将专注找工作。
(下面是反问夹杂问题)
- 业务与技术栈(对方公司)
- 问:我们做什么业务?Go 在系统中负责哪部分?
- 答(面试官说明):公司做 AI 在企业的落地,使用“大模型”,算法侧用 Python,工程与系统主体用 Go,主要通过接口/服务调用完成对接。
- 反问(候选人):为什么整个系统都采用 Go?
- 答(面试官):简单、跨平台编译方便;与 Java/Python相比差异没那么大,选型更偏工程效率。
- 数据结构与算法追问
- 问:图的最短路有什么印象?用什么算法?
- 答:提到 DFS/BFS;最短路说到 Dijkstra。
- 问:Dijkstra 怎么做?
- 答:印象是用堆辅助(细节记不清)。
- 问:堆的性质、如何快速定位子节点?
- 答:只笼统提到最大堆/最小堆,未能展开性质与下标关系。
- 问:快速排序时间复杂度与原理?
- 答:时间复杂度 O(n log n),原理一时想不起来。
- 问:你写的“栈”这题时间复杂度?
- 答:O(1),因为只操作“尾元素”,不需要遍历底层数组
- 前端经历与“React”
- 问:你用过 React 吗?(面试官提到“瑞艾特”)
- 答:没用过 React,之前说的是 Raft 共识算法。前端实操主要是 Vue 写过网关项目的后台管理最小功能;在实习中用 JS 做过表单提交,整体偏简单。
- 到岗与实习时长
- 问:你现在人在南京吗?多久能入职?能实习多久?
- 薪资期望
- 问:实习与转正的薪资期望?
- 反问与面评建议
- 问:作为应届生,我最需要提升哪方面?
- 答(面试官建议):多写程序、多做项目;你后面代码题完成度还可以,但前面静态题错误较多,主要是代码量不够导致理解不牢。两条路都行:多刷题 or 多做工程,增加“见过的场景”。
---
下面是笔试题(只有答案)
收到~我把这份笔试题按「题目|原答案|纠正/标准答案」理了一遍;原文里明显的转写/语法问题我也顺手修了。没给出原始代码导致无法精确跑出的几题,我会标注“待核实”,并给出通用的正确做法,方便你替换。(每题后附原文出处)
1) 简述 Session
原答案(摘录):Session 是“在客户端和服务端之间保存会话连接”的方式;用于“提高连接复用能力,使连接在一定时间内有效”;SessionID 返回给客户端,通常放在 Cookie 里。纠正/标准答案:
- Session 是服务端保存的用户会话状态(登录态、用户偏好等),用来在多次 HTTP 请求间识别同一用户。
- 客户端仅保存Session ID(多为 Cookie 存储),请求时带上以便服务端查回会话。
- “提高连接复用能力”不准确;连接复用/Keep-Alive属于TCP/HTTP 连接层,与 Session 的业务会话状态是两回事。
- 常见风险:会话固定/劫持,需配合
HttpOnly
、Secure
、CSRF 防护、过期/刷新策略、存储(内存/Redis)等。
2) OOP 三大特性
原答案:封装、继承、多态。纠正/标准答案:答案正确,可在笔试中补一句:多态=“面向接口编程”,运行期按动态类型分派。
3) Go 里“近似类”的实现
原答案(摘录):Go 没有继承,采用组合;示例 type cat
+ 方法。纠正/标准答案(要点 + 更佳示例):
- Go 没有类与继承,但有类型 + 方法、结构体嵌入(组合/“伪继承”)和接口多态。
- 示例:
type Animal interface{ Speak() string } type Cat struct{ Voice string } func (c Cat) Speak() string { return c.Voice } // 结构体嵌入(复用 Cat 的字段/方法) type RobotCat struct{ Cat }
4) 什么是 Stack Overflow
原答案:栈空间溢出。纠正/标准答案:正确。常见诱因:无限/过深递归、超大栈上数组;解决:改迭代/尾递归、移堆上、调小栈帧。
5) “会用日志吗”
原答案(摘录):单例持有文件描述符,写入运行信息/错误。纠正/标准答案(更规范要点):
- 选型:标准库
log
或结构化库(zap/zerolog)。 - 级别:DEBUG/INFO/WARN/ERROR;带字段(traceID、uid)。
- 轮转:配合按大小/时间切分;并发安全;不要
defer
在热路径频繁打开/关闭文件。 - 生产:异步写、落盘失败降级、敏感信息脱敏。
6)(提示词改写题)
原文(摘录):把 sj.Get(...).Int()
改成 bitableRepository.GetCell(rowSj, "是否已转审","String")
,并说明 5 点修改。纠正/标准答案:原说明已清楚;注意方法返回值类型与错误处理,避免静默丢错。
7) 代码结果(goroutine 计数)
原答案:
gorountines:2 gorountines:1 gorountines:0
状态:待核实(原题未给代码)。通用说明:runtime.NumGoroutine()
的输出取决于启动/结束时机与调度器时序;若题意是“开启两个子协程并等待全部退出”,常见打印顺序为 1→3→1(主→创建两协程→回到仅主),不一定是 2→1→0。
8) 计算代码结果(数组推演)
原答案:最终 {1,2,3,7,14}
(含中间推演表)。状态:待核实(无源码)。建议写法:若是“按规则累加/翻倍”的动态推演题,请把转移方程或循环与下标变化补齐,否则很难客观给分。
9) 代码结果
原答案:4+6=10
。状态:待核实(无源码)。若题为字符串拼接或位运算,结果可能不同;需以题面为准。
10) 补全代码(回文数判断)
原答案(摘录):“取出 i 的每一位拼到 j;判断 n==j
;调用 IsSymmetry(n)
。”纠正/标准答案(可直接替换):
func isPalindromeNumber(n int) bool { if n < 0 { return false } rev, x := 0, n for x > 0 { rev = rev*10 + x%10 x /= 10 } return n == rev }
11) Go 的“缺省参数”
原答案(摘录):通过 slice 或 “...” 缺省参数;示例函数签名语法错误(func() quankang
、.
)。纠正/标准答案:
- Go 不支持默认/可选参数;常见替代:可变参数 / Functional Options。
- 正确示例(variadic):
func Join(sep string, parts ...string) string { if len(parts) == 0 { return "" } b := strings.Builder{} for i, s := range parts { if i > 0 { b.WriteString(sep) } b.WriteString(s) } return b.String() }
- 正确示例(Functional Options):
type Server struct{ Addr string; Timeout time.Duration } type Option func(*Server) func WithAddr(a string) Option { return func(s *Server){ s.Addr = a } } func WithTimeout(d time.Duration) Option { return func(s *Server){ s.Timeout = d } } func NewServer(opts ...Option) *Server { s := &Server{Addr: ":8080", Timeout: 3*time.Second} for _, opt := range opts { opt(s) } return s }
12) 链式调用
原答案(摘录):cls.getCell().toString()
;CusData
未定义;toString
直接 string(ce.data)
。纠正/标准答案(可直接替换):
type CusData []byte type cell struct{ data CusData } func (ce *cell) ToString() string { return string(ce.data) } type cls struct{ acell *cell } func (c *cls) GetCell() *cell { return c.acell } // 链式使用 c := (&cls{acell: &cell{data: []byte("hello")}}).GetCell().ToString() // "hello"
约定:导出方法用大写开头;链式=返回下一步可调用的对象。
13) HTTP 请求(头与 Body)
原答案(摘录):header:=map[string]string
未初始化;http.POST
非标准库;defer resp.Close()
不对;resp.body()
不存在。纠正/标准答案(标准库 net/http
版本):
type Body struct{ Data string } func httpRequest(b *Body, url string) (string, error) { req, err := http.NewRequest(http.MethodPost, url, strings.NewReader(b.Data)) if err != nil { return "", err } // 写入头(鉴权/自定义等) req.Header = make(http.Header) req.Header.Set("Content-Type", "text/plain") // 依实际场景修改 // req.Header.Set("Authorization", "Bearer <token>") resp, err := http.DefaultClient.Do(req) if err != nil { return "", err } defer resp.Body.Close() data, err := io.ReadAll(resp.Body) if err != nil { return "", err } if resp.StatusCode < 200 || resp.StatusCode >= 300 { return "", fmt.Errorf("http %d: %s", resp.StatusCode, string(data)) } return string(data), nil }
14) 「打印题目」(限制每个元素出现次数 ≤ n)
原答案 A:试图用 posMap
维护位置并“搬移切片”,有语法错误 back.
且逻辑多余。原答案 B:仅用 cntMap
计数并追加,思路正确。纠正/标准答案(保序 & 通过常见样例):
func outputNth(slice []int, n int) []int { if n <= 0 { return []int{} } cnt := make(map[int]int) ans := make([]int, 0, len(slice)) for _, x := range slice { if cnt[x] < n { ans = append(ans, x) cnt[x]++ } } return ans } // 输入: [20 37 20 20 21], n=2 输出: [20 37 20 21]
复杂度 O(n),空间 O(k)(k 为不同元素个数)。
15) 栈实现
原答案(摘录):pop
读 data[s.size]
;push
写 data[s.size+1]
且未递增 size
;越界条件不严谨。纠正/标准答案(两种写法):
这题必须使用固定数组,不允许用slice等
A. 固定数组 + 大小指针
type stack struct { data [50]int size int // 栈内元素个数,范围 [0, len(data)] } func (s *stack) empty() bool { return s.size == 0 } func (s *stack) push(v int) error { if s.size == len(s.data) { return errors.New("full") } s.data[s.size] = v s.size++ return nil } func (s *stack) pop() (int, error) { if s.empty() { return 0, errors.New("empty") } s.size-- return s.data[s.size], nil }