跳到主要内容

通用面试题

速记提纲

个人定位

9 年 Web 开发经验,前端主线,兼具全栈能力。最近担任 Web 开发负责人,主导 Swee AI 社交娱乐平台和 RYZZ Web3 直播社交平台的 Web 架构、核心开发和团队管理。技术栈以 TypeScript、React、Next.js、Node.js、NestJS 为主,覆盖 AI 应用、PWA、SSR、中台系统、埋点分析、Prompt 管理、Web3 实时通信和复杂可视化编辑器。

面试主线

  • 前端架构:React/Next.js、SSR/PWA、状态管理、组件设计、性能优化、工程化。
  • AI 应用:SSE 流式对话、Prompt 模板管理、Playground、SDK、多级缓存、AI 辅助研发工具链。
  • 全栈能力:NestJS、MySQL、PostgreSQL、Redis、Kafka、OSS/CDN、接口设计和数据处理。
  • 中台复用:Prompt 中台、埋点系统、多语言文案、文件管理、配置中心。
  • 复杂前端:Vue + Canvas/SVG 编辑器、变换矩阵、碰撞检测、Web Worker + OffscreenCanvas。
  • 团队协作:技术负责人视角、灰度迁移、质量保障、项目拆解、跨端协同。

必背项目卡片

  • Swee AI PWA:Next.js + PWA + SSE 流式响应,多类型内容混排,requestAnimationFrame 合批 token,IndexedDB 历史消息,Service Worker 缓存。
  • Prompt 模板管理系统:100+ Prompt 模板,变量定义、版本控制、Playground、审批发布、三步渲染管线、SDK、多级缓存,迭代从 2-3 天缩短到分钟级。
  • 事件埋点系统:前端 SDK 批量缓冲,sendBeacon/fetch/XHR 降级,Kafka 削峰,PostgreSQL JSONB + GIN,埋点代码自动生成。
  • RYZZ Web3 主站:Next.js SSR,WebSocket + Protobuf,事件分发,自动重连、心跳、消息缓冲、状态重放,Wagmi + Ethers.js 钱包交互。
  • 虎牙 SSR 重构:PHP 到 React SSR,服务端数据预取、Redux 注入、Hydration、请求聚合、缓存策略和灰度切流,秒开率提升 41%。
  • 凡科快图编辑器:Vue + Canvas/SVG,图层、滤镜、拖拽、文本、矢量图形,Canvas 转 SVG 降低开发成本,Worker 处理复杂计算。

回答公式

  • 项目题:背景 -> 痛点 -> 方案 -> 难点 -> 结果 -> 复盘。
  • 性能题:先定位指标,再分加载、渲染、接口、缓存、监控分层优化。
  • 架构题:先讲业务约束,再讲模块边界、数据流、扩展点和风险控制。
  • 全栈题:先讲接口契约,再讲数据模型、缓存、异步链路、权限和观测。
  • AI 题:先讲场景,再讲 Prompt/上下文/工具/流式/评估/安全/成本。

完整题目答案

Q1: 请做一个通用自我介绍

答案

我叫李文杰,有 9 年 Web 开发经验,前端是主线,也有比较完整的 Node.js 全栈经验。最近在广州未可信息科技担任 Web 开发负责人,主导过 Swee AI 社交娱乐平台和 RYZZ Web3 直播社交平台的 Web 架构设计、核心开发和团队管理。

技术上我主要使用 TypeScript、React、Next.js、Zustand、SWR、Tailwind CSS,也用 NestJS 做过生产级后端服务,涉及 PostgreSQL、MySQL、Redis、Kafka、OSS/CDN。项目上我做过 AI 对话 PWA、Prompt 模板管理系统、事件埋点分析系统、Web3 实时通信、虎牙 SSR 重构和 Canvas/SVG 编辑器。我的优势是既能做复杂前端体验,也能从全栈和工程化角度把能力沉淀成可复用平台。

Q2: 你最核心的技术优势是什么?

答案

我的核心优势是前端架构和全栈落地结合得比较紧。前端上,我能处理 React/Next.js、SSR、PWA、流式渲染、状态管理、性能优化和复杂交互。全栈上,我能用 NestJS、数据库、Redis、Kafka 把接口、缓存、异步链路和数据处理打通。

另外,我这几年也在做 AI 应用落地,比如 Prompt 模板管理、AI 对话流式响应、AI 辅助研发工具链。这让我不只是写页面,而是能把业务能力平台化、工具化。

Q3: 你最近最有代表性的项目是什么?

答案

最有代表性的是 Swee AI 社交娱乐平台。它是一个用户和 AI 角色互动的平台,包含聊天、剧情任务、互动挑战和 AIGC 内容生成。我负责 Web 架构和核心开发,重点做了 AI 移动端 PWA、Prompt 模板管理系统、事件埋点分析系统和 NestJS 后端服务。

这个项目能体现我的几个能力:React/Next.js 前端架构、AI 流式交互、Prompt 工程、中台系统、全栈接口和数据处理,以及团队层面的工程化沉淀。

Q4: 如果只让你讲一个最能体现技术深度的项目,你讲哪个?

答案

我会讲 Prompt 模板管理系统。它解决的是 AI Prompt 分散在代码里、迭代慢、不可调试、不可回滚的问题。我把它做成一个中台系统,支持 100+ Prompt 模板的在线编辑、变量定义、实时预览、版本控制、Playground 调试和审批发布。

技术上,前端用 Next.js 和 CodeMirror 6,后端用 NestJS、PostgreSQL、Redis 和 OSS。核心是三步渲染管线:条件块、循环块、变量插值。Playground 能记录每一步中间结果,让 Prompt 调试从黑盒变成白盒。最后通过 SDK 和多级缓存接入业务,Prompt 迭代周期从 2-3 天缩短到分钟级。

Q5: 你作为 Web 开发负责人主要负责什么?

答案

我主要负责三类事情。第一是架构设计和核心开发,比如 Next.js 架构、PWA、Prompt 中台、埋点系统、实时通信和后端服务。第二是团队工程化,包括项目结构、公共组件、接口规范、代码规范、CI/CD、灰度发布和复用资产沉淀。第三是协作和交付,把产品需求拆成可执行技术方案,协调前后端、设计、运营和测试,保证质量和节奏。

我理解负责人不是只写关键代码,而是要让团队整体交付更稳定、更可复用。

Q6: 你怎么理解前端架构?

答案

前端架构不是选一个框架,而是围绕业务复杂度设计模块边界、数据流、组件复用、性能策略、工程规范和发布机制。比如一个 AI 聊天 PWA,需要考虑流式渲染、消息模型、离线缓存、多媒体资源、接口错误处理和可观测性;一个中后台系统,则更关注表单表格、权限、配置、审计和接口契约。

好的前端架构应该让业务变化时改动范围可控,让团队成员容易理解和复用,也能支撑长期维护。

Q7: 你怎么做技术方案设计?

答案

我通常先确认业务目标和约束,比如用户场景、性能目标、上线时间、数据一致性和风险。然后拆模块,明确前端、后端、数据、缓存、权限、监控和发布方案。对于关键风险,会提前做 PoC 或灰度策略。

方案文档里我会写清楚为什么这么做、替代方案是什么、边界在哪里、怎么验证效果。这样评审时大家讨论的是取舍,而不是只看代码实现。

Q8: 你怎么判断一个能力是否应该平台化?

答案

我会看三个标准。第一,是否被多个业务线重复使用;第二,是否有相对稳定的规则和边界;第三,平台化后收益是否大于维护成本。比如 Prompt 模板、埋点、多语言、文件管理、配置中心都符合这些标准。

如果只是某个业务的临时逻辑,我不会过早抽象。通常会先在两个以上真实场景里验证,再下沉成平台能力。

Q9: 你怎么处理项目里的技术债?

答案

我会先分类:影响线上稳定性的优先级最高,其次是影响开发效率的重复代码和混乱结构,再其次是体验或性能问题。处理方式上,不会为了重构而重构,而是结合业务迭代逐步偿还。

比如虎牙 SSR 重构就是典型技术债治理,旧 PHP 链路维护成本高、体验受限,我们通过页面维度渐进迁移和流量灰度,既控制风险,也逐步完成架构升级。

Q10: 你怎么和产品、后端、测试协作?

答案

我会尽量前置协作。需求阶段和产品确认用户场景、边界和优先级;技术方案阶段和后端确认接口模型、错误码、缓存、权限和兼容策略;开发阶段提供 mock、联调 checklist 和关键状态说明;测试阶段补充风险点和回归范围。

我自己做过全栈,所以比较能理解后端和测试视角,协作时会把问题落到接口契约、数据状态和验收标准上,而不是只说“前端需要这个字段”。

Q11: React 项目里你怎么做组件拆分?

答案

我会按职责拆分。展示组件只负责 UI 和交互,业务容器负责数据获取和状态组织,通用逻辑抽到 hooks 或工具函数。对于复杂页面,会先找稳定边界,比如列表、筛选、详情、弹窗、权限、操作区,而不是按视觉随意拆。

组件 API 要尽量表达业务意图,不要堆很多布尔参数。复用组件要保留扩展点,比如 render props、slot、配置项或组合式组件。

Q12: React 性能优化你常用哪些手段?

答案

我会先定位是不是渲染问题,再决定优化手段。常用方式包括稳定 props、用 React.memo 跳过历史消息这类不变组件的重渲染,用 useMemo/useCallback 避免昂贵计算和函数引用变化,长列表做虚拟滚动,图片和媒体懒加载,流式 token 合批更新。

在 Swee AI 对话里,我用 requestAnimationFrame 批量合并 token,用 React.memo 避免历史消息重渲染,再配合 IndexedDB 和 Service Worker 优化加载体验。

Q13: useMemo 和 useCallback 你怎么用?

答案

我不会把它们当成默认优化,而是在有明确收益时使用。useMemo 适合缓存昂贵计算结果,或者保证传给子组件的对象引用稳定。useCallback 适合缓存函数引用,尤其是传给 React.memo 子组件或作为 hook 依赖时。

如果组件本身渲染成本很低,过度使用反而增加复杂度。优化前最好通过 Profiler 或实际卡顿场景确认瓶颈。

Q14: React Hooks 最容易踩什么坑?

答案

最常见是闭包和依赖问题。比如 effect 里用到某个状态但依赖数组没写,会读到旧值;或者依赖写得不稳定,导致 effect 频繁执行。另一个坑是把服务端数据、本地 UI 状态和副作用混在一起,后续很难维护。

我的做法是保持状态职责清晰,复杂逻辑封装成自定义 hook,依赖数组遵循 lint 规则,遇到频繁变化的回调再考虑 ref 或事件模型。

Q15: 你怎么做 React 状态管理选型?

答案

我按状态范围和来源选。组件内部状态用 useState 或 useReducer;跨少量组件共享用 Context;全局客户端状态用 Zustand、Redux 或 Recoil;服务端数据缓存用 SWR 或 TanStack Query。

我比较强调不要把服务端数据缓存和本地 UI 状态混在一起。比如接口列表、详情、刷新和缓存交给 SWR,弹窗开关、当前 tab、临时编辑状态放本地或 Zustand。

Q16: Next.js 项目你主要关注哪些点?

答案

我会关注渲染模式、路由结构、数据获取、缓存策略、代码分割、SEO、错误处理和部署方式。不同页面不一定都要 SSR,营销页、内容页可以偏 SSG 或缓存,强交互后台页面可以 CSR,首屏要求高的页面才重点考虑 SSR。

在 RYZZ 和 Swee 项目里,Next.js 的价值主要是工程组织、路由、SSR/PWA 能力和生态整合。

Q17: SSR 的核心难点是什么?

答案

核心难点是服务端和客户端渲染一致性、数据预取、性能成本和缓存策略。服务端要在首屏前准备好必要数据,客户端 Hydration 时要复用同一份初始状态,避免闪烁和不一致。

另外 SSR 会增加服务端压力,所以要配合缓存、降级和监控。虎牙 SSR 重构时,我们通过服务端数据预取、Redux Store 注入、请求聚合和灰度切流来控制这些问题。

Q18: Hydration 不一致通常是什么原因?

答案

常见原因是服务端和客户端初始数据不同,或者首屏渲染时使用了浏览器环境变量、随机数、时间、本地存储等只在客户端可用的内容。也可能是接口返回不稳定,导致服务端渲染和客户端重新拉取结果不同。

解决方式是统一初始数据,浏览器相关逻辑放到客户端 effect,关键接口保证稳定,必要时对动态区域做客户端渲染。

Q19: PWA 你实际用来解决什么问题?

答案

在 Swee 里,PWA 主要解决移动端弱网、重复访问和离线查看体验。AI 聊天是高频场景,用户希望打开快、历史消息能马上看到。我用 IndexedDB 保存历史消息,用 Service Worker 对静态资源和部分数据做差异化缓存。

PWA 不只是加 manifest,而是要根据业务选择缓存策略,哪些资源预缓存,哪些走网络优先,哪些离线兜底,都要设计。

Q20: 你怎么设计 AI 聊天消息模型?

答案

我不会把消息只设计成字符串,而是设计成结构化消息。消息包含 id、角色、状态、时间、内容 blocks、错误信息和元数据。blocks 可以是 text、image、audio、video、option、business-card 等。

这样 AI 输出不只是纯文本,后续扩展业务卡片、选项交互、多媒体和结构化内容都比较自然。流式响应时,也可以按 block 类型增量更新。

Q21: SSE 流式渲染怎么处理性能?

答案

SSE 返回 token 很碎,如果每个 token 都 setState,会造成频繁渲染。我会把收到的 token 先放进缓冲区,再用 requestAnimationFrame 合并成一帧更新。这样用户仍然能感受到流式输出,但渲染压力小很多。

同时,历史消息组件要用 React.memo,媒体资源懒加载,长列表必要时虚拟滚动,避免新消息更新导致整个列表重渲染。

Q22: WebSocket 和 SSE 你怎么选?

答案

如果只是服务端向客户端单向推流,比如 LLM token、任务进度、日志输出,SSE 更简单,自动重连也方便。如果需要双向实时通信,比如直播互动、用户中途控制任务、多人协同或实时状态同步,我会选择 WebSocket。

RYZZ 的直播互动用 WebSocket + Protobuf,Swee 的 AI 对话流式响应用 SSE,这两个选择都和业务场景匹配。

Q23: WebSocket + Protobuf 的收益是什么?

答案

WebSocket 适合长连接实时通信,Protobuf 相比 JSON 更紧凑,能减少带宽和解析成本。在 RYZZ 里,Protobuf 相比 JSON 节省约 50% 带宽,对直播互动、预测市场和状态同步这种高频消息很有价值。

我还封装了消息类型分发、自动重连、指数退避、心跳保活、消息队列缓冲和状态重放,让业务层不用关心底层连接细节。

Q24: 你怎么做前端缓存策略?

答案

我会按资源类型区分。静态资源使用 hash 文件名和强缓存;HTML 使用协商缓存或短缓存;接口数据按实时性决定是否缓存;聊天历史、草稿、配置这类可以本地缓存的数据用 IndexedDB 或 localStorage;PWA 通过 Service Worker 做离线和弱网兜底。

缓存要有失效策略,不能只追求快。比如 Prompt 模板和配置中心需要版本号和发布失效机制,避免读到旧数据。

Q25: 你怎么做首屏优化?

答案

我会从资源、数据和渲染三方面看。资源上做代码分割、图片压缩、懒加载、CDN 和缓存;数据上减少首屏接口数量,能聚合就聚合,能预取就预取;渲染上避免主线程长任务,减少不必要重渲染。

虎牙 SSR 重构里,我们通过 SSR、服务端数据预取、请求聚合、svg sprites、缓存策略和灰度优化,让页面秒开率平均提升 41%。

Q26: 长列表怎么优化?

答案

长列表优先考虑虚拟滚动,只渲染可视区域和缓冲区。列表项要保持高度稳定,key 要可靠,避免滚动过程中频繁重排。如果列表里有图片或视频,要懒加载并设置占位尺寸。

如果是聊天列表,还要考虑新消息追加、历史消息向上加载、滚动位置保持和未读定位,这些比普通列表更复杂。

Q27: 图片和媒体资源怎么优化?

答案

常规方式包括合适格式、压缩、CDN、响应式尺寸、懒加载、占位图和错误兜底。对于视频或音频,要注意预加载策略,不要一进入页面就加载所有媒体。

在 AI 聊天里,多媒体内容可能由模型或业务动态生成,所以我会先渲染结构和骨架屏,再按用户视口加载资源,减少首屏压力。

Q28: 你怎么排查运行时卡顿?

答案

我会先用 Performance 面板看主线程长任务,确认是 JS 执行、布局重排、绘制还是资源解码导致。然后定位具体组件或函数,比如频繁 setState、列表全量渲染、大对象计算、图片解码或同步存储操作。

解决方式可能是减少渲染、拆分任务、requestAnimationFrame 合批、Web Worker 后台计算、虚拟列表或资源懒加载。

Q29: Web Worker 你实际用过吗?

答案

用过。在凡科快图编辑器里,有很多计算密集型任务,比如坐标体系、变换矩阵、碰撞检测、边界判断和滤镜处理。如果都放主线程,会影响拖拽和编辑体验。

我们用 Web Worker 和 OffscreenCanvas 处理复杂计算,让主线程保持 UI 响应。这个经验也适用于大数据处理、图片处理和复杂可视化场景。

Q30: Canvas 和 SVG 怎么选?

答案

Canvas 更适合像素级绘制、高频重绘和大量图元场景,但对象级交互要自己实现命中检测和状态管理。SVG 有 DOM 结构,适合矢量图形、文本、图层、事件和对象级编辑。

凡科快图里我们从 Canvas 转向 SVG,是因为编辑器大量操作是对象级编辑,比如选中、拖拽、缩放、文本编辑和图层管理。原本基于 Canvas 时,每个对象的命中检测、变换、文字编辑都要自己维护一套状态,迁移到 SVG 后这些能力直接复用 DOM 和事件系统,整体开发成本减少约 40%,可维护性也明显提升。复杂计算如矩阵变换、滤镜仍然交给 Web Worker 和 OffscreenCanvas 处理。

Q31: TypeScript 给你的项目带来什么价值?

答案

TypeScript 最大价值是让接口、组件和业务模型有明确契约,减少多人协作中的隐性错误。比如接口返回、表单数据、Prompt 变量、埋点事件属性都可以用类型约束。

在中台系统里,类型不仅是代码提示,更是工程边界。配合代码生成、接口 Schema 和 CI typecheck,可以提前发现很多问题。

Q32: 你怎么设计 TypeScript 类型?

答案

我会让类型贴近业务模型,而不是只写 any 或宽泛 object。接口层定义 DTO,页面层定义 ViewModel,组件层定义 Props。对于枚举、联合状态和不同消息 block,会使用联合类型和可辨识字段,让状态更可控。

比如 AI 消息 block 可以通过 type 字段区分 text、image、audio、option,不同类型有不同数据结构,渲染时 TypeScript 能自动收窄。

Q33: 如何避免 TypeScript 类型和后端接口不一致?

答案

最好有统一的接口契约来源,比如 OpenAPI、JSON Schema 或后端类型导出,再生成前端请求类型。没有条件时,也要在联调阶段做响应校验和 mock 对齐。

对于中台系统,我会推动接口规范化,包括错误码、分页结构、字段命名、枚举值和兼容策略。类型只是第一步,接口治理才是根本。

Q34: 你怎么封装请求层?

答案

请求层我会统一处理 baseURL、鉴权、错误码、超时、重试、取消、日志和响应解包。业务代码不应该到处写 fetch 或 axios 细节,而是调用明确的 API 函数。

如果配合 SWR 或 TanStack Query,还要处理缓存 key、刷新策略、乐观更新和错误回退。这样请求逻辑更统一,也方便监控和排查。

Q35: 前端错误处理你怎么做?

答案

我会分层处理。组件渲染错误用 Error Boundary,接口错误在请求层统一归一化,业务错误在页面给用户明确反馈,资源加载错误做兜底,运行时错误和 unhandledrejection 接入监控。

错误处理不是只 catch 掉,而是要能定位、能恢复、能让用户继续完成任务。

Q36: Prompt 模板管理系统的核心难点是什么?

答案

核心难点是把 Prompt 从不可控字符串变成可管理资产。它需要变量定义、语法校验、实时预览、版本控制、审批发布、灰度、回滚和 SDK 接入。

另一个难点是可调试。我们通过三步渲染管线和 Playground 展示中间结果,让业务能看到每一步发生了什么,这样 Prompt 迭代效率才会真正提升。

Q37: Prompt 变量怎么设计?

答案

变量要有类型、说明、默认值、是否必填和来源。渲染前先校验变量,不合法就提前报错,而不是把坏 Prompt 发给模型。

对于数组和对象变量,要支持循环块和条件块。这样 Prompt 模板既能表达复杂业务上下文,也能保持结构清晰。

Q38: 你怎么理解 AI 应用里的流式渲染?

答案

流式渲染的价值是降低用户感知等待时间,让模型边生成边展示。但工程上要处理 token 合批、取消、重试、异常、结束标记、多类型内容和滚动体验。

我会把流式协议设计成结构化事件,而不是纯文本拼接。这样前端能区分文本增量、卡片、错误、完成和元数据。

Q39: AI 应用如何控制成本?

答案

先要能统计成本,包括模型、场景、用户、token、调用次数和重试次数。优化方式包括小模型处理简单任务,大模型处理复杂任务;Prompt 精简;RAG 只注入必要片段;缓存 Prompt 和检索结果;设置最大轮次和超时。

成本优化不能只靠少用模型,而是要让每次调用更有价值。

Q40: AI 应用如何减少幻觉?

答案

减少幻觉要让模型基于可靠上下文回答,并增加验证。比如接入知识库、接口文档、项目代码和结构化数据;回答时要求引用来源;生成结构化结果后用 Schema 校验;代码生成后跑 typecheck 或测试。

如果信息不足,系统应该允许模型说“不确定”或请求补充,而不是强行生成。

Q41: 你怎么使用 AI 编程工具?

答案

我不会只把 AI 当代码补全,而是把它放进研发流程。比如用 Rules 固化项目规范,用 Skills 沉淀领域知识,用 Commands 复用高频任务,用 MCP 接入文件和工具能力。

实际使用时,我会让 AI 先读代码和规则,再给方案,小步修改,最后跑检查并总结 diff。这样输出更可控,也更容易团队化。

Q42: 你怎么看 AI 对前端开发的影响?

答案

AI 会明显提升重复性开发效率,比如页面骨架、接口类型、mock、测试、文档和代码迁移。但越是复杂业务,越需要工程师定义边界、判断取舍和负责质量。

我认为前端工程师的价值会从“写每一行代码”更多转向“设计系统、组织上下文、定义工具、验证结果和把能力产品化”。

Q43: NestJS 你主要用来做什么?

答案

我用 NestJS 做过 Prompt 模板管理、事件埋点服务、多语言文案管理、文件管理和配置中心。它的模块化、依赖注入、Guard、Interceptor、Pipe 很适合中后台和中台服务。

在这些项目里,NestJS 不只是提供接口,还承担权限校验、缓存、文件签名、Kafka 消费、版本控制和数据处理。

Q44: 你怎么设计 RESTful API?

答案

我会先明确资源模型,再设计 URL、方法、状态码、分页、过滤、排序、错误码和权限。创建用 POST,查询用 GET,更新用 PUT/PATCH,删除用 DELETE。复杂动作如果不适合资源模型,可以设计明确的 action,但要保持语义清晰。

接口最重要的是契约稳定,字段命名、错误结构和兼容策略要统一。

Q45: 数据库设计你关注什么?

答案

我关注业务实体、关系、查询路径、索引、约束、数据增长和迁移成本。比如事件埋点系统里,公共查询字段要作为普通列并建立索引,扩展属性用 JSONB 存储;Prompt 模板系统里,要考虑模板版本、审批状态、发布记录和回滚。

数据库设计不能只满足当前页面,还要考虑后续查询、统计和维护。

Q46: Redis 在项目里怎么用?

答案

Redis 主要用于缓存热点数据、降低数据库压力、存临时状态和做限流。比如 Prompt 模板读取是读多写少场景,我用本地内存、Redis、数据库三级缓存。配置中心和多语言文案也适合类似策略。

但 Redis 不是万能的,要处理失效、穿透、击穿和一致性问题。

Q47: Kafka 在埋点系统里为什么需要?

答案

埋点上报流量有峰值,如果请求直接写数据库,会让数据库承压,也影响接口响应。Kafka 可以解耦削峰,服务端校验后快速投递消息,Consumer 再批量写入数据库。

这样上报链路更稳定,也方便后续扩展实时计算、告警和数据仓库同步。

Q48: OSS 直传怎么设计?

答案

前端先向后端申请上传凭证,后端校验权限后返回临时签名或上传参数,前端再直传 OSS。上传完成后,前端通知后端保存文件元数据。这样文件流量不经过业务服务器,安全和性能都更好。

后端不要把长期密钥暴露给前端,CDN 刷新和权限控制也应该由后端统一处理。

Q49: CI/CD 你关注哪些检查?

答案

我会关注 lint、typecheck、测试、构建、产物大小、依赖安全和部署结果。对于 Monorepo,还要只检查受影响的包,避免 CI 太慢。

发布上要支持环境区分、灰度、回滚和日志追踪。CI/CD 的价值是让质量检查自动化,不靠人肉记流程。

Q50: Turborepo 给团队带来什么收益?

答案

Turborepo 适合多应用、多公共包的场景,比如 Web 应用、管理后台、SDK、组件库、工具包和配置包。它能做任务编排和缓存,只构建受影响部分,提高本地和 CI 效率。

更重要的是 Monorepo 能统一规范和复用能力,减少多仓库版本同步成本。

Q51: Docker 和 Kubernetes 你怎么理解?

答案

Docker 用来统一运行环境,把应用和依赖打包成镜像。Kubernetes 用来做部署、扩缩容、滚动更新、健康检查和服务治理。

作为前端和全栈开发,我关注的是应用是否有清晰的环境变量、日志、健康检查、构建产物和回滚策略,这些决定应用能不能稳定运行。

Q52: 你怎么做灰度发布?

答案

灰度发布要先保证新旧链路可并存,再按用户、比例、页面或业务线逐步放量。发布前准备监控指标和回滚开关,发布后观察错误率、性能、接口异常和用户行为。

我在 Prompt 模板系统里用分批灰度和双写兼容,在虎牙 SSR 重构里用页面维度和流量比例逐步切换,核心都是控制风险。

Q53: 你怎么做可观测性?

答案

前端要采集错误、性能、资源、接口和用户行为;后端要有请求日志、错误日志、链路耗时、数据库和缓存指标;AI 应用还要记录模型、token、工具调用、耗时、失败原因和用户反馈。

可观测性的目标是出现问题时能快速知道影响范围、原因和回滚方式。

Q54: 事件埋点 SDK 怎么保证可靠性?

答案

我做过的 SDK 使用批量缓冲,达到 10 条或 5 秒超时就上报,减少请求量。发送方式优先 sendBeacon,再降级 fetch 和 XHR。页面切后台或关闭时,用 visibilitychange 和 beforeunload 兜底。

同时,SDK 要轻量,不能影响业务性能;事件定义要平台化,减少手写字符串和字段不一致。

Q55: PostgreSQL JSONB 适合什么场景?

答案

JSONB 适合半结构化数据,比如埋点事件属性,不同事件字段差异大,但又需要查询。配合 GIN 索引可以提升属性查询效率。

但它不适合替代所有字段。核心字段,比如事件名、用户、时间、业务线,还是应该拆成普通列,方便索引和聚合。

Q56: Web3 项目给你带来哪些经验?

答案

RYZZ 让我更熟悉实时通信和钱包交互。前端要处理 WebSocket + Protobuf 消息、状态同步、自动重连,也要处理钱包连接、签名、交易构造、approve 授权、gas 估算和链上确认。

这类项目对状态一致性、异常处理和用户反馈要求很高,因为链上操作不可随意回滚,前端必须把每一步状态讲清楚。

Q57: 你怎么处理复杂编辑器里的坐标和变换?

答案

复杂编辑器要有统一坐标体系,区分画布坐标、屏幕坐标、对象局部坐标和缩放后的坐标。旋转、缩放、拖拽都可以抽象成矩阵变换,命中检测和边界判断也要基于统一模型。

如果每个功能各算一套坐标,后面会非常难维护。凡科快图项目让我对这类复杂交互有比较深的实践。

Q58: 你怎么带团队做 Code Review?

答案

我会让 Code Review 重点关注正确性、可维护性、边界情况、性能风险和是否符合项目规范,而不是只挑格式问题。格式问题应该交给工具解决。

对于新人,我会通过 Review 解释为什么这么写,而不是只说“不行”。好的 Review 应该让团队知识沉淀,而不是制造压力。

Q59: 遇到线上故障你怎么处理?

答案

先止血,再定位,再修复,最后复盘。止血可能是回滚、降级、关闭开关或切流。定位时看监控、日志、错误堆栈、最近发布和影响范围。修复后要补测试、补监控或补流程,避免同类问题再次发生。

线上故障最重要的是节奏,不要一开始就陷入细节,先恢复用户体验。

Q60: 你怎么学习新技术?

答案

我一般先从真实问题出发,而不是为了追热点。比如 AI 工具链、MCP、Prompt 工程,是因为项目里确实有 AI 应用和研发提效需求。学习时我会看官方文档、做小 PoC,再总结成团队可复用的规则、模板或实践。

能落地、能复用、能被团队理解的新技术,才是真正有价值的技术。

Q61: 你有什么不足?

答案

我会如实说,最近几年我的主线是 React、Next.js 和 Node.js,全职 Vue3 项目经验没有 React 多。但我早期做过 Vue 复杂编辑器,对响应式、组件化和前端复杂交互有基础,Vue3 的 Composition API 和生态可以快速补齐。

另外,AI Agent 框架方面,我更偏实际落地和工程集成,不会只围绕某个框架讲概念。后续如果团队需要 LangChain、Dify 或 Semantic Kernel,我可以结合已有 LLM、Prompt、MCP 和工具链经验快速迁移。

Q62: 你离职或换机会看重什么?

答案

我更看重岗位是否能持续做有挑战的工程建设,比如前端架构、中台平台、AI 应用落地、全栈协同和团队效率提升。我希望不是只做单点业务需求,而是能把复杂问题沉淀成长期复用的能力。

同时也看重团队技术氛围、业务空间和协作方式,因为这些会直接影响技术方案能不能真正落地。

Q63: 你期望加入后先做什么?

答案

我会先熟悉业务、代码结构、团队规范和当前痛点,找出高频重复、风险高或收益明显的方向。短期先保证业务交付和修复明显问题,中期再推动复用资产、工程化和性能优化。

如果团队有 AI 方向,我会优先找一个清晰场景做试点,比如接口联调、代码生成、知识库问答或 Prompt 管理,用可量化效果证明价值。

Q64: 你怎么证明自己能从前端做到全栈?

答案

我不是只写过一点接口,而是做过生产级全栈系统。Prompt 模板管理系统涉及 Next.js、NestJS、PostgreSQL、Redis、OSS 和 SDK;事件埋点系统涉及前端 SDK、NestJS、Kafka、PostgreSQL、Grafana;配置中心、多语言和文件管理也都包含后端接口、缓存、权限和数据处理。

所以我的全栈能力是围绕业务闭环来的,能理解前端体验,也能处理后端数据和稳定性问题。

Q65: 最后你会怎么总结自己?

答案

我会总结为:我是一个前端主线、全栈视角、偏平台建设和 AI 落地的工程师。过去做过从复杂编辑器、SSR、Web3 实时通信,到 AI 对话、Prompt 中台、埋点系统和 NestJS 后端服务的项目。

我的优势不是只会某个框架,而是能把复杂业务拆解成稳定架构,把重复能力沉淀成平台工具,并且用工程化和 AI 工具提升团队效率。

Q66: 面试官追问 React 组件为什么会重复渲染,你怎么答?

答案

React 重复渲染通常来自父组件状态变化、props 引用变化、context 更新或列表 key 不稳定。判断时我会先用 React DevTools Profiler 看是哪部分反复 render,再看 props 和状态是否真的变化。

优化不一定一上来就 memo,而是先把状态下沉或拆分,让变化范围更小;再用 React.memo、useMemo、useCallback 稳定引用。像 AI 聊天历史消息这类不应该随着新 token 全量重渲染的组件,就很适合 memo。

Q67: 如果追问 React key 为什么重要,你怎么答?

答案

key 是 React 在列表 diff 时识别节点身份的依据。稳定 key 可以让 React 正确复用组件状态,避免错误复用或不必要重建。如果用 index 当 key,在插入、删除、排序时可能导致状态错乱,比如输入框内容、动画状态或组件缓存错位。

我的习惯是优先使用业务 id,比如消息 id、事件 id、文件 id。如果数据还没有后端 id,也会生成稳定临时 id,而不是依赖数组下标。

Q68: Next.js 里 SSR、SSG、CSR 怎么选?

答案

我会按页面特征选。内容稳定、对 SEO 友好的页面适合 SSG;个性化强但需要首屏体验的页面适合 SSR;强交互后台页面可以 CSR;部分动态内容可以用客户端请求补充。

选择不是绝对的,要结合数据实时性、访问量、缓存策略和服务端成本。比如虎牙直播 M 站需要首屏性能和 SEO,所以 SSR 更合适;中后台管理系统很多页面 CSR 就够了。

Q69: 如果追问 Next.js 数据预取怎么做,你怎么答?

答案

SSR 场景下,服务端在渲染前根据路由和页面需求预取数据,把结果注入初始状态,客户端 Hydration 时复用这份数据,避免首屏二次请求导致闪烁。

关键是数据依赖要清晰,接口并发要控制,错误要有兜底。虎牙 SSR 重构里,我们做过服务端数据预取、Redux Store 注入和 Hydration,这样页面首屏能直接带数据输出。

Q70: SWR 适合解决什么问题?

答案

SWR 适合管理服务端数据缓存,比如请求去重、缓存复用、焦点重新校验、错误重试和乐观更新。它让页面不需要自己维护 loading、error、data、refresh 这些重复逻辑。

我会把 SWR 用在列表、详情、配置读取这类接口数据上,而不是用它管理弹窗开关、当前 tab 这类 UI 状态。服务端数据和客户端状态要分开。

Q71: Zustand 相比 Redux 的优势是什么?

答案

Zustand 更轻量,API 简洁,不需要写很多 action、reducer 和 Provider,适合中小型全局状态和业务状态管理。Redux 更适合强规范、复杂状态流、需要中间件和严格调试的场景。

我在 Swee 里用 Zustand,是因为很多状态更偏客户端业务状态,要求轻量和低样板代码。选型时我会看团队规模、状态复杂度和调试要求。

Q72: Tailwind CSS、CSS Modules、Sass 你怎么取舍?

答案

Tailwind 适合快速搭建一致 UI,减少命名成本,也适合组件化项目。CSS Modules 适合局部样式隔离,写法接近传统 CSS。Sass 适合复杂样式组织、变量、mixin 和老项目迁移。

我不会只认一种方案。新项目如果有设计系统和高效率诉求,可以用 Tailwind;复杂组件内部需要局部样式时,CSS Modules 也很好;存量项目或复杂样式体系可以继续用 Sass。

Q73: 如何保证设计系统的一致性?

答案

我会把颜色、字号、间距、圆角、阴影、层级这些设计 token 统一管理,再把高频组件封装成可复用组件。业务页面尽量使用组件和 token,而不是随手写样式。

同时要有文档、示例和 Code Review 约束。设计系统不是 UI 库本身,而是一套从设计、组件、代码到规范的协作机制。

Q74: Prompt Playground 如果要支持对比实验,你会怎么做?

答案

我会让 Playground 支持同一组变量下对比不同 Prompt 版本、不同模型和不同参数。每次运行记录输入变量、最终 Prompt、模型配置、输出、耗时、token 成本和人工评分。

这样业务可以直观看到哪个版本更稳定,而不是凭感觉修改 Prompt。后续还可以沉淀评估集,做自动化回归。

Q75: CodeMirror 6 自定义语法高亮大概怎么实现?

答案

可以用 ViewPlugin 扫描文档内容,识别模板变量、条件块和循环块的位置,再用 Decoration 给这些范围加样式。变量自动补全可以基于 CompletionSource,根据当前光标上下文返回候选变量。

这类实现的关键是编辑器状态和业务变量定义要打通,不能只做颜色高亮,还要能提示变量是否合法、是否缺失。

Q76: Prompt 模板 SDK 你会怎么设计 API?

答案

我会让业务以模板 key 和变量调用,比如传入 templateKey、version 或 environment、variables,SDK 返回渲染后的 Prompt 或直接请求模型。SDK 内部处理缓存、版本、错误、降级和日志。

API 要简单,但内部要有治理能力。业务方不应该关心模板存在哪张表、缓存怎么读、灰度怎么切,只需要按契约传变量。

Q77: Prompt 模板多级缓存如果发布新版本,怎么失效?

答案

发布新版本时,后端先更新数据库里的发布版本,再删除或刷新 Redis 缓存,同时通过版本号让本地内存缓存自然失效或主动失效。SDK 读取时最好带版本信息,避免缓存 key 混乱。

如果担心瞬间不一致,可以让新版本灰度发布,只对部分业务或用户生效;发现问题时回滚到旧版本,缓存也按版本隔离。

Q78: 埋点事件定义平台为什么重要?

答案

如果没有事件定义平台,埋点很容易出现事件名不统一、字段漏传、枚举值混乱、需求变更无记录的问题。平台化以后,事件名、属性、枚举、发布状态和接入代码都有统一来源。

这样数据分析更可信,也能让前端 SDK、服务端校验和代码生成围绕同一份定义工作。

Q79: 埋点 SDK 如何减少对业务性能的影响?

答案

首先 SDK 要轻量,初始化不要阻塞业务。其次上报要批量合并,避免每个事件都发请求。再次发送要异步,优先使用 sendBeacon 这类低阻塞方式,失败再降级。

还要注意采样、队列大小和异常兜底,SDK 自己不能因为报错影响业务页面。

Q80: Kafka Consumer 批量写入为什么要双触发?

答案

只按数量触发会导致低峰期数据迟迟不落库,只按时间触发又可能高峰期批次太小、写入太频繁。所以我会用数量和时间双触发,比如达到 500 条或 5 秒就写入。

这种方式能在吞吐和实时性之间平衡,适合埋点这类高频但允许短暂延迟的数据。

Q81: PostgreSQL 表数据量变大后怎么优化?

答案

先看查询模式和慢查询,确认索引是否命中。常用优化包括合理索引、按时间分区、冷热数据归档、聚合表、查询条件限制和批量写入优化。

埋点数据通常按时间增长很快,所以事件时间、业务线、事件名这些字段要重点设计索引或分区。JSONB 扩展属性可以用 GIN,但不能滥用。

Q82: NestJS 的 Guard、Pipe、Interceptor 分别适合做什么?

答案

Guard 适合做权限和访问控制,比如用户是否登录、是否有某个资源权限。Pipe 适合做参数转换和校验,比如 DTO 校验、类型转换。Interceptor 适合做横切逻辑,比如日志、响应包装、耗时统计、缓存和错误转换。

这三个机制能让业务代码保持干净,把通用逻辑放到框架层统一处理。

Q83: NestJS 服务如何做统一错误处理?

答案

我会用 Exception Filter 或统一异常基类,把业务错误、参数错误、权限错误和系统错误转换成统一响应结构,包括错误码、消息、请求 id 和必要上下文。

统一错误处理的价值是前端可以稳定展示错误,监控也能按错误码统计,而不是每个接口返回一套格式。

Q84: 你怎么设计权限系统?

答案

中后台常见是 RBAC,用户绑定角色,角色绑定资源和操作权限。前端负责根据权限控制菜单、按钮和页面入口,后端必须做真正鉴权,不能只依赖前端隐藏。

对于配置中心、文件管理、Prompt 发布这类系统,还要有操作审计和高风险操作确认,比如发布、删除、回滚都要记录人、时间和变更内容。

Q85: 配置中心最重要的设计点是什么?

答案

配置中心最重要的是版本、灰度、回滚、权限和缓存一致性。配置不是简单 key-value,生产环境配置一旦出错会影响业务,所以必须有审批、发布记录和快速回滚。

读取性能上可以用多级缓存,但发布时要能主动失效。对客户端来说,最好按环境、业务线和版本读取配置。

Q86: 多语言文案管理怎么设计?

答案

我会按项目、语言、模块、key 和版本管理文案。后台支持编辑、导入导出、翻译集成、审批发布和缺失检测。各端可以通过代码生成或 SDK 获取文案。

关键是 key 要稳定,不能随便改;翻译要有状态,比如待翻译、已翻译、已审核;发布后要能回滚。

Q87: 文件管理系统有哪些安全点?

答案

首先上传要做权限校验和临时签名,不能暴露长期密钥。其次要限制文件类型、大小和路径,避免恶意文件和覆盖问题。再次文件访问要区分公开和私有,私有文件需要鉴权或临时 URL。

如果接入 CDN 刷新,也要放在后端控制,记录操作日志,避免前端直接拥有高权限。

Q88: Web3 钱包连接最容易出什么问题?

答案

常见问题是用户拒签、链不匹配、余额不足、allowance 不足、交易 pending、gas 估算失败、交易被替换或失败。前端要把每一步状态展示清楚,不能只给一个失败提示。

我在 RYZZ 里封装过钱包连接、allowance 检测、approve、gas 估算、签名、发送交易和链上确认,核心是把复杂链路拆成可理解的用户状态。

Q89: WebSocket 断线重连怎么设计?

答案

我会用心跳检测连接状态,断线后指数退避重连,避免瞬间打爆服务。重连期间业务消息进入队列,连接恢复后按规则补发或重新拉取状态。

对于实时业务,还要做状态重放,比如重新订阅频道、拉取最新快照,保证前端状态和服务端一致。

Q90: Protobuf 接入前端有什么成本?

答案

Protobuf 的成本主要是 schema 管理、编译生成、调试可读性和版本兼容。不像 JSON 可以直接看,Protobuf 需要工具链支持,也要保证前后端使用同一份 proto 定义。

所以只有在消息频繁、带宽敏感或结构稳定的实时场景里收益更明显,比如直播互动和高频状态同步。

Q91: Canvas 编辑器里命中检测怎么做?

答案

简单图形可以用包围盒判断,复杂图形要考虑旋转、缩放和路径。常见做法是把鼠标点从屏幕坐标转换到对象局部坐标,再判断是否落在对象范围内。

如果对象很多,还要做空间索引或分层,避免每次移动都遍历所有对象。编辑器性能很多时候就卡在这些细节上。

Q92: 旋转元素的边界判断怎么处理?

答案

旋转后不能再直接用未旋转的宽高判断边界,而要计算四个顶点经过矩阵变换后的坐标,再基于这些点求外接包围盒,或者用分离轴定理做更精确的碰撞判断。

凡科快图这类编辑器里,拖拽、缩放、旋转、吸附和裁剪都依赖统一的变换计算,所以底层坐标模型很重要。

Q93: SVG 编辑器的性能瓶颈可能在哪里?

答案

SVG 对象多了以后,DOM 节点数量、属性更新、事件监听和布局绘制都会成为瓶颈。优化方式包括减少节点、分层渲染、批量更新属性、避免频繁 layout、复杂效果转 Canvas 或离屏处理。

所以我会根据场景混合使用 SVG 和 Canvas,而不是绝对选择一种技术。

Q94: 前端测试你更关注哪些模块?

答案

我更关注高风险、高复用、高变更频率的模块,比如 Prompt 渲染管线、变量校验、接口请求封装、权限判断、埋点 SDK、配置发布和核心组件。

这些模块一旦出问题影响面大,适合写单元测试和集成测试。普通展示页面可以更多依赖类型检查、组件测试和 E2E 覆盖关键流程。

Q95: 如何给 Prompt 渲染管线写测试?

答案

我会准备不同类型的模板输入,包括普通变量、缺失变量、条件块命中和不命中、循环空数组、嵌套变量和非法语法。每个用例断言中间结果和最终 Prompt。

因为 Prompt 渲染是核心逻辑,测试不只看最终字符串,也要看每一步是否符合预期,这样重构时更安全。

Q96: 前端监控 SDK 和埋点 SDK 有什么区别?

答案

前端监控 SDK 更关注错误、性能、资源加载、接口异常和用户环境,目标是发现和定位问题。埋点 SDK 更关注业务行为,比如点击、曝光、转化、停留时长,目标是分析业务效果。

两者底层都有采集、缓冲、上报和降级,但数据模型、采样策略和使用方不同。实际项目里可以共用部分底层能力,但最好区分语义。

Q97: 你怎么做接口异常降级?

答案

我会按接口重要性设计降级。核心链路接口失败,要给明确错误和重试;非核心模块失败,可以隐藏模块或展示兜底内容;配置类接口可以使用本地缓存或默认配置;弱网场景可以延迟加载。

降级不是简单吞掉错误,而是让用户还能完成主要任务,同时监控能看到异常。

Q98: 如何处理前端安全问题?

答案

常见安全点包括 XSS、CSRF、敏感信息泄露、上传文件风险和依赖漏洞。前端要避免直接渲染不可信 HTML,必须渲染时要做白名单清洗;Token 存储要谨慎;接口要配合 SameSite、CSRF Token 或鉴权机制。

AI 应用还要关注 Prompt 注入和敏感数据泄露,不能把用户无权限的数据注入给模型。

Q99: 如果让你优化团队研发效率,你会从哪里开始?

答案

我会先找重复最多、标准最明确的环节,比如项目模板、接口类型生成、Mock、组件库、埋点代码、Prompt 模板、发布 checklist 和常见问题文档。先把这些做成工具或规范,收益最快。

然后再考虑 AI 辅助,比如让 Agent 生成接口代码、检查变更风险、生成测试用例和总结 PR。研发效率提升要从流程痛点出发。

Q100: 如果面试官让你现场反问,你怎么问?

答案

我会问三个方向。第一,团队当前最核心的前端挑战是什么,是性能、工程化、中台复用,还是 AI 落地?第二,当前技术栈和协作流程里,哪些地方最影响交付效率?第三,如果我加入,前三个月最希望我解决什么问题?

这些问题能帮助我判断岗位真实需求,也能体现我关注落地价值,而不是只聊技术名词。

Q101: Swee 的 AI 对话为什么要支持多类型内容,而不是只做文本?

答案

Swee 是 AI 社交娱乐平台,不是普通问答产品。用户和 AI 角色互动时,除了文本,还会出现剧情任务、互动选项、图片、音频、视频和业务卡片。如果只按纯文本设计,后续每加一种互动都要重写聊天框。

所以我把消息抽象成结构化 blocks,每个 block 有自己的类型和数据结构。这样文本流式输出、图片展示、选项点击和业务卡片都能在同一套消息模型里扩展。

Q102: AI 对话中“取消生成”和“重新生成”你会怎么处理?

答案

取消生成需要前端和服务端都支持中止。前端保存当前请求的 AbortController 或会话 id,用户取消时通知服务端停止模型调用,同时把消息状态改成 canceled。重新生成时,要基于同一段上下文重新发起请求,但要区分旧消息和新消息,避免流式事件写回旧节点。

工程上要特别注意并发和乱序:每次生成都有唯一 messageId 或 requestId,前端只接受当前活跃请求的增量。

Q103: Prompt 模板系统里的审批发布怎么设计更稳?

答案

我会把模板状态拆成草稿、待审批、已发布、已下线。编辑只影响草稿,审批通过后才能生成发布版本。发布记录要包含版本号、修改人、审批人、发布时间、变更说明和回滚目标。

这样线上永远读取稳定发布版本,编辑中的内容不会影响生产。如果新版本效果不好,可以快速回滚到上一版。

Q104: Prompt 模板系统为什么要双写兼容?

答案

Prompt 从代码迁移到平台时,业务不可能一次性全部切换。双写兼容可以让旧链路和新链路在一段时间内并存:业务仍然可以按旧方式运行,同时新平台记录和验证对应模板。

等效果和稳定性验证通过,再分批切换读取来源。这样能降低迁移风险,也方便出问题时快速回退。

Q105: 如果 Prompt 渲染结果异常,你怎么定位?

答案

我会先看 Playground 的中间结果。第一步确认变量输入是否完整、类型是否正确;第二步看条件块是否按预期命中;第三步看循环块展开是否正确;最后看变量插值后的最终 Prompt。

如果最终 Prompt 正常但模型输出异常,再看模型参数、上下文长度、示例质量和输出格式约束。这样能把问题拆成模板问题和模型问题。

Q106: 埋点代码自动生成怎么避免业务乱埋?

答案

自动生成不是让业务随便生成字符串,而是基于事件定义平台生成。事件名、属性、枚举和触发时机都先在平台定义,代码生成只根据已发布定义输出调用代码和 TypeScript 类型。

这样既降低接入成本,也保证数据口径统一。后端收到上报后还能按同一份定义做字段校验。

Q107: 埋点系统怎么处理离线或弱网数据?

答案

弱网下可以先把事件放到内存队列,必要时落 IndexedDB,等网络恢复后再批量上报。但要限制队列大小、过期时间和重试次数,避免无限堆积。

对于关键事件,可以提高重试优先级;普通曝光事件可以采样或丢弃。埋点可靠性和性能要平衡,不能为了 100% 上报影响业务体验。

Q108: 虎牙 SSR 重构时为什么要做流量灰度?

答案

因为这是线上大流量站点,从 PHP 切到 React SSR 涉及路由、数据预取、缓存、Hydration 和部署链路,任何环节出问题都会影响用户。流量灰度可以先让少量用户进入新链路,观察性能、错误率和业务指标。

如果异常,可以快速切回旧链路。大型重构最重要的是可回滚,而不是一口气替换。

Q109: SSR 中接入 TAF/TARS RPC 替代 HTTP 的意义是什么?

答案

在服务端渲染场景里,服务端需要请求后端数据。如果内部服务之间还走普通 HTTP,可能有额外协议开销和链路延迟。接入 TAF/TARS RPC 可以更贴近内部服务体系,降低延迟,提高服务端数据获取效率。

对用户来说,服务端数据更快准备好,首屏 HTML 就能更早返回。

Q110: RYZZ 的消息队列缓冲和状态重放解决什么问题?

答案

WebSocket 连接不稳定时,用户可能短暂断线。如果业务消息在断线期间丢失,前端状态就会和服务端不一致。消息队列缓冲可以暂存待发送或待处理消息,状态重放则是在重连后重新拉取或补齐关键状态。

对于直播互动、预测市场这类实时业务,重连后状态正确比单纯连上更重要。

Q111: Web3 交易流程里 approve 为什么常被问?

答案

approve 是 ERC-20 授权流程里很关键的一步。用户合约转账前,通常需要先授权目标合约可以使用多少 token。前端要先查 allowance,如果不足再发起 approve,approve 确认后才能继续业务交易。

这个流程涉及链上状态、用户签名、交易确认和失败处理,能体现前端对 Web3 业务细节的理解。

Q112: 多语言文案系统如何和代码生成结合?

答案

后台统一管理 key、语言、模块和文案内容,发布后根据不同端生成对应格式的语言包,比如前端 JSON、移动端资源文件或 TypeScript 常量。这样各端不用手动复制文案,也能避免 key 拼错。

同时可以做缺失检测和翻译状态管理,哪些语言未翻译、哪些 key 未使用,都能在平台上看到。

Q113: 配置中心的版本回滚怎么保证安全?

答案

每次发布都生成不可变版本,记录配置内容、发布人、时间和说明。回滚不是手动改回旧值,而是把线上生效版本指针切回某个历史版本。

这样回滚动作简单、可审计,也不会因为人工复制配置漏字段。高风险配置还可以加审批和灰度。

Q114: 文件管理里的 OSS 直传如何处理大文件失败重试?

答案

普通文件可以整体重试,大文件更适合分片上传。前端把文件切片,后端生成上传凭证,分片上传成功后再合并。失败时只重传失败分片,节省时间和带宽。

上传完成后要校验文件大小、hash 或 ETag,再保存元数据,避免前端误以为上传成功。

Q115: 凡科快图里文本编辑为什么复杂?

答案

编辑器里的文本不是普通 input,它要支持画布坐标、缩放、旋转、选区、字体、行高、对齐、拖拽和图层关系。文本既是可编辑内容,又是画布对象。

所以要处理 DOM 输入和画布/SVG 展示之间的同步,还要保证缩放和旋转状态下编辑体验正常。

Q116: 编辑器里的撤销重做怎么设计?

答案

撤销重做可以基于命令模式或快照模式。命令模式记录每次操作的 do 和 undo,内存更省,也能表达语义;快照模式实现简单,但对象多时内存压力大。

复杂编辑器通常会结合使用:普通属性变化记录命令,大范围操作或关键节点记录快照。重点是状态变化要可序列化、可恢复。

Q117: 从 Canvas 转 SVG 后还有哪些坑?

答案

SVG 方便对象级编辑,但节点多了会有 DOM 性能问题,复杂滤镜和大规模图形可能不如 Canvas。另一个问题是不同浏览器对 SVG 文本、滤镜和导出的一些细节可能不一致。

所以转 SVG 不代表完全抛弃 Canvas,而是根据编辑和渲染场景混合使用,复杂计算仍然可以放 Worker 或 Canvas 处理。

Q118: 如果面试官追问“你管理团队的方式”,怎么答?

答案

我会说我更偏工程负责人风格:先把目标拆清楚,再明确模块负责人、接口边界和验收标准。过程中通过方案评审、Code Review、阶段检查和风险同步保证质量。

对新人或中级同学,我会给可落地的任务和明确反馈;对复杂任务,我会先和团队一起定方案,再让成员负责具体模块,最后把经验沉淀成文档或工具。

Q119: 如果问你“项目里最难的取舍是什么”,怎么答?

答案

我会讲平台化和交付速度的取舍。比如 Prompt 模板系统,如果一开始就做成大而全的平台,会拖慢业务;但如果一直散落在代码里,又会导致后期不可维护。

我的做法是先围绕最高频痛点做最小可用能力:在线编辑、变量、版本、Playground 和 SDK,跑通后再补审批、灰度、评估和更多治理能力。

Q120: 如果问你“这些项目的共同方法论是什么”,怎么答?

答案

共同方法论是把复杂业务拆成模型、流程和工具。Swee AI 对话先抽象消息模型,Prompt 系统抽象模板和变量,埋点系统抽象事件定义,编辑器抽象对象和坐标变换,SSR 重构抽象路由、数据和渲染链路。

抽象清楚以后,再通过工程化、缓存、监控、灰度和文档把它变成可长期维护的系统。这也是我做项目时最重视的部分。

Q121: 凡科微传单这个低代码 H5 搭建平台你做了什么?

答案

微传单是一个面向运营和中小商家的 H5 可视化搭建平台,用户拖拽组件就能搭出活动页、邀请函、问卷等 H5。我作为前端组长,主要参与编辑器内核、组件体系和模板能力的迭代维护。

技术上,编辑器是典型的低代码架构:Schema 驱动渲染,编辑态和预览态共用一套组件渲染层,组件以 manifest 描述属性、样式、事件和数据;属性面板根据 manifest 自动生成。难点在于组件扩展性、跨设备适配、撤销重做、性能(大量组件下的拖拽流畅度)和发布产物的体积控制。这些经验后来对我做 Prompt 模板、配置中心这类 Schema 驱动的平台帮助很大。

Q122: 凡科快图的图层管理怎么设计?

答案

图层既要支持 z-index 排序、显示/隐藏、锁定、分组、嵌套,又要和画布对象一一对应。我们用一棵图层树作为唯一数据源,画布渲染、属性面板、图层面板都基于这棵树派生视图,避免多份状态不一致。

操作上,每次结构变化(新增、删除、移动、分组)都生成一个命令对象,进入撤销重做栈;选中、显示、锁定这类轻量状态走另一套不进栈的状态。性能上,图层多时面板做虚拟滚动,画布只对脏区域重绘。

Q123: Swee PWA 的 Service Worker 缓存策略具体怎么配?

答案

我按资源类型差异化配置。静态资源(JS、CSS、字体、图标)走 Cache First + 长缓存,文件名带 hash,新版本通过 Service Worker 更新自然替换;HTML 走 Network First,回退到缓存,避免拿到旧版本壳子;图片走 Stale-While-Revalidate,先展示旧的再后台更新;接口默认 Network Only,只有少数稳定配置接口允许 SWR 缓存。

Service Worker 自身用 skipWaiting + clients.claim 配合前端弹"有新版本"提示,让用户主动刷新,避免强制刷新打断 AI 对话。

Q124: AI 历史消息用 IndexedDB 怎么设计?

答案

我按会话维度建表,主表是 conversation,子表是 message,message 上建 conversationId + createdAt 的复合索引,方便按会话分页加载。每条消息存结构化 blocks、状态、时间和元数据,媒体资源只存引用 URL,不存二进制。

写入用批量事务,流式 token 不每个都写,而是在一段稳定后再持久化,降低 IO。读取上,进入会话先从 IndexedDB 读最近 N 条秒开,再后台和服务端增量对齐。还要做容量上限和过期清理,避免长期使用后存储膨胀。

Q125: SSE 的断线、取消和重连你怎么处理?

答案

SSE 我用原生 EventSource 或 fetch + ReadableStream。fetch 方案更灵活,可以自定义 header、传 body、用 AbortController 取消。每次请求带一个 requestId 和服务端返回的 lastEventId,断线重连时带上 lastEventId,服务端从断点续推,避免重复 token。

异常上要区分网络断开、服务端 5xx、模型超时和用户主动取消。网络断开做指数退避重连,有最大次数;用户取消立刻 abort,并把当前消息状态置为 canceled;服务端错误要给明确兜底文案,而不是只显示一个红叉。

Q126: Prompt 模板从代码迁移到平台,4 批灰度具体怎么做?

答案

我把 100+ 模板按风险和调用量分了 4 批:第 1 批是低风险、低调用量的内部工具 Prompt,验证平台基础链路;第 2 批是中等调用量的非核心业务 Prompt,验证缓存、SDK 和监控;第 3 批是高调用量但相对独立的业务,验证性能和稳定性;第 4 批是核心对话 Prompt,这时候平台已经被充分验证。

每批迁移都用双写兼容:代码里的旧 Prompt 保留,SDK 优先读平台,失败降级回代码。监控对比新旧 Prompt 的输出是否一致、模型耗时、失败率。每批观察至少几天没问题再进入下一批,这样做下来零事故上线。

Q127: Prompt 三步渲染管线具体怎么实现?

答案

三步是条件块、循环块、变量插值,顺序不能换。第一步条件块解析 {{#if}}...{{/if}},根据变量值决定保留或丢弃内容,这一步先做是因为后面循环和变量都可能在条件分支里。第二步循环块解析 {{#each}}...{{/each}},把数组变量展开成多段内容,每段内部还可能有嵌套变量。第三步才是变量插值 {{var}},把剩下的标量变量替换。

每一步都在 AST 上操作,而不是字符串正则,这样能处理嵌套和转义。Playground 会保存每一步的输入、输出和命中规则,业务调试时能直接定位是哪一步出问题。

Q128: MPC 钱包和普通 EOA 钱包,前端处理上有什么不同?

答案

EOA 钱包(MetaMask 这类)的私钥在用户本地,前端通过 EIP-1193 注入对象发起签名,用户在钱包扩展里确认,签名结果同步返回。MPC 钱包的私钥由多方分片托管,签名要走服务端 API,流程是异步的:前端调接口发起签名请求,后端协调多方计算,可能还需要二次验证(短信、邮箱、TOTP),完成后再回调。

所以前端要把签名抽象成统一接口,内部区分同步和异步两条路径,UI 上对 MPC 钱包要展示"等待验证""签名中"等中间态,错误处理也更复杂(网络失败、验证超时、风控拦截)。这也是为什么我在 RYZZ 把钱包层做了统一封装。

Q129: Wagmi 和 Ethers.js 你怎么搭配使用?

答案

Wagmi 负责 React 层:钱包连接状态、链切换、账户监听、网络监听,这些都是 hooks,直接和组件状态绑定。Ethers.js 负责底层:合约 ABI 编码解码、交易构造、事件订阅、自定义 Provider。

Wagmi 内部其实就是基于 viem 或 ethers,但它把状态管理封装好了,业务不需要自己维护。我会用 Wagmi 拿到 signer 或 provider,再传给 Ethers.js 写自定义合约逻辑,比如批量 multicall、复杂事件过滤、estimate gas 时的失败兜底。这样既享受 React 集成的便利,又有足够的底层控制力。

Q130: Turborepo 在 Swee 给团队带来什么实际收益?

答案

Swee 有 Web 主站、管理后台、官网、SDK、组件库、工具包、Prompt SDK、埋点 SDK 等十几个包。Turborepo 主要解决三个问题:任务编排(build/lint/test 按依赖顺序跑)、缓存(本地和远端缓存,只构建变化的包)、并发(独立任务并行)。

实际收益上,CI 平均时长从十几分钟降到几分钟,本地切分支后的冷启动也快很多。同时 Monorepo 让公共组件、Hooks、工具函数能直接跨项目复用,接口类型、Prompt 定义、埋点定义也能从一个源头分发到所有端,大大降低了同步成本。

Q131: 多语言 8 种语言 + 微软翻译器 + 代码生成怎么打通?

答案

后台是统一的 key/语言/模块结构,编辑器里调微软翻译器 API 做初翻,人工再校对。审批发布后,后端按各端格式生成语言包:Web 端是 JSON,移动端是 strings/xml,后端是 properties。各端通过 SDK 或 CI 拉取最新语言包,代码里只引用 key,不写文案。

代码生成还会输出 TypeScript 类型,key 拼错时编译期就能发现。后台还能看到哪些 key 没翻译、哪些没使用、哪些发布后被改动,运营和开发的协作成本明显降低。

Q132: 配置中心的可视化编辑怎么设计?

答案

每个配置项有 Schema 定义,包括类型(string/number/boolean/enum/object/array)、约束、默认值和说明。前端根据 Schema 自动生成表单,复杂结构用嵌套表单或 JSON 编辑器。提交前在前端做 Schema 校验,后端再次校验,避免脏数据落库。

发布前展示 diff,让操作人确认改动;高风险配置需要审批;每次发布生成不可变版本,回滚就是把生效指针切回旧版本。读取走内存 + Redis + DB 三级缓存,发布时按版本号主动失效。这套设计在 Prompt 模板、多语言、配置中心三个系统里是复用的。

Q133: 你作为 Web 开发负责人,管多少人,怎么分工?

答案

Swee 阶段我直接带的 Web 团队是 5-7 人,包含前端、Node.js 全栈和兼做管理后台的同学。分工上,我自己负责架构、核心模块(Prompt 中台、埋点系统、对话内核)和高风险改动;资深同学负责独立子系统(管理后台、官网、SDK);中级同学负责业务页面和组件;新人通常先从修复 issue 和写组件开始,逐步接复杂任务。

跨职能上还和移动端、后端、设计、产品、运营对接,所以协作面比团队规模大不少。我倾向把团队当成"工程负责人 + 模块负责人"的结构,而不是单纯按层级。

Q134: 9 年经验为什么没有走纯管理,还在做技术主导?

答案

我做过组长和 Web 负责人,管理工作一直在做,但我有意识地保留了核心技术深度。一个原因是我们团队规模没大到需要全职 manager,负责人同时是架构师和模块 owner 更高效;另一个原因是我自己更喜欢把复杂问题拆清楚、设计出可长期维护的方案,这是纯管理岗很难给到的成就感。

对我来说,管理和技术不是二选一。我把团队产出、复用资产、工程化都看成技术结果的一部分。后续如果业务规模需要,我也愿意承担更大的管理职责,但前提是技术决策不脱节。

Q135: 你从凡科到虎牙再到现在,换工作的逻辑是什么?

答案

凡科四年让我把复杂前端(可视化编辑器、低代码搭建)的底子打扎实,从工程师做到组长。换到虎牙是想接触更大流量的场景,虎牙的 SSR 重构、播放器、SDK 都是面向亿级用户的,工程要求和稳定性要求完全不同。

到未可信息科技是因为这是虎牙核心团队出来创业,业务从 0 到 1,我可以同时做架构、全栈和团队建设,而且方向是 AI 和 Web3 这两个我很想深入的领域。每一次换工作我看的都是能不能在新维度有真实的成长,而不是只看 title 或薪资。

Q136: 你接到一个新的 AI 功能需求,整体落地流程是什么?

答案

我会先和产品对齐场景边界:用户输入是什么、期望输出是什么、错误时怎么办、有没有合规要求。然后做技术拆解:选模型(成本/能力/延迟)、设计 Prompt 模板、定义上下文来源(用户输入/检索/工具调用/历史)、定义输出格式(纯文本/结构化/多 block)、设计流式协议。

接着小规模 PoC,在 Playground 跑通核心 Prompt,准备评估集;之后接 SDK 进业务,加监控(成本、延迟、失败率、用户反馈)、灰度上线、收集 badcase 迭代 Prompt。整个流程的核心是"先把上下文和评估搞清楚,再写代码",这样后期迭代才不会乱。

Q137: AI 应用怎么做效果评测?

答案

我把评测分三层。第一层是离线评估集:积累 50-200 个代表性输入,人工标注期望输出,每次 Prompt 或模型变更都跑一遍,看通过率、相似度、关键字段命中率。第二层是线上指标:成本、token、延迟、失败率、用户重试率、点踩比例、会话完成率。第三层是用户反馈:点赞点踩、问卷、客诉,定期采样人工 review。

评测要服务于迭代,所以每次 Prompt 改动都要能看到这三层指标的变化,而不是凭感觉说"好像变好了"。

Q138: 你怎么在团队推广 AI 辅助开发?

答案

我不会一上来要求所有人用,而是先自己跑通几个真实场景:接口类型生成、Mock 数据、组件骨架、测试用例、PR 总结、文档生成。把这些场景沉淀成 Rules、Skills、Commands、MCP 配置,放到团队仓库,大家直接用就行。

同时分享真实 diff 和效率数据,让团队看到"用了和没用差多少",而不是空讲概念。对新人我会要求"先理解再用",避免无脑接受输出。最后定期 review badcase,持续优化规则。推广的关键是降低门槛和给出可量化的收益。

Q139: 简历写了熟悉 Kubernetes,你实际用到什么程度?

答案

我不是 SRE 角色,但 Swee 和 RYZZ 的前端和 Node.js 服务都跑在 K8s 上,所以我熟悉 Deployment、Service、Ingress、ConfigMap、Secret、HPA 这些基本资源,能写和审 Helm Chart 或 manifest,能看懂 kubectl 输出、排查 pod 启动失败、OOM、健康检查不通过这类常见问题。

更复杂的集群运维、网络、存储是 DevOps 同学负责,我配合提供应用侧的健康检查、优雅退出、日志规范、资源声明这些。这个深度足够支撑应用上线和日常排查,但我不会包装成专家。

Q140: PostgreSQL JSONB + GIN 为什么适合事件埋点?

答案

埋点事件有共同骨架(事件名、时间、用户、业务线)和差异化属性(每个事件自定义字段)。如果把所有属性都拆成列,表结构会爆炸,新增字段还要 DDL;如果用普通 JSON 列,查询性能很差。

JSONB 是 PostgreSQL 的二进制 JSON,存储紧凑,支持索引。GIN 索引可以对 JSONB 的 key 或值做倒排,常见查询如"事件 X 中属性 Y = Z 的数量"能走索引,不用全表扫。核心字段仍然做普通列+B-tree 索引,JSONB 只承担扩展属性,这样既灵活又有性能。这也是我在 Swee 埋点服务里实际的设计。

Q141: Next.js App Router 和 Pages Router 核心区别是什么?

答案

最大区别是渲染模型。Pages Router 默认是 Client Component,通过 getServerSideProps/getStaticProps 决定数据来源;App Router 默认是 Server Component,数据获取直接在组件里 await,Client Component 需要显式 "use client"

其次是路由组织:App Router 用文件夹 + 约定文件(page.tsx/layout.tsx/loading.tsx/error.tsx/not-found.tsx),嵌套布局天然支持,部分渲染可以共享 layout 不重渲染。再就是缓存模型完全不同:App Router 有 fetch cache、Router Cache、Full Route Cache 几层,需要专门理解。新项目我会优先 App Router,老项目迁移要按页面渐进。

Q142: Server Components 在 Swee 你怎么用?

答案

我把 RSC 用在三类场景:首屏数据展示(用户资料、内容卡片、配置)直接服务端拉数据,减少瀑布请求;敏感数据处理(token 在服务端用完不下发);静态结构布局(layout、导航壳子)避免重复打包到客户端。

强交互的部分,比如 AI 对话框、表单、Modal、Hooks 状态,都明确 "use client"。边界划分原则是"能在服务端完成的就别下发到客户端",这样既减少 bundle,又能让首屏更快。需要注意 RSC 不能用 useState、useEffect、浏览器 API,数据传给 Client Component 时必须是可序列化的。

Q143: Server Actions 你会用在什么场景?

答案

Server Actions 适合表单提交、简单 mutation、不需要复杂客户端状态的服务端调用,比如点赞、收藏、保存草稿、提交反馈。优势是不用单独写一个 API route,直接在组件里定义 async 函数,加 "use server",前端 <form action={fn}>useTransition 调用。

但我不会把所有 mutation 都搬过去。复杂业务、多端共用、第三方调用还是走 NestJS 后端,因为后端有完整的 Guard、Pipe、日志、监控、限流体系。Server Actions 更适合 Next.js 内部的轻量 mutation,降低样板代码。

Q144: App Router 的 loading.tsx / error.tsx / not-found 怎么组织?

答案

loading.tsx 实际是基于 Suspense 的 fallback,放在哪一级目录就对哪一级路由生效,我一般在列表页、详情页这种有明显加载等待的层级放骨架屏。error.tsx 是 Error Boundary,捕获该层级及以下的渲染错误,必须是 Client Component,要提供 reset 让用户重试。not-found.tsx 配合 notFound() 函数使用,常用在详情页找不到资源时。

我会在 root layout 放全局兜底的 error 和 not-found,再在关键子路由覆盖更精准的 UI。这样既保证不会白屏,也能让不同业务区有自己的错误体验。

Q145: Next.js App Router 的缓存模型你怎么用?

答案

主要有四层:Request Memoization(同一请求内 fetch 去重)、Data Cache(跨请求的 fetch 缓存)、Full Route Cache(整页静态化产物)、Router Cache(客户端导航缓存)。控制方式包括 fetchcache/next.revalidate 选项、Route Segment 的 export const revalidate/dynamic,以及 revalidatePath/revalidateTag 主动失效。

我的做法是默认让稳定数据走缓存,关键路径加 tag,mutation 后用 revalidateTag 精准刷新,而不是整页失效。需要实时的页面显式标 dynamic = 'force-dynamic'。缓存模型用对了首屏和导航都很快,用错了就会出现"为什么数据不更新"这种坑。

Q146: Streaming SSR + Suspense 你怎么用?

答案

App Router 默认支持 Streaming。我把页面拆成"首屏立即返回的骨架部分"和"需要等数据的 Suspense 边界"。比如详情页头部用户信息、面包屑可以瞬时返回,下面的评论列表、推荐内容包在 <Suspense fallback={...}> 里,数据准备好就流式注入。

这样 TTFB 几乎不受慢接口影响,LCP 和 FCP 都能明显改善。落地时要注意:Suspense 边界不要切太碎,否则 HTML 流碎片化反而影响性能;慢接口要有超时和兜底,避免一个接口拖住整段流。

Q147: NestJS 模块怎么拆,怎么避免循环依赖?

答案

我按业务领域拆模块,比如 prompt、tracking、user、auth、storage 各一个模块,模块内部聚合 Controller、Service、Repository、DTO。共享能力(日志、配置、缓存、HTTP client)放在 SharedModule 或 CoreModule,通过 @Global() 注册一次。

循环依赖最常见的原因是两个模块互相 import 对方的 Service。解决方式:第一,优先重构,把共用逻辑下沉到第三个模块;第二,实在无法避免就用 forwardRef(() => OtherModule) 配合 @Inject(forwardRef(() => OtherService));第三,通过事件(EventEmitter 或消息队列)解耦,A 发事件 B 消费,而不是直接调用。我倾向第一种,循环依赖通常是模块边界没切对的信号。

Q148: NestJS 的 Provider Scope 什么时候用?

答案

默认是 DEFAULT(单例),整个应用共享一个实例,适合大部分 Service,性能也最好。REQUEST 是每个 HTTP 请求一个实例,适合需要拿当前请求上下文(用户、租户、traceId)的场景,但有性能开销,且会让依赖它的所有上层 Provider 都变成 REQUEST scope,所以要谨慎。TRANSIENT 是每次注入都新建,适合有状态的临时对象。

我大部分用 DEFAULT,请求上下文优先用 AsyncLocalStoragenestjs-cls 这类方案传递,而不是把整条链路改成 REQUEST scope。

Q149: NestJS 怎么做事务?

答案

如果用 TypeORM,可以用 QueryRunner 手动控制事务,或者用 @Transactional() 装饰器(typeorm-transactional 包)结合 AsyncLocalStorage 隐式传递事务。Prisma 用 prisma.$transaction([...])$transaction(async (tx) => ...) 的 interactive transaction。

我推荐把事务边界定义在 Service 层,Controller 不直接管事务。跨模块调用要小心,涉及第三方调用或消息发送的不要放进数据库事务,避免事务长时间持有连接。需要跨服务一致性时,走 Outbox 模式或最终一致,不要硬上分布式事务。

Q150: NestJS Provider 的几种注册方式区别是什么?

答案

useClass 是默认方式,Nest 帮你实例化类;useValue 注入常量或已有对象,常用于配置、Mock;useFactory 通过工厂函数动态创建,可以注入其他依赖,适合配置驱动的对象(比如根据环境创建不同的 client);useExisting 是别名,让两个 token 指向同一个实例。

实际项目里,数据库连接、第三方 SDK client、配置对象常用 useFactory;测试时用 useValue 注入 Mock;接口和实现分离时用 useClass 配合 token,方便切换实现。

Q151: NestJS 单元测试怎么写?

答案

NestJS 提供 Test.createTestingModule,可以构造一个测试用的模块,然后 overrideProvider(...).useValue(mock) 把外部依赖换成 Mock。Service 测试只关心业务逻辑,数据库、HTTP、Redis 全 Mock 掉。Controller 测试可以配合 supertest 起一个测试应用做接口级测试。

我习惯分两层:Service 层用纯 Mock 写单测,跑得快;关键接口写 E2E,真起 Nest 实例,但数据库用内存版或测试库。覆盖率不是越高越好,核心业务逻辑、边界条件、错误分支必须覆盖,简单 CRUD 不必硬凑。

Q152: Claude Code 的 Rules 或 AGENTS.md 你怎么写?

答案

我把 Rules/AGENTS.md 当成项目宪法,内容包括:技术栈和版本约束、目录结构、命名规范、代码风格(比如"用 TypeScript 不写 any""组件用函数式不用 class")、提交规范、测试要求、禁止行为(比如"不要主动修改 package.json""不要执行破坏性命令"),以及关键流程(改完代码必须跑 lint/typecheck)。

要写得简洁、可执行、可验证,而不是讲大道理。每条规则最好都能让 AI 立刻判断"我现在做的事是否符合"。规则放在仓库根目录,所有人和所有 AI 工具共用一份,避免规则分散。

Q153: Skills 文件你怎么沉淀和组织?

答案

Skills 是领域知识胶囊,适合放"AI 默认不会但项目里反复用到"的知识。我会按主题建 Skill,比如"如何写一个新的埋点事件""如何接入 Prompt 模板""如何添加多语言文案""如何审查 AI 输出"。每个 Skill 包含触发场景、步骤、示例、注意事项。

Skills 不要写成大段文档,要写成"AI 看完能立刻按步骤操作"的形式。和 Rules 的区别是:Rules 是普适约束,Skills 是任务级 SOP。我会定期 review,新业务出现就补 Skill,过时的删掉,避免上下文污染。

Q154: Slash Commands 你常做哪些?

答案

我团队里常用的 Commands 包括:/review 触发对当前 diff 的代码审查;/test 让 AI 为改动生成或补充测试;/doc 生成或更新 API 文档;/commit 按规范生成 commit message;/changelog 根据合并的 PR 生成版本说明;/migrate 走某个固定迁移流程。

每个 Command 背后是一个明确的 prompt + 步骤模板,封装高频任务,降低使用门槛。新人不用记复杂的 prompt,直接打命令就能跑标准流程。

Q155: Subagent 你用在什么场景?

答案

Subagent 适合需要独立上下文、不污染主对话的子任务。我常用的场景:并行调研多个方案(主 agent 出方案,subagent 分别去验证每个方案的可行性);大范围代码探索(让 subagent 去搜索和总结某块代码,只返回结论);批量处理(对多个文件做相同改动,每个文件开一个 subagent);写测试或文档这种独立任务。

主 agent 保持高阶决策和上下文,subagent 处理具体执行,这样主线程对话不会被大量代码和搜索结果挤爆,效率明显提升。

Q156: MCP 你接入过什么,带来什么价值?

答案

我接入过:文件系统 MCP(让 AI 在受控范围读写文件)、数据库 MCP(查 schema、跑只读 SQL)、Git/GitHub MCP(看 PR、issue、提交历史)、内部接口文档 MCP(实时拿到最新接口定义)、Prompt 平台 MCP(读取已发布模板)。

价值是让 AI 拿到的不是过期上下文,而是项目当下的真实状态。比如让 AI 写接口调用代码,它能直接读到最新 OpenAPI 定义,而不是猜字段;让它调试问题,它能直接看最近的提交和 PR。MCP 是把"工具能力"变成"AI 能力"的关键。

Q157: Hooks(PreToolUse/PostToolUse 等)你怎么用?

答案

Hooks 让我能在 AI 调工具前后插入自己的逻辑。我常用的场景:PreToolUse 拦截危险命令(rm -rfgit push --force、生产环境写操作),要求二次确认;PostToolUse 自动跑格式化、lint、typecheck,把检查结果反馈给 AI 让它自己修;Stop 钩子在对话结束时输出 diff 总结、提醒未提交的改动。

Hooks 本质是"给 AI 套一层安全壳和质量门",让人类不用全程盯着也能放心让 AI 干活。规则化、自动化的检查比靠 AI 自觉更可靠。

Q158: AI 辅助开发遇到过的最大坑是什么?

答案

最大的坑是 AI 在缺上下文时会"自信地编造":猜接口字段、用错版本 API、引用不存在的工具方法、写出看起来对其实跑不起来的代码。早期我吃过亏,直接 commit 后才发现一堆 typecheck 错误。

后来我的应对是:第一,上下文先行,接入 MCP 拿真实信息;第二,Rules 里明确"不确定时必须先读代码或搜索";第三,Hooks 强制 PostToolUse 跑 typecheck/lint;第四,Code Review 重点看 AI 生成的部分,尤其是接口调用、类型定义、依赖引入。AI 越强,越要把验证机制做厚。

Q159: 怎么避免 AI 写出来的代码被无脑合并?

答案

第一,所有 AI 生成的代码都要走和人一样的 Review 和 CI 流程,不开后门。第二,PR 模板里要求作者说明"AI 生成了哪些部分、做了哪些验证、哪些没验证",让责任归属清晰。第三,CI 强制 typecheck、lint、单测、关键 E2E,过不了就合不进去。第四,Code Review 重点看"AI 不擅长的地方":边界条件、错误处理、性能、安全、与已有架构的契合度。

我反对把 AI 当成免责盾牌,作者依然要为合进去的代码负责,这是底线。

Q160: AI 辅助下,Code Review 的角色有什么变化?

答案

变化是 Reviewer 不再花太多时间挑格式、命名、低级 bug,这些 AI 和工具能解决。Reviewer 的注意力更多转向高层判断:架构是否合理、抽象是否过度、是否符合业务语义、是否引入了隐性风险、是否和已有代码风格一致。

同时 Reviewer 也要识别"AI 味"的代码:过度通用、过度防御、过多注释、不必要的抽象。这些不是错,但长期会让代码库失去内聚性。Code Review 在 AI 时代反而更重要,因为它是"判断"的最后一道防线,而判断恰恰是 AI 当前最弱的部分。

Q161: 埋点系统用户反馈"丢数据了",你怎么排查?

答案

我会按"前端 → 网关 → Kafka → Consumer → DB → 看板"这条链路自上而下排查,不靠猜。

第一步先界定丢失范围:让反馈方给具体维度——哪个事件名、哪个用户/设备、哪段时间、丢了多少、怎么发现的(是看板少了、还是对比 BI 表少了)。先把"真的丢"和"看板口径不对/数据延迟/采样"区分开,很多时候不是数据丢,是看板的时区、过滤条件、归因模型不对。

第二步分段比对:每一段都有埋点和监控,我会顺序确认:

  1. 前端 SDK:本地有没有打点(用 SDK 的 debug 模式或抓控制台日志);批量缓冲队列有没有堆积异常;sendBeacon/fetch/XHR 三级降级的成功率分别多少;有没有被广告拦截、CSP、iOS WebView 限制。
  2. 网关/接入层:上报接口的 QPS、5xx、限流拒绝、参数校验失败率,看是不是被服务端挡掉了。
  3. Kafka:Producer 端的成功投递率、ack 配置、batch.size 是否打满;Topic 的 Lag、ISR 状态、有没有 partition leader 切换导致短暂不可用。
  4. Consumer:消费 Lag、双触发攒批(500 条/5s)是否正常、有没有 Consumer 频繁 rebalance 导致重复消费或漏消费、异常事件是否进了死信队列。
  5. 数据库:Consumer 批量 INSERT 是否成功、有没有约束冲突、磁盘是否满、连接池是否打满。
  6. 看板:聚合 SQL 的时间窗口、JSONB 属性过滤条件、有没有缓存导致看不到最新数据。

第三步用三方对账:每一段都打了入流量计数,前端上报数 vs 网关接收数 vs Kafka 写入数 vs DB 入库数 vs 看板查询数,差异在哪一段就定位到哪一段。如果各段都对得上但用户还说丢,大概率是采集没触发(SDK 没初始化、事件名拼错、条件没命中)。

事后:把这次排查路径固化成 runbook,补缺失监控(比如某段没分钟级计数就加上),让下次同类问题 5 分钟而不是 5 小时就能定位。

Q162: 数据库慢查询怎么排查?

答案

我按"定位 → 分析 → 优化 → 验证"四步走,以 MySQL/PostgreSQL 为例。

第一步定位慢查询:打开 slow_log(MySQL 设 long_query_time=1、PostgreSQL 用 log_min_duration_statementpg_stat_statements),按总耗时、调用次数、平均耗时排序,找 Top N。线上紧急排查也可以直接看应用 APM(慢接口对应的 SQL)或 SHOW PROCESSLIST / pg_stat_activity 抓正在跑的长事务。

第二步分析执行计划:对 Top 慢 SQL 跑 EXPLAINEXPLAIN ANALYZE,重点看:

  1. 是否走索引:type 出现 ALL(全表扫)、PostgreSQL 出现 Seq Scan 在大表上,就是索引没命中。
  2. 扫描行数:rowsactual rows 远大于最终返回行数,说明过滤效率低。
  3. JOIN 顺序和方式:Nested Loop 在大表 JOIN 上往往是灾难,Hash Join/Merge Join 才合理。
  4. 临时表和排序:Using temporary; Using filesort 说明 ORDER BY/GROUP BY 没走索引。
  5. 索引是否被破坏:WHERE date(create_time) = ? 这种函数包列会让索引失效;隐式类型转换(字符串列传数字)也会;LIKE '%xx' 前缀通配也不走索引。

第三步分场景优化:

  • 缺索引:按 WHERE + ORDER BY + JOIN 列建复合索引,注意最左前缀;选择性高的列在前。
  • 索引太多:写入慢往往是索引过多,删掉冗余/重复索引。
  • 大表无分页:LIMIT 100000, 20 这种深分页用游标(WHERE id > last_id)替代 OFFSET。
  • 聚合慢:高频聚合查询做物化视图或预聚合表,定时刷新。
  • 数据倾斜:某些值占比极高(比如 status=0 占 99%),普通索引没用,要换部分索引或调整查询条件。
  • 历史数据:按时间分区或归档,让热数据集中。
  • N+1 查询:ORM 的 lazy load 经常导致,改成 JOIN 或 dataloader 批量加载。
  • 锁等待:SHOW ENGINE INNODB STATUSpg_locks 看锁竞争,长事务要拆短。

第四步验证和固化:优化前后用同样的 EXPLAIN 对比,生产灰度后看 P95/P99 是否下降。把这个慢 SQL 加到回归测试或监控,避免后续改动又退化。

关于 PostgreSQL JSONB:埋点系统里 JSONB + GIN 是常见慢查询源头。要点是:GIN 索引对 ?/@> 操作符有效,但对 ->> 取值后比较无效,要建表达式索引 CREATE INDEX ON events ((properties->>'key'));@>->> 更能用索引;返回字段多时 JSONB 反序列化本身也是成本,只查需要的 key。

Q163: 为什么从上家公司离职?

答案

主要是因为近半年来,公司的业务方向和团队规划一直不太明确,整体处在调整阶段。老板也和我们沟通过,希望大家结合自己的情况,判断是否继续留下来。 我综合考虑了自己的职业规划和后续发展方向,觉得当时的岗位已经不太适合我长期发展,所以最后选择离开。

Q164: 你现在想去什么样的公司?

答案

我现在比较看重三点:第一是业务稳定,有明确的发展方向;第二是团队氛围好,沟通协作比较顺畅;第三是岗位本身和我的前端经验匹配,同时也有一定挑战。 我希望不是单纯做页面,而是能参与到组件设计、性能优化、工程化、用户体验这些更完整的前端工作里。总体来说,我希望加入一个做事踏实、技术氛围比较好、能长期成长的团队。

Q165: 你的优点和缺点是什么?

答案

优点的话,我觉得我比较踏实,做事比较细心,考虑问题会比较全面,喜欢把事情做好。 缺点的话,有时会倾向于自己先解决问题,不太主动寻求帮助,可能会在某些细节上花费过多时间。我也在努力改进这个习惯,学会更好地平衡效率和质量。