Community编程与开发github.com

SaaS 状态一流

为生产级软件即服务产品提供多租户、基于使用量的计费、PLG、AI 功能集成、现代可观测性、合规和安全的技能/项目。

SaaS 状态一流 是什么?

这个技能全面涵盖了构建和运营现代 SaaS 产品的核心模式。它指导用户如何选择并实施多租户隔离策略(共享模式、按租户模式、按数据库模式或混合模式),并重点强调了通过 PostgreSQL 行级安全(RLS)等技术强制执行数据隔离。在计费方面,该技能推崇将支付处理委托给 Stripe 等提供商,并详细说明了基于订阅和基于使用量(计量)的计费模式,包括处理 webhook 事件以实现状态同步。此外,它还深入探讨了产品驱动增长(PLG)策略、AI 功能集成(特别是 RAG 和流式响应)、功能标志(功能开关)以及降低“价值实现时间”(TTV)的优化入职流程。该技能适用于所有主流 AI 代理平台(如 Claude Code、Cursor、Codex),因为它提供的是一般性 SaaS 架构和实现指南,不依赖特定平台。

兼容平台~Claude Code✓Codex CLI~Cursor
npx skills add Satcomx00-x00/skills-db

Installed? Explore more 编程与开发 skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

在你喜欢的 AI 中提问

打开一个已预加载此 Agent Skill 的新对话。

文档

saas

SaaS 的最新实践——为生产级软件即服务产品提供多租户、基于使用量的计费、PLG、AI 功能集成、现代可观测性、合规和安全性。

说明

多租户

根据您的用例选择合适的隔离模型:

模型描述适用场景
共享模式所有租户共享同一张表;使用 tenant_id 标记行早期阶段、成本敏感、合规要求简单
按租户模式(Schema-per-tenant)每个租户拥有独立的数据库 schema中等隔离需求、易于按租户迁移
按数据库模式(DB-per-tenant)每个租户拥有独立的数据库企业级、严格的数据驻留要求、高合规(SOC 2、HIPAA)
混合模式核心数据共享,敏感数据隔离拥有混合层级产品的受监管行业

共享模式租户规则:

  • 每个查询必须通过 tenant_id 进行过滤——通过基础仓库或 ORM 中间件强制执行,而非依赖开发人员自律
  • 使用 PostgreSQL 行级安全(RLS) 作为第二道防线:
    ALTER TABLE orders ENABLE ROW LEVEL SECURITY;
    CREATE POLICY tenant_isolation ON orders
      USING (tenant_id = current_setting('app.current_tenant')::uuid);
    
  • 在每个租户作用域的表上为 tenant_id 创建索引
  • 编写集成测试来验证跨租户数据隔离
  • 限定所有对象存储路径:s3://bucket/{tenantId}/uploads/{fileId}
  • 永远不要暴露可预测的自增 ID——使用 UUID(v7 支持排序)

订阅与计费

固定费率订阅

  • 将计费委托给提供商(Stripe、Paddle、LemonSqueezy)——永远不要在内部实现支付处理
  • 仅在数据库中存储 customerId 和 subscriptionId;永远不要存储卡号
  • 通过 webhook 同步状态——将您的数据库视为计费提供商事实的最终一致只读副本:
    customer.subscription.created  → 激活计划
    customer.subscription.updated  → 升级 / 降级 / 座位变更
    customer.subscription.deleted  → 取消 + 开始宽限期
    invoice.payment_failed         → 催缴:通知 → 限制 → 取消
    
  • 使用 webhook 事件 id 作为幂等键——在处理前存储它;如果已存在则跳过
  • 存储原始 webhook 负载以用于审计和重放

基于使用量/计量计费(现代默认)

  • 实时跟踪使用事件(API 调用、座位数、消耗的 token、存储 GB):
    await stripe.subscriptionItems.createUsageRecord(subscriptionItemId, {
      quantity: tokensUsed,
      timestamp: Math.floor(Date.now() / 1000),
      action: 'increment',
    });
    
  • 向客户展示实时用量仪表盘——账单日出现意外会导致流失
  • 在每个计费周期内实施软限制(80% 时警告)和硬限制(100% 时阻止)
  • 考虑采用免费层级 + 基于使用量的向上销售作为产品驱动增长(PLG)的入门

产品驱动增长(PLG)

  • 免费层 / 免费增值 — 让用户在付费前体验价值;设置限制使付费计划明显物有所值
  • 价值实现时间(TTV) — 衡量并最小化从注册到第一个有意义操作(“啊哈时刻”)的时间
  • 应用内升级提示 — 在用户达到计划限制的上下文时刻显示升级操作,而不是在通用设置页面
  • 产品合格线索(PQL) — 根据产品使用信号(不仅仅是演示请求)对用户评分,并转给销售跟进
  • 病毒式/协作功能 — 邀请团队成员、分享报告、公开页面——每个动作都是一个分发渠道

AI 功能集成

现代 SaaS 产品需要提供 AI 原生功能。应用以下模式:

AI 助手和副驾驶:

  • 使用流式响应(SSE / ReadableStream),让用户实时看到生成的 token——绝不能让他们等待完整响应
  • 始终使 AI 输出可编辑——用户需要能够更正、优化和拥有输出
  • 显示置信度信号并引用来源(RAG)以建立信任

检索增强生成(RAG):

  • 在隔离的命名空间(Pinecone、pgvector、Qdrant)中存储每个租户的向量嵌入——绝不能混合租户数据
  • 写入时嵌入内容;读取时通过余弦相似度查询
  • 智能地切分文档(512–1024 token,重叠);当源内容更改时重新嵌入

使用和成本控制:

  • 跟踪每个租户的 LLM token 消耗——相应地计费或者限流
  • 缓存确定性提示(相同输入 → 相同输出)以降低成本
  • 对于分类/分流任务使用更便宜的模型;保留前沿模型用于生成任务

数据隐私:

  • 提供数据处理附录(DPA),明确声明租户数据是否用于模型训练
  • 提供退出选项;企业客户会要求这一点

功能标志与计划门控

  • 在服务层对功能进行门控——客户端隐藏只是 UI 优化,不是安全措施
  • 将授权逻辑集中到一个单一的 can(tenant, feature) 辅助函数中:
    function can(tenant: Tenant, feature: Feature): boolean {
      return tenant.plan.features.includes(feature);
    }
    
  • 使用标志系统(LaunchDarkly、Unleash、GrowthBook 或简单的基于数据库的表)将功能部署与功能发布解耦
  • 使用标志的场景:渐进式发布(% 的租户)、测试计划、A/B 测试、终止开关

入职引导

  • 最小化 TTV:在 < 5 分钟内引导用户完成第一个有价值的操作
  • 使用清单驱动的入职引导流程,带有清晰的进度指示器
  • 预填充示例/演示数据,使产品在首次使用时不会感觉空荡荡
  • 仅收集最低限度的数据;其他所有内容推迟收集
  • 基于产品事件(不仅仅是时间)触发入职引导邮件:注册时欢迎,24 小时后如果未完成第二步则提醒,第一个里程碑时庆祝
  • 提供交互式产品导览(而非视频),让用户通过实践学习

可观测性与运维

结构化日志 — 每条日志行必须包含:

{ "level": "info", "msg": "...", "tenantId": "...", "userId": "...", "requestId": "...", "timestamp": "..." }

配备 OpenTelemetry 的分布式追踪:

  • 在框架级别进行插桩(Next.js instrumentation.ts、Express 中间件)
  • 跨服务边界传播追踪上下文(traceparent 标头)
  • 导出到您选择的后端(Jaeger、Honeycomb、Datadog、Grafana Tempo)

需要跟踪的每个租户指标:

  • 活跃用户(DAU/MAU)、API 调用量、错误率、p50/p95/p99 延迟、功能采用率、存储消耗

告警:

  • 对租户级别的异常进行告警——突然的错误率飙升、使用量下降、计费失败
  • 按层级定义 SLO:免费(尽力而为)、专业(99.5%)、企业(99.9%)

管理工具(内部):

  • 为支持目的模拟租户(审核每次模拟操作)
  • 查看审计日志、管理订阅、覆盖功能标志、触发重新入职引导
  • 构建健康仪表盘:按租户状态、最近错误、使用趋势

数据、分析与合规

事件跟踪:

  • 将结构化产品事件(user.signed_up、feature.used、subscription.upgraded)发送到数据仓库(Snowflake、BigQuery)或 CDP(Segment、PostHog、RudderStack)
  • 使用这些事件进行流失预测、同期群分析和 PLG 评分

GDPR/隐私:

  • 提供数据导出端点和账户删除端点——GDPR 要求,企业买家也期望
  • 记录您的数据保留政策;实施自动清理
  • 对营销 cookie 和分析使用同意管理

SOC 2/合规卫生(企业就绪):

  • 维护所有改变状态用户操作(谁、做了什么、何时、从哪个 IP)的审计日志
  • 实施基于角色的访问控制(RBAC),默认最小权限
  • 对所有管理员账户强制要求 MFA;为企业租户提供 SSO(SAML/OIDC)
  • 定期进行依赖项漏洞扫描和渗透测试
  • 维护漏洞披露政策

数据隔离与安全

  • 针对每个 API 调用,根据已验证会话验证 tenant_id——永远不要信任请求体
  • 按租户进行速率限制,以防止“吵闹邻居”效应
  • 对数据库中的 API 密钥进行哈希处理(bcrypt 或 Argon2);绝不存储明文
  • 要求 HTTPS;设置较长时间 max-age 的 Strict-Transport-Security 头
  • 会话 cookie 使用 HttpOnly; Secure; SameSite=Lax 属性

示例

// 具有 RLS 上下文的租户作用域仓库
class TenantRepository<T> {
  constructor(
    private readonly tenantId: string,
    private readonly db: PrismaClient,
  ) {}

  protected async withTenantContext<R>(fn: () => Promise<R>): Promise<R> {
    return this.db.$transaction(async (tx) => {
      await tx.$executeRaw`SELECT set_config('app.current_tenant', ${this.tenantId}, true)`;
      return fn();
    });
  }

  async findById(id: string): Promise<T | null> {
    return this.withTenantContext(() =>
      (this.db as any).findFirst({ where: { id, tenantId: this.tenantId } })
    );
  }
}

// 具有按租户用量计费的 AI 流式端点
export async function POST(req: Request) {
  const session = await getSession(req);
  const { prompt } = await req.json();

  const stream = await openai.chat.completions.create({
    model: 'gpt-4o',
    messages: [{ role: 'user', content: prompt }],
    stream: true,
  });

  let tokensUsed = 0;
  const readable = new ReadableStream({
    async start(controller) {
      for await (const chunk of stream) {
        const text = chunk.choices[0]?.delta?.content ?? '';
        controller.enqueue(new TextEncoder().encode(text));
        tokensUsed += chunk.usage?.completion_tokens ?? 0;
      }
      controller.close();
      // 流完成后记录用量
      await meteringService.record(session.tenantId, 'ai_tokens', tokensUsed);
    },
  });

  return new Response(readable, { headers: { 'Content-Type': 'text/plain; charset=utf-8' } });
}

// 具有幂等性的 Stripe webhook
app.post('/webhooks/stripe', express.raw({ type: 'application/json' }), async (req, res) => {
  const event = stripe.webhooks.constructEvent(
    req.body, req.headers['stripe-signature']!, process.env.STRIPE_WEBHOOK_SECRET!
  );

  const seen = await db.webhookEvent.findUnique({ where: { stripeEventId: event.id } });
  if (seen) return res.sendStatus(200);
  await db.webhookEvent.create({ data: { stripeEventId: event.id, payload: event } });

  switch (event.type) {
    case 'customer.subscription.updated':
      await subscriptionService.sync(event.data.object);
      break;
    case 'customer.subscription.deleted':
      await subscriptionService.cancel(event.data.object.metadata.tenantId);
      break;
  }

  res.sendStatus(200);
});

参考

相关技能