13 代码地图与事实索引(面试前抽查用)
用途:正文里的行号会随代码迭代漂移,不要背行号。面试前用这一篇按符号名抽查几处,确认口径没变。 每节格式:要证明什么 → 文件(符号)→ 一句话事实 → 复查命令。
一、30 秒复查脚本(面试前跑一遍,确认关键事实没变)
bash
cd /Users/binhy/Binhy-Projects/shatangAI
# ① 数据库是 PostgreSQL(不是 MySQL)
grep -n '"pg"' backend/package.json && grep -n "postgres:17" compose.yml
grep -rn "mysql2" backend/package.json || echo "✅ 无 mysql2"
# ② 供应商:只有腾讯云在跑
sed -n '1,30p' backend/services/video/providers/index.ts
# ③ 部分唯一索引还在
grep -rn "WHERE status = 'held'" backend/migrations/*.sql
# ④ 轮询超时与间隔表
grep -n "POLL_INTERVALS_SEC\|POLL_TIMEOUT_MS\|MAX_POLL_ATTEMPTS" backend/services/queue/pollTimeoutPolicy.ts
# ⑤ 启动/周期恢复 + 对账巡检的调度
grep -n "recoverStalledTasks\|recoverOrphanedCreditHolds\|creditAudit\|setInterval" backend/index.ts
# ⑥ 自管链路的拦截清单(数一下几条)
grep -c "recovery\] Skip" backend/services/taskRecovery.ts
# ⑦ 测试规模
find backend -path '*__tests__*' -name '*.test.ts' | wc -l
ls backend/migrations/*.up.sql | wc -l
# ⑧ 类型强度(诚实边界)
grep -rn "@ts-nocheck" backend --include=*.ts | wc -l
grep -n '"strict"' tsconfig.json backend/tsconfig.json admin/tsconfig.json二、按主题索引
A. 任务队列与生命周期(你的主场,必须全熟)
| 要证明什么 | 文件(符号) | 一句话事实 |
|---|---|---|
| 提交队列定义与 jobId 去重 | backend/services/queue/videoQueue.ts(getQueue / enqueueVideoTask / forceEnqueueVideoTask) | jobId = taskId;attempts: 1(业务自己重试);enqueueVideoTask 当前无调用方,只有恢复逻辑用 forceEnqueueVideoTask |
| 提交 Worker 的幂等与阶段推进 | backend/services/queue/videoWorker.ts(executeTask) | 三层守卫(终态跳过 / 历史权威 / 已有外部号不重复提交);submitting → polling 后立刻投轮询 job |
| 自管链路的 skip 清单 | 同文件 :67-94 | longVideo / shots / script-library 三条;注释里写了完整资损链(空 prompt 烧钱出废片) |
| 轮询队列(冒号 jobId 坑) | backend/services/queue/videoPollQueue.ts(safeJobIdPart) | BullMQ 的 jobId 不能含 :;delay=0 时故意不带 jobId |
| 轮询 Worker 主流程 | backend/services/queue/videoPollWorker.ts(processStandardPollJob) | 四态归一化;自管链路前置拦截(字幕擦除 volcengine、换脸 tencent-mps 不在注册表里) |
| 收尾(最强的一段) | 同文件 finalizeStandardTask | 音轨校验 → Redis SET NX 抢占 → 超分 → 转存 → 写库 → 最后才结算 |
| 轮询超时策略 | backend/services/queue/pollTimeoutPolicy.ts | 间隔表 [10,10,10,15,20,30,30,30];2 小时由表反推(MAX_POLL_ATTEMPTS=243) |
| 崩溃恢复 | backend/services/taskRecovery.ts(recoverStalledTasks) | 启动 + 每 10 分钟;9 条自管链路拦截;Case 1/2 分支;启动强制覆盖 active |
| 供应商任务不存在判定 | backend/services/queue/providerNotFound.ts | 先剥随机 RequestId 再匹配(判 not_found 会直接退款,宁可漏判) |
| 僵尸 job 清理 | backend/scripts/clean-zombie-polls.ts | 手工脚本,不在定时任务里;默认 dry-run |
| 僵尸/存活心跳 | backend/index.ts | 启动顺序:迁移 → Redis → 8 个 Worker → 恢复(任务/积分/支付)→ 每 10 分钟巡检 |
B. 积分与钱
| 要证明什么 | 文件(符号) | 一句话事实 |
|---|---|---|
| 冻结表 + 部分唯一索引 | backend/migrations/009_credit_holds.up.sql | 这条迁移是你写的(1/1 提交);UNIQUE (task_id) WHERE status='held' |
| 真冻结(事务内) | backend/services/user/userCreditsStore.ts(holdCredits) | 扣减 + hold + 流水在同一事务;冲突则 throw 回滚 |
| 立即扣减 + 事后补日志 | 同文件(consumeUserCredits / journalCreditHold) | 主流 /generate 链路走这条 → 有"已扣款未建任务"窗口 |
| 结算 / 退款(CAS 幂等) | 同文件(settleCredits / settleCreditsWithAdjustment / refundCredits) | 都是 WHERE status='held';差额结算 refundOnly(只退不补) |
| 双余额拆分 | backend/services/billing/creditSplit.ts | 先扣赠送池;退款按冻结时记下的原比例 |
| 拆分不变式 | backend/migrations/20260823100000_dual_credit_ledger_check.up.sql | CHECK (gift_credits + paid_credits = credits) |
| 悬空冻结恢复 | backend/services/creditRecovery.ts | 扫 held 超 10 分钟;ve_ 前缀最先分流 |
| 对账巡检(五类异常) | backend/services/billing/creditAudit.ts | 漂移 / 拆分漂移 / 多退 / 悬挂 / 负余额;多退按金额不按笔数 |
| 兜底扣费的双重幂等 | backend/services/queue/videoPollWorker.ts(deductCreditsIfNeeded) | 同时查流水和 credit_holds;注释记着 37 单/14 用户/370 分事故 |
| 按实际时长结算口径 | backend/services/billing/deliveredDurationSettlement.ts | 只退不补 / ceil / 失败回落原结算 |
| 锁 | backend/services/lock/creditLock.ts | SET NX EX 10 + Lua 比对释放;拿不到不阻断 |
C. 支付与鉴权
| 要证明什么 | 文件 | 一句话事实 |
|---|---|---|
| 渠道抽象 | backend/services/payment/channels/types.ts | createOrder / queryOrder / closeOrder / isConfigured;ChannelId = wechat_native | alipay_precreate |
| 微信支付(你写的) | backend/services/payment/wxpayClient.ts | 手写 HTTP;WECHATPAY2-SHA256-RSA2048 签名、RSA 验签、AES-GCM 解密(末 16 字节取 tag) |
| 回调路由(raw 顺序) | backend/routes/payment.ts + backend/index.ts | express.raw 必须在全局 express.json() 之前注册 |
| 入账四层幂等 | backend/services/payment/creditTopupService.ts | 订单行 FOR UPDATE + credit_idempotency_key 短路 + crediting 中间态 + UNIQUE 兜底 |
| 关单判定纯函数 | backend/services/queue/pollDecision.ts | 本地过期 ≠ 上游未付;严格大于(宁可多等不误关) |
| 启动恢复 | backend/services/payment/paymentRecovery.ts | 先查上游再关单;查单失败保留重试 |
| 微信扫码登录(你写的) | backend/services/wechat/wechatOAuth.ts | 开放平台 snsapi_login;state 存 Redis 防重放 |
| JWT | backend/services/auth/userToken.ts | HS256 / 7d;SECRET_KEY 有硬编码兜底(已知风险) |
| 身份解析 | backend/middleware/userResolver.ts | 验签 + 实时比 token_version → 覆写 x-login-user;LEGACY_HEADER_AUTH 决定裸头是否采信 |
| 限流 | backend/middleware/rateLimiter.ts | IP+path 固定窗口 + 按用户名的动作配额(要保护的是账号里的钱) |
D. 供应商与合成
| 要证明什么 | 文件 | 一句话事实 |
|---|---|---|
| 只有腾讯云在跑 | backend/services/video/providers/index.ts | resolveProvider() 恒返回 'tencent-vod';老实现保留是为了在途任务轮询 |
| Provider 契约 | backend/types/providers.ts | createTask / queryStatus / poll / generateVideo |
| 能力表 | backend/services/video/modelCapabilities.ts | {maxRefImages, maxSegmentSec, supportsEdit};查表前要归一到基础模型 id |
| 模型/定价目录 | backend/services/modelCatalog.ts | retired 通道 → 腾讯替身映射;pricingServiceKey 显式字段 |
| 首尾帧降级 | backend/services/video/providers/tencentVod.ts | 有参考媒体时 FirstFrame/LastFrame 强制降级为 Reference |
| 多段一致性(四层) | backend/services/queue/productionWorker.ts | 尾帧→首帧锚 + 音色锚;段三态机 generated/done + planFingerprint 复用 |
| 提示词约束 | backend/services/production/promptCompiler.ts | 承接块 + 尾帧定格(被称为"连贯性链的单点故障");硬约束置底 |
| 拼接(自研 ffmpeg) | backend/services/production/stitcher.ts | 逐段归一化 → concat;loudnorm 改"先测量再线性增益" |
| ffmpeg 封装 | backend/services/media/mediaUtils.ts | execFile 调系统二进制,FFMPEG_PATH 可覆盖 |
| 音轨门禁 | backend/services/media/videoAudioGuard.ts | 必须有可用音轨,否则阻止发布 + 退款 + 告警 |
| 生成方式清单 | backend/services/navCatalog.ts | 19 个导航标签 / 14 种真实生成方式 |
E. 存储与文件生命周期
| 要证明什么 | 文件 | 一句话事实 |
|---|---|---|
| 存储适配器 | backend/services/storage/index.ts | 实际只有 2 个后端(OSS + 本地);isRealOssEnabled() 为假时静默降级到本地磁盘 |
| 引用存在性判定 | backend/migrations/20260709120000_oss_delete_queue_guard.up.sql(oss_key_in_use) | 跨 12+ 张业务表判断;后续迁移扩充到对话/脚本快照 |
| 生命周期服务 | backend/services/storage/ossLifecycleService.ts | 临时 7 天 / 待删 30 天;物理删除前四处复检 |
| 四次误删事故 | 20260709120000 / 20260709160000 / 20260709170000 / 20260909120000 迁移注释 | "先全删再全插";中文文件名 percent-encoding 造成双杀链;try_ons 跨素材共享被误伤;对话快照未覆盖 → 1638 处引用约 1136 处失联 |
F. 基础设施与工程化
| 要证明什么 | 文件 | 一句话事实 |
|---|---|---|
| 发布脚本 | scripts/deploy.sh | 7 步、set -euo pipefail、HEAD 断言、按 pm_cwd 推断进程名、探活期望 401 |
| 基础设施容器 | compose.yml | 只有 PG 17 + Redis 7,无 app service;默认值与生产不一致 |
| 配置收口 | backend/config/index.ts | 181 个 env、约 33 个顶层配置键(25 个分组 + 8 个标量)、IIFE 派生 enabled;零必填校验 |
| 自研迁移 | backend/scripts/migrate.ts + backend/services/db/migrationRunner.ts | checksum 校验、重复版本检测硬失败;无 advisory lock |
| 连接池 | backend/services/db.ts | max 20 / min 2 / idle 30s / maxUses 7500;缺 statement_timeout 与 ssl |
| CI | .github/workflows/ci.yml | 只有后端单测一个 job;2026-08-08 起因欠费未执行 |
| hook | .githooks/pre-push | 跑后端单测,但 core.hooksPath 未设置 → 未安装 |
| 健康检查 | backend/index.ts(/api/health) | 查 PG + Redis + ffmpeg/ffprobe,返回 ok/degraded |
G. 前端
| 要证明什么 | 文件 | 一句话事实 |
|---|---|---|
| 路由 | src/router/routes.tsx | react-router 7,真路由(约 57 条) |
| 请求层(两套并存) | src/shared/lib/apiClient.ts + src/shared/utils/request.ts | 前者 139 处引用(401 登出 / 402 充值 / 429 toast) |
| App.tsx 已不存在 | src/(无 App.tsx) | 已拆成 page + feature hook;巨型转移到 useAssistantChat.ts 等 hook |
| 长任务轮询 | features/video-history/useVideoHistory | react-query refetchInterval:非终态 3 秒,终态停止 |
| 进度条真相 | useDisplayProgress / getDisplayProgress | max(真实/持久化/模拟/阶段下限) 后 min 到 cap;模拟用 mulberry32 播种 |
| 管理后台 | admin/ | 独立应用;唯一 strict:true 且 build 跑 tsc -b;Recharts 三张图 |
| 共享规则 | shared/*.json | 前后端共用 9 份 JSON(错误词典、分镜、计价等) |
三、"作者归属"复查(面试前务必确认一次)
bash
cd /Users/binhy/Binhy-Projects/shatangAI
for f in backend/services/taskRecovery.ts backend/services/queue/videoWorker.ts \
backend/services/user/userCreditsStore.ts \
backend/migrations/009_credit_holds.up.sql \
backend/services/payment/wxpayClient.ts \
backend/services/wechat/wechatOAuth.ts \
backend/services/queue/productionWorker.ts \
backend/services/modelCatalog.ts; do
tot=$(git log --oneline -- "$f" | wc -l | tr -d ' ')
me=$(git log --oneline --author=se_hyxiong@163.com -- "$f" | wc -l | tr -d ' ')
printf "%-56s total=%-4s mine=%s\n" "$f" "$tot" "$me"
done当前结论(可作为"我负责哪块"的依据)
| 模块 | 你的占比 | 说法 |
|---|---|---|
taskRecovery.ts | 14/33 | 我主导 |
videoWorker.ts | 10/19 | 我主导 |
videoPollQueue.ts | 你起的头 | 我引入的 |
userCreditsStore.ts | 11/45 | 核心实现我参与 |
creditRecovery.ts | 3/9 | 我参与 |
009_credit_holds.up.sql | 1/1 | 完全是我写的 |
wxpayClient.ts | 2/2 | 我写的 |
wechatOAuth.ts | 1/1 | 我写的 |
userResolver.ts | 1/3 | 我参与(JWT 覆写身份那层) |
rateLimiter.ts | 1/7 | 我参与(用户名维度配额) |
productionWorker.ts | 0/35 | 同事主导 → 只说"我从恢复和结算侧参与,能讲清分段续跑" |
modelCatalog.ts | 0/30 | 同事主导 → "模型与定价的权威清单,我读过但不负责维护" |
四、口径漂移的四个高危点(每次面试前重新确认)
| 高危点 | 为什么容易漂 | 确认方式 |
|---|---|---|
| 供应商数量与"唯一通道" | 上游一直在变(半年内从火山切到腾讯) | 看 providers/index.ts 的 resolveProvider 返回什么 |
enqueueVideoTask 有没有调用方 | 一旦有人开始用队列提交,bullet ① 的说法要改 | grep -rn "enqueueVideoTask" backend --include=*.ts |
LEGACY_HEADER_AUTH 生产取值 | 决定 X-Login-User 能否被伪造,仓库里查不到 | 上服务器查 backend/.env |
SECRET_KEY 生产取值 | 若是默认值 = 完整认证绕过 | 上服务器查 backend/.env(最优先) |
五、仓库里"会误导你"的文档(别拿它们当依据)
| 文档 | 问题 |
|---|---|
ARCH.md | 停留在 TS 迁移早期:写"App.tsx 约 10k 行""无自动化测试""51 个文件迁移完成",全部过期 |
README.md | 写"MySQL 存储用户和积分数据"(错的)、USE_DB_AUTH(代码 0 命中)、"跳过 provider='unknown'"(该分支不存在) |
CHORE.md | 不是技术债日志,是"TS 迁移 Agent Prompt";写"51 个文件全量迁移、tsc 零错误"与现状严重不符 |
docs/architecture/provider-integration-guide.md | 写"默认 kuaizi""MYNEW_PREFER 灰度开关""按 model 前缀路由"——代码里全都不存在 |
docs/db.md | 标题写"生产级高可用 PostgreSQL",实际内容是规划阶段待评审,引用的 dbHealth.ts 从未创建 |
ARCH.md / docs/* 里的"零错误"表述 | 实际:根与 backend strict: false、后端 54 个 @ts-nocheck |
结论:本套材料以代码为准;给面试官引文档时,只引"迁移注释里的历史事故记录",那是真实证据。