02 · ARCHITECTURE

系统架构详解

三段代码 + 一张共享数据库怎么变成一条流水线 — 部署、请求流、LLM 路由、API 地图、worker 模块。

📅 2026-05-01

三段式部署

三块独立部署的代码,没有消息总线、没有 RPC 框架;它们靠 Supabase 这张共享数据库交换状态。这是新人最常误解的地方 —— "Vercel 怎么通知 worker?" 答案是:不通知。Vercel 写一行进 scrape_jobs(status=pending),worker 自己轮询读到再执行。

🖥️ Vercel · Next.js 16

仪表盘 + API + Cron

App Router,(dashboard) 路由组承载所有运营页面,/api/* 是薄 API 层,/api/cron/* 是 Vercel Cron 触发器。鉴权用 Supabase SSR;src/middleware.ts 把未登录用户重定向到 /login,但 cron 路径绕过中间件改用 CRON_SECRET Bearer。

🗄️ Supabase Postgres

事实源 + Auth + RLS

21 个 migration(supabase/migrations/)按编号顺序就是项目演化史。多租户隔离靠 tenant_scope RLS 政策,按 app.accessible_org_ids() 自动过滤。客户端走 src/lib/supabase.tscreateServerClient(SSR 带 cookie);服务端跨租户工作才用 createServiceClient(绕 RLS)。

🤖 Fly.io Worker · Python

FastAPI + Playwright (NRT)

部署为 akke-worker-prod,1GB VM 只能开一个 Chromium。worker/main.py 装 routers;worker/services/browser.py 懒加载浏览器实例。所有 HTTP 路由用 FLY_WORKER_SECRET Bearer 守门。worker/fly.toml 决定镜像,但 deploy 必须 cd worker 否则 build context 1.9GB。

端到端请求流

从早上 9 点的第一次抓取到晚上 23 点的日报汇总,一天里 cron 顺序如下。

同步路径例外:UI 上"立即抓取"按钮调的是 /scrape/source/{id} —— 不入队,worker 内联跑出结果立即返回。给运营即时反馈用,不走 cron 异步管线。

LLM 层(双模型路由)

src/lib/llm.ts 是 provider-agnostic 包装器,能对接任何 OpenAI 兼容接口。2026-04-25 起按用途分流到两个模型

用途调用当前模型覆盖 env
对话/人格chatReply()qwen/qwen3-235b-a22bLLM_CHAT_MODEL
批量评分/工具调用analyzeComments() · detectDecisionSignals()deepseek/deepseek-chat-v3-0324LLM_ANALYSIS_MODEL
全栈降级(应急)全部LLM_MODEL(覆盖以上两个)

关键导出

函数作用
analyzeComments批量意向评分 → {id, score, label, keywords}[]
chatReply按 stage 选系统提示词(ice_break/nurture/decision),中文人设小艾
detectDecisionSignals检测客户表达决策意向("加微信"/"约时间"),触发 handoff
extractLeakedToolCalls · sanitizeReply清洗 DeepSeek V3 偶发把 function<|tool▁sep|>NAME 协议字符泄漏到正文

跨会话客户记忆

src/lib/memory.ts 浅合并所有 (org_id, douyin_user_id) 相同 conversation 的 customer_profile JSONB。注入到 prompt 时是自然语言段落而非 KV 表 —— 让模型自然引用,而不是进入"档案查询"模式。

API 路由地图

按业务域分组,全在 src/app/api/。中间件守 UI 路由;cron 路由用 CRON_SECRET Bearer 自守;worker 反向 Bearer 用 FLY_WORKER_SECRET

分组关键路由作用
auth/api/auth/*Supabase SSR 鉴权回调
cron/api/cron/{scrape,analyze,send,check-replies,daily-stats}Vercel Cron 入口,全部 vercel.json 声明
accounts/api/accounts/* · DELETE /api/accounts/[id]抖音账号池管理;删除调 delete_account_safe RPC 自决策硬删 vs 归档
sources/api/sources/* · POST /api/sources/import-batch号源管理;批量导入走分享短链 302 解析
scrape/api/scrape/jobs异步抓取入队(Phase 1,2026-04-22 上线)
messages/api/messages/approve · /api/messages/reject人审闸门
knowledge/api/knowledge/*品牌话术 + 金牌对话样本管理
chat/api/chat会话回复(chatReply 入口)
handoffs/api/handoffs/*转交微信记录
invitations/api/invitations/*组织成员邀请
staff/api/staff/*Akke 内部团队管理面板
admin / brand / comments / stats管理面板辅助、品牌档案、评论查询、统计快照

Worker 内部模块

FastAPI 路由声明在 worker/main.py,按职能拆到 worker/routers/。所有路由用 utils/auth.py:verify_secret 作为 FastAPI 依赖鉴权。

路由 / 模块作用
/scrape抓单条视频评论
/scrape/discover从博主主页或视频页发现关联视频
/scrape/source/{id}同步触发某号源抓取(UI "立即抓取" 按钮)
/scrape/resolve分享文案 → URL(302 跟随)
/scrape/resolve-share-batch批量分享文案 → sec_uid(号源批量导入用)
/scrape/user-profile抓抖音用户自填城市(高意向 enrich)
/sendDM 发送(已切到本地 Mac / iPhone WDA,Fly Web 路径废弃)
/queue/tick · /queue/stats手动队列操作(运维用)
services/comment_api.pyf2 签名直调抖音 Web API(fast path,比 Playwright 快一个数量级)
services/browser.pyChromium 单例懒加载(1GB VM 强制)
services/queue.py背景轮询 scrape_jobs
dm_ios_wda*.pyiPhone WDA 自动化抖音 app(V1.5 起)
dm_ios_wda_inbox_poll.py收件箱轮询(双向跟进 Phase 1+2)

数据 schema 速查

21 个 migration 累计的 schema,按业务域分。完整 DDL 在 supabase/migrations/

身份与租户

作用
organizations客户公司
user_org_memberships用户 ↔ 组织映射 + 角色(Owner/Operator)
brand_profile每个 org 一份:品牌话术、微信号、二维码、报价规则。2026-04-23 收紧为 per-org(之前是全局)。

账号池

作用
accounts抖音账号;type='scraping' | 'messaging' 区分;org_id NOT NULL(migration 014 起硬隔离)
source_accounts号源 — 我们要持续抓哪些博主

采集管线

作用
videos视频元数据 + 描述
comments评论 + IP 属地(migration 019)+ 城市(migration 021)+ intent_score/intent_label
scrape_jobs异步抓取队列;result 列含进度(migration 009)

对话管线

作用
conversations抖音用户 ↔ 我方账号;stage 状态机(ice_break → nurture → decision → handed_off → closed);customer_profile JSONB(自然语言客户记忆)
messages所有发出 / 收到的消息原文。status 只接受 pending_approval,写 pending 会触发 CHECK 报错。
message_queue人审闸门:pending_approval / approved / rejected / sent没有 content 字段,内容写在 messages

知识库与统计

作用
knowledge_documents客户录入的话术 / 报价 / 案例。buildKnowledgeSection 截取前 1500 字注入 prompt。
conversation_examples金牌对话 few-shot
daily_stats每日发送量 / 回复率 / 转化率快照
RLS 提醒:followup_rules / knowledge_articles / scrape_keywords 三张全局共享表外,所有业务表 RLS 政策都是 org_id IN app.accessible_org_ids()。改 schema 时不要漏给新表加这条政策。

本地开发

5 个密钥配齐 + 跑 npm run dev 就能起。

位置命令
前端 devnpm install && npm run devlocalhost:3000/login
前端 lint / typenpm run lint · tsc --noEmit
健康体检npm run test:all(data audit + schema probe + LLM smoke)
worker devcd worker && uvicorn main:app --reload --port 8000
worker deploycd worker && fly deploy(必须 cd 子目录)

必需环境变量

变量谁要作用
NEXT_PUBLIC_SUPABASE_URL · _ANON_KEY前端浏览器Supabase 客户端
SUPABASE_SERVICE_ROLE_KEY仅 server 端跨租户操作;不要 import 进 client component
OPENROUTER_API_KEYLLM_API_KEY前端 serverLLM 网关
FLY_WORKER_URL · FLY_WORKER_SECRET前后双方Vercel ↔ worker 双向 Bearer
CRON_SECRETVercel cron 路由Vercel 自动注入;cron 路径绕中间件靠它鉴权