三段代码 + 一张共享数据库怎么变成一条流水线 — 部署、请求流、LLM 路由、API 地图、worker 模块。
三块独立部署的代码,没有消息总线、没有 RPC 框架;它们靠 Supabase 这张共享数据库交换状态。这是新人最常误解的地方 —— "Vercel 怎么通知 worker?" 答案是:不通知。Vercel 写一行进 scrape_jobs(status=pending),worker 自己轮询读到再执行。
仪表盘 + API + Cron
App Router,(dashboard) 路由组承载所有运营页面,/api/* 是薄 API 层,/api/cron/* 是 Vercel Cron 触发器。鉴权用 Supabase SSR;src/middleware.ts 把未登录用户重定向到 /login,但 cron 路径绕过中间件改用 CRON_SECRET Bearer。
事实源 + Auth + RLS
21 个 migration(supabase/migrations/)按编号顺序就是项目演化史。多租户隔离靠 tenant_scope RLS 政策,按 app.accessible_org_ids() 自动过滤。客户端走 src/lib/supabase.ts 的 createServerClient(SSR 带 cookie);服务端跨租户工作才用 createServiceClient(绕 RLS)。
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 顺序如下。
/api/cron/scrape · 入队
给每个 source_accounts.is_active=true 的号源写一行 scrape_jobs(status=pending, job_type=scrape_source),立即返回 200。不阻塞。
worker/services/queue.py 每 ~30s 扫一次 pending,挑一行执行:discover_videos_from_profile → scrape_video_comments → 写 videos/comments → 标 completed。失败累 attempts,到 QUEUE_MAX_ATTEMPTS 才放弃。
/api/cron/analyze · LLM 评分
抓 intent_score IS NULL 的评论分批喂 DeepSeek V3 → 写回 intent_score/intent_label。高意向(≥7)继续起草首发文案 → 写 messages + message_queue(status=pending_approval)。
/queue 点通过 → /api/messages/approve → message_queue.status=approved。
/api/cron/send · 批量调 worker
每 5 分钟扫一次 approved,调 Fly worker /send,Playwright 驱动浏览器(或 iPhone WDA)发出 DM。必须解 protobuf 校验业务级 status_code,HTTP 200 不等于真投递。
/api/cron/check-replies · 查回复
worker 收件箱轮询,新对话写入 messages,conversations.stage 自动从 ice_break 推到 nurture。LLM 检测决策信号触发 handed_off。
/api/cron/daily-stats · 出日报
把当日发送量、回复率、转化率快照写进 daily_stats。
/scrape/source/{id} —— 不入队,worker 内联跑出结果立即返回。给运营即时反馈用,不走 cron 异步管线。
src/lib/llm.ts 是 provider-agnostic 包装器,能对接任何 OpenAI 兼容接口。2026-04-25 起按用途分流到两个模型:
| 用途 | 调用 | 当前模型 | 覆盖 env |
|---|---|---|---|
| 对话/人格 | chatReply() | qwen/qwen3-235b-a22b | LLM_CHAT_MODEL |
| 批量评分/工具调用 | analyzeComments() · detectDecisionSignals() | deepseek/deepseek-chat-v3-0324 | LLM_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 表 —— 让模型自然引用,而不是进入"档案查询"模式。
按业务域分组,全在 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 | — | 管理面板辅助、品牌档案、评论查询、统计快照 |
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) |
/send | DM 发送(已切到本地 Mac / iPhone WDA,Fly Web 路径废弃) |
/queue/tick · /queue/stats | 手动队列操作(运维用) |
services/comment_api.py | f2 签名直调抖音 Web API(fast path,比 Playwright 快一个数量级) |
services/browser.py | Chromium 单例懒加载(1GB VM 强制) |
services/queue.py | 背景轮询 scrape_jobs |
dm_ios_wda*.py | iPhone WDA 自动化抖音 app(V1.5 起) |
dm_ios_wda_inbox_poll.py | 收件箱轮询(双向跟进 Phase 1+2) |
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 | 每日发送量 / 回复率 / 转化率快照 |
followup_rules / knowledge_articles / scrape_keywords 三张全局共享表外,所有业务表 RLS 政策都是 org_id IN app.accessible_org_ids()。改 schema 时不要漏给新表加这条政策。
5 个密钥配齐 + 跑 npm run dev 就能起。
| 位置 | 命令 |
|---|---|
| 前端 dev | npm install && npm run dev → localhost:3000/login |
| 前端 lint / type | npm run lint · tsc --noEmit |
| 健康体检 | npm run test:all(data audit + schema probe + LLM smoke) |
| worker dev | cd worker && uvicorn main:app --reload --port 8000 |
| worker deploy | cd worker && fly deploy(必须 cd 子目录) |
| 变量 | 谁要 | 作用 |
|---|---|---|
NEXT_PUBLIC_SUPABASE_URL · _ANON_KEY | 前端浏览器 | Supabase 客户端 |
SUPABASE_SERVICE_ROLE_KEY | 仅 server 端 | 跨租户操作;不要 import 进 client component |
OPENROUTER_API_KEY 或 LLM_API_KEY | 前端 server | LLM 网关 |
FLY_WORKER_URL · FLY_WORKER_SECRET | 前后双方 | Vercel ↔ worker 双向 Bearer |
CRON_SECRET | Vercel cron 路由 | Vercel 自动注入;cron 路径绕中间件靠它鉴权 |