Appearance
从零发布你的第一个 SaaS(worked example:口语搭子)
状态基准:2026-08-30(SM4-1 试点 ECS-B 真栈验收;完整证据包 = 仓库内
docs/design/3.task/packages/SM4-1/artifacts/ecs_b_acceptance.md,属内部过程资产,不入对外站点)。 读者:要孵化第一个自营 SaaS 的建设者(人类工程师或 AI coding agent)。 性质:本文是真实跑通的完整叙事——下面每一步都在 2026-08-30 的 ECS-B 真栈上实际发生过,数字、事件、回复摘录全部来自验收证据,不是示例假数据。机制的完整参数表在saas-mode-b-integration-guide.md(§7 有同源 15 步 checklist),本文负责把它讲成一个故事。 契约事实源:openapi.yaml。
0. 你要造什么
「口语搭子」——一个职场英语口语 SaaS,两个会话 Agent:
| Agent | 人设 | 单价 |
|---|---|---|
| 日常口语陪练 | 咖啡馆场景日常对话陪练 | 10 分/轮 |
| 面试模拟官 | 产品经理行为面试英文模拟 | 15 分/轮 |
用户注册登录、钱包扣点、租户后台——一行代码都不用写。你写的只有:从模板派生的前端(品牌与页面定制)。
1. 平台侧准备(运营动作,15 步 checklist 的 1~7)
- Keycloak SPA client(public + PKCE,client id
agenthub-oral-buddy)+ 四个 protocol mapper(aud-…/tenant_id/user_id/permissions)——形状参照 mode-b 指南 §1。验收实测:试发 token 的aud同时含 oral-buddy 与 console,四 claim 齐。 - 建正式租户:运营首登 JIT 自动建个人租户后,
POST /tenants升级正式租户(slugoral-buddy,kind=formal,name=口语搭子)。 - 建项目「口语搭子 Web」挂该租户——这是计费归因维度,ID 填进前端
VITE_PROJECT_ID。建项目的 curl 见 hello-agent 教程 §2.1(POST /api/v1/builder/projects;或经 web-builder 界面 Projects 页创建。该端点契约收录登记为跟进项)。 - 建模型组(LLM-chat,空组走平台 fallback 即可起步)。
- 建双 Agent 并发布(人设 / 模型组 / 单价 10 与 15 / 护栏齐全——单价与护栏强制配对,缺一发布被拒)。
判据:GET /api/v1/app/agents?agent_type=conversation 返回的 items 数组长度 = 2(响应无 count 字段)、points_per_turn 10/15(响应键为 snake_case)。
2. 派生你的前端(checklist 8~11)
bash
cd frontend
node scripts/new-saas-app.mjs --name oral-buddy --target ../oral-buddy- workspace 依赖二选一(源码内联或私有 registry),填 env(
VITE_KEYCLOAK_CLIENT_ID/VITE_KEYCLOAK_URL/VITE_PROJECT_ID…),VITE_USE_MOCKS=true先离线冒烟。 - 部署:把
deploy/compose-snippet.yml的 service 块加进编排(服务名web-oral-buddy)。验收实测:GET https://<网关>/oral-buddy/→ HTTP 200,登录页标题即你的品牌「口语搭子 · 职场英语口语陪练」。
3. 用户进来(checklist 12~13)
- 终端用户在你的站点注册 → 平台 JIT 自动建域内用户行;被邀请入域(redeem +
active-tenant切换)后,活跃域 = 口语搭子。 - 运营给首批种子积分:
tenant_grant手动入账(投产开闸前的标准形态;平台充值中心已代码收口,开闸属运维外部前置)。验收实测:+200 分,source=tenant_grant,流水operation_id可对账(请求体expires_at必填——长效积分到期时间)。
4. 见到钱动起来:双轮对话(checklist 14)
用户从目录选 Agent 开会话、各发一轮。验收实测(SSE 事件序列 turn_started → turn_delta → turn_completed):
- 日常口语陪练(扣 10 分),真实上游英文回复开场:
"Hey there! Welcome to the café. What can I get for you today? We've got a great latte special on right now."
- 面试模拟官(扣 15 分):
"Welcome! For a product manager behavioral round, let's kick off with a 1-minute English self-introduction. …"
每轮 turn_completed 携带 points_charged(10 / 15)——前端展示即扣点回显。
5. 对账(checklist 15):每分钱都能回答「花在哪、买了什么」
以运营身份读 GET /api/v1/billing/points/ledger?user_id=<终端>&project_id=<项目>,验收实测五行流水:
| 时间线 | type | amount | operation_id | 含义 |
|---|---|---|---|---|
| t0 | manual_adjust | +200 | sm41-grant-… | 种子入账 |
| t1 | freeze | 10 | turn:25d4ddf8-… | 陪练轮冻结 |
| t2 | deduct | 10 | turn:25d4ddf8-… | 陪练轮划扣 |
| t3 | freeze | 15 | turn:1b1cc78a-… | 面试轮冻结 |
| t4 | deduct | 15 | turn:1b1cc78a-… | 面试轮划扣 |
同一 turn 的 freeze/deduct 共享 operation_id;cost_records 另有两行 call_kind=llm 且 project_id 指向「口语搭子 Web」——模型成本也按项目归因。这就是「强治理执行」在你 SaaS 上的样子。
6. 常见卡点(真栈踩过的坑)
| 症状 | 原因与处置 |
|---|---|
| 终端用户积分页 403 | saas_user 目录缺 billing:points:read/ledger:read(已登记平台任务 SM5-1);开闸前对账走运营侧 ledger |
| 发布被拒(409) | 单价与五项护栏强制配对,缺一不可(cookbook 计费章) |
| SSE 一次性整段到达 | 检查网关 proxy_buffering off(模板 nginx 已配,勿删) |
| 切换租户后行为怪 | active-tenant 响应恒 token_refresh_required: true,客户端必须强制 refresh token(mode-b 指南 §3) |
7. 你学到了什么
- 你的 SaaS = 业务壳;平台 = headless 会话运行时 + 身份 + 钱包(模式 B,ADR-0058 唯一路线)。
- 每一轮消费都可对账:
operation_id串起 freeze/deduct,cost_records记录模型成本与项目归因。 - 下一步深入:
conversation-runtime-protocol.md(SSE 帧与状态机)→saas-integration-cookbook.md(若后续需要自有后端 / 服务端集成)→ 多模态(语音 / 图片 / 文生图,同一会话通道按需启用)。