Skip to content

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
后端 TypeScript156,056 行 / 618 个 service 文件 / 38 个路由文件find backend -name '*.ts'
前端187,676 行 / 49 个页面 / 54 个 feature 域find src
数据库迁移135 组 .up.sql/.down.sqlbackend/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/33backend/services/taskRecovery.ts、creditRecovery.ts
视频任务提交 Worker10/19backend/services/queue/videoWorker.ts
轮询队列设计你起的头backend/services/queue/videoPollQueue.ts(feat: video poll to mq)
积分冻结/结算/退款内核11/45backend/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/3backend/middleware/userResolver.ts
限流(IP 维度 + 用户名维度配额)1/7backend/middleware/rateLimiter.ts
OSS 生命周期(临时追踪 + 删除队列)1/8storage/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 分钟版,然后展开下面三段)

⑤ 一次生成的完整链路 ​

拿最常见的"通用生成"举例,一次请求走下来是这样:

  1. 前端带 JWT 提交生成请求,后端先做配额与积分校验——限流是按用户名而不是按 IP 计的,因为按 IP 的话攻击者换个 IP 就是零成本,而这里要保护的是账号里的钱。
  2. 校验通过后冻结积分:在一个数据库事务里 SELECT ... FOR UPDATE 锁住用户行,扣减余额、写 credit_holds 冻结日志、写 user_credit_transactions 流水——三件事要么都成、要么都不成。
  3. 提交给供应商拿到外部任务号,然后把冻结记录的 task_id 从平台临时号重绑到供应商任务号,这样后续结算和退款都能按同一个 key 找到它。
  4. 提交完立刻释放 Worker 槽位,转成轮询:入 video-poll 队列,jobId 是 poll-<taskId>-<第几轮>。任务提交只要几秒,但供应商生成要几分钟——所以 Worker 不能一直占着,这是队列拆分的原因。
  5. 轮询到成片后收尾:画质增强(480P 生成 → 超分到 720P/1080P)、临时 URL 转存到自有存储、生成封面,然后写 video_history、把 video_tasks 推到 done、最后结算冻结积分。
  6. 失败路径反过来:标失败 + 退款(把资金退回用户余额并把冻结标记 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 字段去取实现的,删掉实现会让发布时还在途的老任务全部轮询失败,那就是"钱付了、片子拿不到"。所以废弃通道的实现要一直保留到最后一个在途任务结束。

【备注讲解】这一段的作用是抢答"你觉得难点在哪"。主动交底困难 + 已经解决,比让面试官自己挖出来从容得多。注意第三段里"老实现不删"这个细节——它体现的是"你懂生产发布",字节的面试官很吃这套。


六、三个记忆锚点(全程反复用) ​

如果只记三件事,记这三件,任何问题都能往回收:

  1. 提交与轮询分离:Worker 只负责"提交给供应商"(数秒级),拿到外部任务号就立刻交给轮询队列(分钟级)。理由是保住并发槽位,也是全链路可靠性的基础。
  2. 部分唯一索引防重复扣费:credit_holds (task_id) WHERE status='held' 唯一 —— 用数据库约束而不是应用层判断来兜住"同一任务只能冻结一次",因为应用层判断在并发和重启下都会漏。
  3. 权威状态在服务端,不在浏览器:凡是"任务能不能收尾"的判断,都必须由服务端巡检对照真实供应商状态得出,前端轮询只做展示。这是拿一次 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 个问:

  1. "你们这边的 AI 视频链路,单条成片的 P99 延迟大概是什么量级?瓶颈是在模型侧还是在后处理拼接?"(显示你懂这条链路的真实瓶颈)
  2. "生成任务这类长耗时异步链路,你们是自研任务编排还是用现有队列?失败任务的积分/额度回滚是怎么保证不重不漏的?"(把话题引到你的强区)
  3. "团队现在做 AI 视频,是更偏模型能力接入,还是更偏工程编排和交付质量?"(判断岗位定位)
  4. "如果我进来,前三个月最希望我补上的是哪一块?"(面试标准收尾)

不要问:加班多不多、用不用 AI 写代码(会显得你对项目没想法)、薪资福利(留给 HR 面)。

持续学习,持续构建。