深浅色
项目素材库
面试时讲项目要能说出细节。这里存简历上写不下、但面试会被追问的内容。
详细版见
3.projects/EinoTalk/复盘.md与3.projects/pivot-workbench/复盘.md,这里只放「开口就能说」的部分。
项目:EinoTalk
- 一句话介绍:基于 Go + React + TypeScript 的全栈即时通讯应用,在完整 IM 闭环(私聊/群聊/会话/联系人/文件)之上集成 CloudWeGo Eino AI 助手,支持流式回复、ReAct 工具调用与滚动摘要记忆。
- 技术栈:Go 1.25 / Gin / GORM / MySQL / Redis / Gorilla WebSocket / JWT / Kafka(可选)/ CloudWeGo Eino / Prometheus;前端 React 18 + TypeScript + Vite + Zustand + Ant Design;Docker Compose(含多实例编排)+ k6 压测。
- 我的角色 / 负责模块:
- AI 助手流式链路:Eino ReAct 编排、三级降级、80ms/32 字符增量聚合、delta/done 帧协议、前端打字机渲染
- 多实例集群一致性:在线路由表 + 实例间消息总线、跨实例定向投递、Kafka 分区键与 Producer 配置修正
- 稳定性:优雅关停(done 信号替代 close(channel))、Redis 前缀白名单清理
- 工程质量:dispatch.go 测试补全、CI 接入 Redis 服务容器、构建期注入前端后端地址、Go 版本对齐
- 背景与难点:接手时基线已能单机演示,但存在三个会击穿面试信任的硬伤——多实例部署静默丢消息、关停触发 send on closed channel panic、核心分发链路零测试且文档宣称的能力与代码不一致。难点在于这些都不是「加功能」,而是要在不破坏既有 IM 闭环的前提下把「纸面分布式」变成真分布式。
- 方案与取舍:
- 流式推帧:逐 token 推 → 前端节流 vs 服务端 80ms/32 字符双阈值聚合。选后者,代价是首字延迟最多 +80ms,换来帧数量级下降。
- 跨实例投递:全实例广播 vs 路由表 + 定向总线。选后者并用 Redis 承载路由表(不引入 etcd/ZK),路由失效降级为广播。代价是路由有过期窗口。
- 关停:加锁 + 标志位 vs done channel + context。选后者,彻底消除 close panic 与 goroutine 泄漏。
- Agent 记忆:不引入向量库,用「短期窗口(私聊 10 / 群聊 20 条)+ 滚动摘要(≤300 字,Redis 1h + 5min 节流锁)」;预留 pgvector 升级路径。
- 量化结果:
- 压测(4 核 8G、channel 模式、单实例、不含 LLM):100 VU → 6k msg/min,P95 45ms / P99 90ms;200 VU → 12k msg/min,P95 68ms / P99 130ms;500 VU → 30k msg/min,P95 110ms / P99 220ms 偶发 429;1000 VU → 60k msg/min,P95 180ms / P99 350ms 限流生效。
- Agent 与消息主链路解耦:LLM 调用独立协程 + 25s 超时 + 429 背压,不阻塞消息收发。
- 测试:dispatch.go 18 个用例(发现 2 个真实缺陷)、双实例端到端集成测试 6 个、关停语义测试 8 个、分区键用例 5 个。
- 可能的追问:
- Q:为什么用 Eino 不用 LangChain? -> A:Eino 是 Go 生态的 LLM 编排框架,与后端同语言,不用为 Agent 引入 Python 侧车;ReAct / Tool / Stream 三件事一个库解决。代价是版本较新(0.9.x),所以做了 Mock 回退解耦。
- Q:如果流量涨 10 倍会怎样? -> A:channel 模式先撞 Transmit 缓冲上限(1000 VU 已见 429),解法是调大 CHANNEL_SIZE、放大 GORM 连接池,再往上必须切 Kafka 横向扩实例;切 Kafka 后瓶颈转移到消费者并发与 DB 写入,需扩分区 + 加实例。Agent 链路瓶颈在 LLM 供应商配额,靠独立协程 + 背压保护主链路。
- Q:这块你踩过什么坑? -> A:applyEnvOverrides 是空壳函数(文档说支持环境变量覆盖,实际没实现);Kafka Renew 报 NOSCRIPT(Lua 脚本未预加载);go.mod 写了 go 1.26.1 这个不存在的版本。三个都指向同一结论:没有 CI,文档和代码会脱节。
- Q:路由表为什么不用 etcd? -> A:项目已依赖 Redis,复用它零新增组件;etcd 一致性更强但要多维护一个有状态集群,对这个体量是过度设计。代价是 Redis 单点,所以保留「路由失效降级广播」的兜底。
- Q:Kafka 分区键为什么必须按会话维度? -> A:Kafka 只保证分区内有序。按消息 ID 或随机分区会让同一会话的消息落到不同分区被并行消费,用户看到乱序;按会话维度分区才能保证同会话串行消费。
项目:pivot-workbench
- 一句话介绍:面向本地表格数据的 AI 数据分析 Agent 工作台。导入 CSV/Excel 后用自然语言下目标(如「按渠道看利润」),Agent 自动生成透视配置、调用浏览器侧工具执行真实聚合、读取结果摘要并输出带数据的 Markdown 报告。
- 技术栈:Vue 3 + TypeScript + Vite + Element Plus + ECharts + SheetJS(xlsx) + Dexie/IndexedDB + Web Worker;Node 本地 Agent 服务(OpenAI 兼容接口);Vitest + Playwright + ESLint + GitHub Actions。
- 我的角色 / 负责模块:独立完成全部实现。核心文件:
src/agent/buildAgentContext.ts(上下文构造与采样)、src/agent/validateAgentConfig.ts(LLM 输出校验与保守归一化)、src/agent/agentToolExecutor.ts(5 个工具执行器)、src/composables/useAnalysisAgent.ts(run 状态机与多轮上下文)、src/workers/pivot.worker.ts+src/utils/pivotEngine.ts(Worker 内计算)、server/index.mjs(plan/finalize 两段式 Agent 服务)。 - 背景与难点:LLM 生成的透视配置是不可信输入——会引用不存在的字段、用不支持的聚合方式、漏掉 values。同时数据分析的计算量在浏览器里不能阻塞主线程。难点在于既要让 Agent「能办事」,又要保证它办错事时不破坏用户当前的分析视图。
- 方案与取舍:
- 工具调用位置:后端执行需要上传整个数据集(隐私/流量/延迟三输)vs 前端执行 + 白名单校验。选前端执行,数据不出本地;代价是 Agent 能力受限于前端暴露的工具集。
- LLM 非法输出:直接丢弃让用户重说 vs 校验 + 保守归一化 + 自动补默认值 + warning 回传。选后者;但只做保守修复,字段不存在一律报错让 Agent 重规划,绝不臆造字段。
- 失败恢复:无限重试 vs 最多重规划一次 + 从失败 observation 提取 avoidFieldKeys。选后者,避免烧 token 与用户长等。
- 执行顺序:先校验后 apply,非法配置进不到状态里,失败时用户当前透视视图完全不变。
- 量化结果:
- 实测环境:Intel i9-14900HX / 32GB / Windows,Edge headless,Vite dev server,取 3 次中位数。
- 10 万行数据集(region + customerId 行维度、channel 列维度、sales 求和 + profit 平均):Worker 内聚合 903ms,主线程直接算 870ms —— Worker 不让聚合变快,它消除的是阻塞。
- 主线程最长阻塞:主线程直接算 -> 901ms 长任务(界面冻结近 1 秒);走 Worker -> 0ms(3 次中 2 次为 0,1 次 58ms 来自模块初始化)。
- 代价:把 10 万行交给 Worker 的结构化克隆约 118ms,一次性(每个数据集一次)。
- 规模扩展性(单次运行):1 万行 88ms / 5 万行 456ms / 10 万行 917ms,近似线性。
- Agent 闭环端到端:中位数 365ms(3 个不同目标各 3 步全部成功),其中服务端 plan 5-12ms、finalize 4-5ms,其余为浏览器侧工具执行(Worker 聚合 + 状态渲染);local-fallback 模式,不含 LLM 网络耗时。
- 质量门禁:8 个 Vitest 单测文件 + 2 条 Playwright E2E 链路 + ESLint + CI。
- 可能的追问:
- Q:为什么叫「前端编排型 Agent」? -> A:常规做法是后端持有工具和数据集、由后端执行。我反过来——后端只做目标理解与计划生成,返回结构化 JSON actions,真实工具执行在浏览器。好处是数据不出浏览器、聚合是本地毫秒级;代价是能力受工具集限制,且需要一整层校验兜住 LLM。
- Q:如果流量涨 10 倍会怎样? -> A:这是纯前端项目,没有服务端并发概念。数据集变大时的瓶颈在 Worker 内存与结构化克隆的拷贝开销,解法是改用 Transferable / SharedArrayBuffer 避免拷贝,以及分块 postMessage 做流式进度。
- Q:这块你踩过什么坑? -> A:第一版让 LLM 直接返回 PivotConfig,结果大量非法配置,后来才改成「白名单 action + 参数校验」;另外 runPivot 对相同配置的重复请求会确定性超时,原因是 isResultFor 没覆盖「结果已算好且配置相同」的情况,改成先比对复用再等待。
- Q:为什么字段统计只扫前 10000 行,采样会不会判断错? -> A:全量扫 10 万行做 distinct/top 会阻塞主线程几百 ms。对 LLM 决策来说「字段大概是什么类型、有哪些取值」采样足够;rowCount 仍取全量,规模信息不失真。
- Q:Worker 通信有什么代价? -> A:实测 10 万行结构化克隆约 118ms,一次性(每个数据集一次)。相比它换来的「主线程 0 阻塞」很划算;数据继续变大应该上 Transferable 或 SharedArrayBuffer 避免拷贝。
项目:北京博视科技公司(实习,2026.06 - 2026.09)—— 医疗三端一体化管理平台
- 一句话介绍:公司与医院合作的三端一体化管理平台,包含 PC 管理后台、医疗数据大屏、医护微信小程序。我主导全项目权限体系从页面级升级到按钮级精细化权限,并独立负责大屏与小程序的权限重构和日常迭代开发。
- 技术栈:Vue(PC 管理后台,
v-permission为自定义指令)+ 微信小程序 + ECharts + Apifox + Git + husky / lint-staged / ESLint / Prettier。(Vue 版本、小程序是否用 uni-app 待确认) - 我的角色 / 负责模块:
- 权限体系改造(跨三端):PC 端动态路由 +
v-permission指令控制按钮显隐;小程序封装权限组件 + 校验函数;大屏按角色动态渲染图表与菜单 - 大屏与小程序日常迭代:管理台报表 / 表单 / 查询页面、ECharts 医疗数据可视化
- 性能优化:虚拟列表、分页加载、图片懒加载、CDN 加速、防抖 / 节流、动态组件与异步导入
- 工程化:封装通用工具函数 / 请求拦截器 / 业务组件;Apifox 联调;husky + lint-staged 提交前检查
- 权限体系改造(跨三端):PC 端动态路由 +
- 背景与难点:医院是多科室分级管控场景,同一个页面里不同科室、不同角色的医生能看到的按钮和数据范围都不一样。原来的权限只做到「页面级」——能进这个页面就能看到全部内容,既不安全也不满足医院的合规要求。难点在于:三端技术形态完全不同(Vue 后台 / 小程序 / 大屏),但权限模型必须统一,不能各写一套。
- 方案与取舍:
- 权限粒度:页面级 vs 按钮级。选按钮级,因为医院要求「同页面不同角色可见范围不同」,页面级无法表达。代价是每个按钮都要挂权限标识,改造面广、回归测试量大。
- 三端实现方式:抽一套通用权限模型 + 各端适配层 vs 各端各写一套。选前者——PC 用指令、小程序用组件 + 校验函数、大屏用角色驱动的渲染配置,模型统一但落地方式适配各端。代价是适配层需要维护三份。
- 大屏渲染:全量渲染后按角色隐藏 vs 按角色动态渲染。选动态渲染,避免把无权限的数据下发到前端(隐藏只是视觉上的,数据仍在)。
- 性能优化:优先做「减少 DOM 数量」和「减少首屏资源」两类,而不是逐页微调。所以选虚拟列表 + 分页加载 + 异步导入 + CDN。
- 量化结果:待补。目前只有动作没有数字,面试官一定会追问。可补的候选指标:
- 权限改造覆盖范围:X 个页面 / Y 个按钮 / Z 个角色
- 首屏加载:从 Xs 降到 Ys(异步导入 + CDN 前后对比,用 Lighthouse 或 Performance 面板)
- 大屏:单页图表数量、数据刷新周期、渲染耗时
- 迭代产出:负责的需求数 / 缺陷修复数
- 工程化:接入 husky 后提交前拦截的问题数、代码规范覆盖率
- 可能的追问:
- Q:按钮级权限是怎么实现的? -> A:PC 端登录后拉取权限码列表存起来,路由守卫按权限码过滤动态路由(控制能进哪些页面),
v-permission指令在元素挂载时比对权限码决定是否移除该 DOM(控制按钮显隐)。小程序没有指令机制,所以封装了一个权限组件 + 一个校验函数,组件用于模板层,函数用于逻辑层判断。 - Q:前端隐藏按钮算不算安全? -> A:不算,前端只做体验层控制,真正的安全边界在后端。我的做法是:前端隐藏 + 后端每个接口都校验权限码,双保险。大屏这块我特意做成「按角色动态渲染」而不是「渲染后隐藏」,就是为了不把无权限的数据下发到前端。
- Q:三端权限怎么保证一致? -> A:抽了统一的权限模型(角色 → 权限码),三端只是消费同一份权限码的不同适配层。这样新增角色时只改权限配置,不用改三端代码。
- Q:虚拟列表是怎么做的? -> A:只渲染视口内的行 + 上下缓冲区,用滚动偏移量计算起止索引,容器高度用「行高 × 总行数」撑开避免滚动条跳动。适合医疗报表这种几万行的表格。
- Q:首屏优化做了哪些? -> A:分两类。减少资源:动态组件 + 异步导入做路由/组件级懒加载,CDN 加速静态资源。减少 DOM 与请求:虚拟列表、分页加载、图片懒加载。另外用防抖 / 节流压掉高频操作的重复渲染。
- Q:这段实习最大的收获? -> A:(待你补真实感受,建议围绕「权限模型要统一、落地要适配」或「前端权限不是安全边界」这两点讲)
- Q:按钮级权限是怎么实现的? -> A:PC 端登录后拉取权限码列表存起来,路由守卫按权限码过滤动态路由(控制能进哪些页面),