01 项目口述介绍(30 秒 / 2 分钟 / 5 分钟 / 10 分钟四版)
怎么用这篇:先读第一节「事实底盘」把事实钉死,再从 30 秒版往上练。 每版都标注了【口述】(直接念出声的稿子)和【备注讲解】(讲不清时补的知识点,不要背出声)。 最后一节是雷区清单,念错会当场翻车,务必先看。
一、事实底盘(先钉死,再开口)
1.1 产品是什么
鲨堂 AI(shatangAI)=面向电商内容团队的 AI 营销视频生成平台,线上地址 https://shatang.top。
用户上传商品图 + 模特图 + 一段文案,平台自动产出成片短视频:商品展示、模特口播、爆款视频翻拍改编、长视频分段生成、字幕擦除、换脸、数字人口播等。核心价值是把「一条带货短视频从创意到成片」从几小时的剪辑工时压到几分钟的下单等待。
1.2 技术栈(一句话版本)
前端:React 19.2 + Vite 8 + Zustand 5 + React Router 7 + Tailwind 3(49 个页面 / 54 个 feature 域)
后端:Express 5 + TypeScript(tsx 直跑,无编译步骤)
数据:PostgreSQL 17(pg 连接池,135 组 SQL 迁移)+ Redis 7(ioredis)
异步:BullMQ 5(9 条业务队列 / 18 个 Queue+Worker 文件)
存储:阿里云 OSS / 火山 TOS / 腾讯 COS 三套适配器 + 本地磁盘(开发态)
AI:阿里云 DashScope(Qwen VL 做分镜理解、Qwen-Image 出图)
腾讯云 VOD AIGC(视频生成主通道)+ 超分模板(720P/1080P)+ MPS(字幕擦除、换脸)
火山引擎 Ark / 豆包 Seedance(历史通道,2026-08 起停用但保留实现,供在途任务轮询)
后台:独立 React 项目(87 个 tsx,Recharts 做数据可视化,admin 独立子域部署)
部署:pm2 + nginx 反代 + Docker Compose(只跑 PG/Redis 两个基础设施容器)+ 自研迁移工具⚠️ 简历写的是 MySQL,代码里是 PostgreSQL。 这是第一优先要改的地方,详见 10-压力面与简历修正话术.md。
1.3 规模数字(可以直接说,都有出处)
| 数字 | 值 | 出处 |
|---|---|---|
| 提交数 | 2,533 commits(2026-04-16 起) | git rev-list --count HEAD |
| 后端 TypeScript | 156,056 行 / 618 个 service 文件 / 38 个路由文件 | find backend -name '*.ts' |
| 前端 | 187,676 行 / 49 个页面 / 54 个 feature 域 | find src |
| 数据库迁移 | 135 组 .up.sql/.down.sql | backend/migrations/ |
| 测试文件 | 后端 349 个、前端 200+、后台 18 个 | find -name '*.test.ts' |
| 最大单文件 | backend/services/queue/videoPollWorker.ts 2,121 行;backend/routes/video.ts 8,573 行 | wc -l |
1.4 你的真实贡献边界(这一节决定面试安全)
按 git log --author=se_hyxiong@163.com 精确核对,你主导/深度参与的模块:
| 模块 | 你的提交占比 | 关键文件 |
|---|---|---|
| 任务崩溃恢复与巡检 | 14/33 | backend/services/taskRecovery.ts、creditRecovery.ts |
| 视频任务提交 Worker | 10/19 | backend/services/queue/videoWorker.ts |
| 轮询队列设计 | 你起的头 | backend/services/queue/videoPollQueue.ts(feat: video poll to mq) |
| 积分冻结/结算/退款内核 | 11/45 | backend/services/user/userCreditsStore.ts |
| 积分冻结表迁移 | 1/1(你写的) | backend/migrations/009_credit_holds.up.sql |
| 微信支付客户端 / 微信扫码登录 | 2/2、1/1(你写的) | backend/services/payment/wxpayClient.ts、wechat/wechatOAuth.ts |
| 身份解析中间件(JWT + token_version) | 1/3 | backend/middleware/userResolver.ts |
| 限流(IP 维度 + 用户名维度配额) | 1/7 | backend/middleware/rateLimiter.ts |
| OSS 生命周期(临时追踪 + 删除队列) | 1/8 | storage/ossLifecycleService.ts |
| 分销/邀请码、积分板块、仪表盘 | 你发起 | services/distribution/、services/invite/ |
| 前端 App.tsx 拆分(Page + feature hook 架构) | 你起的头 | refactor/front: App.tsx 拆分为独立 Page + feature hook 架构 |
| 首尾帧 / 分镜 / 快节奏 / 镜头拆分 | 你主导 | src/features/keyframes、shots、shared/shot-beat-allocation.json |
你只有"了解级"的模块(被问到要老实说,别主动往深处带):
| 模块 | 主作者 | 你该怎么说 |
|---|---|---|
| 脚本库一键生成的分段生产 Worker | 他人(35 commits,你 0) | "这条链路是同事主导的,我从恢复和结算侧参与,能讲清它的分段续跑状态机" |
模型目录 modelCatalog.ts(30 commits,你 0) | 他人 | "模型与定价的权威清单在 modelCatalog,我读过但不负责维护" |
| 视频编辑器 orchestrator / 标准复刻 settle | 他人 | 同上 |
【备注讲解】为什么要把这张表背下来:字节面试官会顺着简历 bullet 往下一路挖到实现细节。你要是把同事写的模块讲成自己的,第三层问题("这个函数为什么要加这个判断")就答不上来,比一开始划清边界扣分得多。主动划界反而是加分项——它证明你知道团队里每块代码谁负责。
二、30 秒版(电梯稿)
【口述】
我做的项目叫鲨堂 AI,是一个电商 AI 视频生成平台,已经上线在 shatang.top。 业务上把「上传商品图和文案 → 出成片短视频」这条链路做成自动化的:用户下单后平台去调度多个 AI 视频供应商生成,再做多段拼接和画质增强,最后交付成片。 我负责的是后端异步链路:BullMQ 任务队列编排、任务全生命周期状态机和崩溃恢复、以及积分冻结—结算—退款的资金一致性。整套系统现在有 130 多组数据库迁移、9 条业务队列,一天稳定跑着用户的生产任务。
【备注讲解】30 秒版只做三件事:说清业务价值、点出你的主场、给一个规模锚点。不要提技术名词堆砌,也不要在这个阶段说"我全栈"——留出空间让面试官问,你才有机会讲深。
三、2 分钟版(主线稿,最常用)
【口述】
① 业务背景
鲨堂 AI 是给电商内容团队用的 AI 营销视频生成平台。典型用户是店铺运营或内容团队:上传商品图、模特图、一段文案,选一种生成方式,几分钟后拿到一条可以直接投的短视频。生成方式有十几种,比如商品展示、模特口播、爆款视频翻拍、长视频分段、字幕擦除、换脸等,覆盖了电商短视频的主要形态。
② 系统形状
系统分成三块:面向用户的 React 主站、独立部署的管理后台、以及一个 Express 5 + TypeScript 的后端。 后端最核心的特征是它自己不生产视频,它是个调度器:真正的生成来自外部供应商——阿里云 DashScope 负责分镜理解和出图,腾讯云 VOD 的 AIGC 接口负责视频生成和超分,MPS 负责字幕擦除和换脸。 所以后端的能力集中在三件事:任务编排、状态一致、钱不出错。
③ 我具体做了什么
我主要做三块。 第一是任务编排:我最早把视频状态轮询从"进程内定时器"改成了 BullMQ 队列(提交任务和轮询结果是两条队列,用固定 jobId 做去重),后来又把轮询间隔做成前密后疏的退避表,并给轮询加了 2 小时的硬超时——之前没有终止条件,上游卡死时这些 job 会一直空转,是我们一次线上"假 DDoS"的成因之一。 第二是任务全生命周期:
pending → processing → done/failed的状态机落库,加一层启动时的崩溃恢复——服务重启后扫video_tasks里所有非终态任务,按"有没有拿到供应商任务号"分两条路处理:没有就重新入队,有就去问供应商真实状态,完成就补结算、失败就退款、还在跑就交给轮询队列续跑。这里有个关键设计:恢复逻辑不能和正在运行的 Worker 抢,所以启动时强制覆盖、周期性巡检时跳过活跃 job。 第三是积分一致性:每次生成前先冻结积分,写一条credit_holds记录,成功结算、失败退款。credit_holds上有一个只对status='held'生效的部分唯一索引,同一个任务最多只能有一条活跃冻结,从数据库层面杜绝重复扣减。另外我有一个定时对账巡检,专门抓五种异常:余额与流水漂移、赠送/付费双余额拆分漂移、多退、冻结悬挂超 30 分钟、余额为负。④ 结果与规模
现在线上稳定运行,代码库 2,500 多个提交、后端 15 万行 TypeScript、135 组数据库迁移,测试文件 300 多个。
【备注讲解】2 分钟版的骨架是 业务 → 形状 → 我的三块活 → 数字。三块活要和简历三条 bullet 一一对上,面试官接下来就会顺着这三块挖。每一块都要留一个"钩子":BullMQ 去重、启动恢复、部分唯一索引——钩子的作用是把面试官引到你准备最深的地方。
四、5 分钟版(加上架构细节)
【口述】
(前半段用 2 分钟版,然后展开下面三段)
⑤ 一次生成的完整链路
拿最常见的"通用生成"举例,一次请求走下来是这样:
- 前端带 JWT 提交生成请求,后端先做配额与积分校验——限流是按用户名而不是按 IP 计的,因为按 IP 的话攻击者换个 IP 就是零成本,而这里要保护的是账号里的钱。
- 校验通过后冻结积分:在一个数据库事务里
SELECT ... FOR UPDATE锁住用户行,扣减余额、写credit_holds冻结日志、写user_credit_transactions流水——三件事要么都成、要么都不成。- 提交给供应商拿到外部任务号,然后把冻结记录的 task_id 从平台临时号重绑到供应商任务号,这样后续结算和退款都能按同一个 key 找到它。
- 提交完立刻释放 Worker 槽位,转成轮询:入
video-poll队列,jobId 是poll-<taskId>-<第几轮>。任务提交只要几秒,但供应商生成要几分钟——所以 Worker 不能一直占着,这是队列拆分的原因。- 轮询到成片后收尾:画质增强(480P 生成 → 超分到 720P/1080P)、临时 URL 转存到自有存储、生成封面,然后写
video_history、把video_tasks推到done、最后结算冻结积分。- 失败路径反过来:标失败 + 退款(把资金退回用户余额并把冻结标记
refunded)。⑥ 状态机与崩溃恢复
状态集合是
pending / queued / processing / done / failed / cancelled,其中processing还带一个phase字段表示细分阶段(submitting→polling),前端就能显示"提交中/生成中"。 崩溃恢复是按阶段分支的:有外部任务号的任务去问供应商真实状态,没拿到外部任务号的看是否超过 5 分钟再重投。这里最麻烦的不是恢复本身,而是有些链路是"内存自管"的——长视频、批量镜头、复杂复刻这些任务的状态在进程内存里,重启就丢了。它们绝不能被通用恢复逻辑接管,否则会被误判成失败、甚至清零点数。所以恢复逻辑里有一长串按generateMode/sourceMode的分支拦截,并且对每个拦截都写清了"为什么会误杀"。⑦ 钱为什么不出错
三层防护:
- 加锁层:Redis 分布式锁 + 数据库行锁
FOR UPDATE双重串行化,Redis 锁拿不到也不会误报失败,退化成数据库行锁串行化。- 约束层:
credit_holds的部分唯一索引防重复冻结;user_credit_transactions记录每一笔余额变动,是唯一真相。- 巡检层:定时对账抓五种异常(漂移、多退、悬挂、负余额、双余额拆分不符),只读不改,异常报出来人来看。
【备注讲解】5 分钟版的杀手锏是第 ⑤ 段的时序。面试官听完这段,通常下一个问题就是"为什么提交和轮询要拆两条队列"或者"并发收尾怎么办"——这两个问题你都有非常具体的答案(见
04-任务队列与状态机深挖问答.md的 Q3、Q9)。
五、10 分钟版(主动引导深挖方向)
5 分钟版讲完,主动加一段"我认为这个系统最难的三件事",把面试官引到你的强区:
【口述】
我自己复盘下来,这个系统最难的是三件事:
第一件,是"多段视频的一致性"。 供应商单次生成有秒数上限(比如 30 秒),超过就要分段生成再拼接。但分段拼接最难的不是拼接,是段与段之间不能跳——人换了脸、商品换了包装、声音换了人,用户一眼就看出来。我们的做法是三层层层兜底:把上一段的尾帧抽出来当作下一段的首帧锚(画面连续);把第一段的人声抽出来当后续段的音色参考(音色一致);再在提示词里加"承接上一段"和"尾帧定格"的编译约束(语义连续)。这三层都要落库,因为一旦进程重启,得能从"已经花钱生成过的段"继续往下跑,不能重复烧钱。
第二件,是"钱和任务状态必须同时正确"。 这里的难点是异步系统里这两件事天然不同步:任务成功但进程崩在写库之前、供应商超时但其实成功了、用户浏览器关了导致收尾没跑完。我们的答案是:把权威状态的判断从"前端轮询"挪到"服务端巡检"。这个改动是有代价的——之前有一条链路是前端轮询独占收尾的,结果用户一关页面任务就永久停在"生成中"、积分永久冻结,生产上有一条批次这样挂了 68 小时。改成服务端巡检对照供应商状态收尾之后,这类悬挂才归零。
第三件,是"多供应商的差异收敛"。 不同供应商的 API 形状完全不一样:有的是同步返回、有的是异步任务 + 轮询、参数名和状态码都不一样。我们做了一层 Provider 适配器(契约在
backend/types/providers.ts,实现放services/video/providers/),对上层只暴露createTask / queryStatus / poll几个方法。后来出现了一件更有意思的事:我们把主通道从火山引擎的 Seedance 切到了腾讯云 VOD 的 AIGC,但老实现一个都没删——因为轮询是拿数据库里存的provider字段去取实现的,删掉实现会让发布时还在途的老任务全部轮询失败,那就是"钱付了、片子拿不到"。所以废弃通道的实现要一直保留到最后一个在途任务结束。【备注讲解】这一段的作用是抢答"你觉得难点在哪"。主动交底困难 + 已经解决,比让面试官自己挖出来从容得多。注意第三段里"老实现不删"这个细节——它体现的是"你懂生产发布",字节的面试官很吃这套。
六、三个记忆锚点(全程反复用)
如果只记三件事,记这三件,任何问题都能往回收:
- 提交与轮询分离:Worker 只负责"提交给供应商"(数秒级),拿到外部任务号就立刻交给轮询队列(分钟级)。理由是保住并发槽位,也是全链路可靠性的基础。
- 部分唯一索引防重复扣费:
credit_holds (task_id) WHERE status='held'唯一 —— 用数据库约束而不是应用层判断来兜住"同一任务只能冻结一次",因为应用层判断在并发和重启下都会漏。 - 权威状态在服务端,不在浏览器:凡是"任务能不能收尾"的判断,都必须由服务端巡检对照真实供应商状态得出,前端轮询只做展示。这是拿一次 68 小时的生产事故换来的结论。
七、雷区清单(念到就等于自曝)
| 不能说 | 为什么 | 应该说 |
|---|---|---|
| "数据库用的 MySQL" | 代码里是 PostgreSQL 17,pg 驱动,迁移全是 PG 语法(JSONB、TIMESTAMPTZ、部分唯一索引)。面试官让你写条 SQL 就穿帮 | "PostgreSQL 17";如果简历已投出去,见 10 文档的修正话术 |
| "集成了 6 个供应商,都在用" | 现在新任务全部走腾讯云 VOD,火山 Ark/Seedance、筷子科技都已停用(resolveProvider() 直接返回 'tencent-vod') | "历史接入过 5 家、目前在生产链路里主动使用 1 家,其余保留实现供在途任务轮询" |
| "Docker Compose 一键部署" | compose 里只有 PostgreSQL 和 Redis 两个基础设施容器,Node 服务是 pm2 托管 + nginx 反代 | "基础设施用 Docker Compose 起 PG/Redis,应用侧是 pm2 + nginx,发布走一个自研的零静默失败脚本" |
| "全站 100% TypeScript 类型安全" | 根 package.json 有 TypeScript 6,但路由层大量 require + any,且历史上存在 @ts-nocheck | "全栈统一 TypeScript,后端用 tsx 直跑;类型强度是分层的,新写的 service 是强类型,老路由层还有 any 没收干净" |
| "我独立从 0 到 1 做了整个项目" | 仓库 2,533 commits 由 8+ 位作者共同提交,你约 269 commits。被问"团队多大、你负责哪块"会立刻露馅 | "团队协作项目,我负责后端异步链路和积分一致性这几块",然后亮出 01 第一节的归属表 |
| "支持 10+ 种生成方式,都是我做的" | 功能域确实有 10+ 种,但实现分散在多人手里 | "平台支持 10 多种生成方式,我主导其中首尾帧、分镜、快节奏这几条,其他链路我读过、能讲清设计但我不是作者" |
八、被问"这个项目是不是 AI 写的"(必答)
字节面试官现在大概率会直接问这个问题。不要否认,也不要自贬。三段式答法:
【口述】
是 AI 深度参与的——我们团队是重度用 AI 编程的,很多实现是先让模型起草、我再改。 但有两件事是我自己把住的: 第一是设计决策。比如"轮询必须加 2 小时硬超时"、"冻结记录要用部分唯一索引"、"恢复逻辑在启动时必须强制覆盖、周期巡检时必须跳过活跃 job"——这些判断模型给不出来,因为它是从我踩过的坑里长出来的。仓库里那些写了很长的注释解释"为什么这么改"的地方,基本都是事故之后我补的。 第二是验证。我给关键路径补了单测:轮询超时策略、身份判定、限流窗口判定这些"改错就直接造成资损或误封"的逻辑,我都抽成纯函数单独测。后端现在有 349 个测试文件。 我不会说"每一行都是我手写的",但这个系统的可靠性设计是我的。
【备注讲解】关键是给 AI 参与划一条可验证的界线:设计决策 + 验证责任是人做的。千万不要说"AI 写的我也不太懂"——那等于放弃这个项目。也不要说"全是我手写的"——代码风格和提交历史都会出卖你。
九、反问清单(面试尾声用,问出水平)
按优先级排,挑 2~3 个问:
- "你们这边的 AI 视频链路,单条成片的 P99 延迟大概是什么量级?瓶颈是在模型侧还是在后处理拼接?"(显示你懂这条链路的真实瓶颈)
- "生成任务这类长耗时异步链路,你们是自研任务编排还是用现有队列?失败任务的积分/额度回滚是怎么保证不重不漏的?"(把话题引到你的强区)
- "团队现在做 AI 视频,是更偏模型能力接入,还是更偏工程编排和交付质量?"(判断岗位定位)
- "如果我进来,前三个月最希望我补上的是哪一块?"(面试标准收尾)
不要问:加班多不多、用不用 AI 写代码(会显得你对项目没想法)、薪资福利(留给 HR 面)。