mcpField
M
multitenancy

multitenancy

github.com/claude-dev-suite开发4696 次安装2 天前更新
代码仓库

关于 multitenancy

此技能介绍多租户应用架构,涵盖租户隔离方案和请求级租户识别。内容包括实现模式、反模式及供开发者使用的生产检查清单。

主要功能

  1. 01比较数据库、Schema 与行级隔离
  2. 02展示共享 Schema 的租户范围模式
  3. 03介绍 PostgreSQL 行级安全
  4. 04列出租户识别策略
  5. 05提供生产环境隔离检查清单

适用场景

  1. 01为 SaaS 选择隔离策略
  2. 02为 Prisma 查询限定租户范围
  3. 03应用 PostgreSQL 行级安全
  4. 04上线前审查租户隔离

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/claude-dev-suite/claude-dev-suite --skill multitenancy

安装后在 Claude Code 中输入/multitenancy确认已加载。

详细介绍

多租户架构

隔离策略

策略隔离级别复杂度成本
每个租户一个数据库最高高高
每个租户一个架构高中中
共享架构(tenant_id 列)中低低
行级安全性(RLS)中高中低

使用租户 ID 的共享架构(最常见)

// Middleware: resolve tenant from subdomain or header
function tenantMiddleware(req: Request, res: Response, next: NextFunction) {
  const host = req.hostname; // acme.myapp.com
  const subdomain = host.split('.')[0];
  const tenant = await tenantRepo.findBySubdomain(subdomain);
  if (!tenant) return res.status(404).json({ error: 'Tenant not found' });
  req.tenantId = tenant.id;
  next();
}

// Always filter by tenant
app.get('/api/products', async (req, res) => {
  const products = await db.product.findMany({
    where: { tenantId: req.tenantId },
  });
  res.json(products);
});
使用 Prisma 进行租户范围限定
// Extension to auto-apply tenant filter
const prisma = new PrismaClient().$extends({
  query: {
    $allOperations({ args, query, operation }) {
      if (['findMany', 'findFirst', 'count', 'updateMany', 'deleteMany'].includes(operation)) {
        args.where = { ...args.where, tenantId: getCurrentTenantId() };
      }
      if (['create', 'createMany'].includes(operation)) {
        args.data = { ...args.data, tenantId: getCurrentTenantId() };
      }
      return query(args);
    },
  },
});

PostgreSQL 行级安全性

-- Enable RLS
ALTER TABLE products ENABLE ROW LEVEL SECURITY;

-- Policy: users see only their tenant's data
CREATE POLICY tenant_isolation ON products
  USING (tenant_id = current_setting('app.tenant_id')::uuid);

-- Set tenant context per request
SET app.tenant_id = 'tenant-uuid-here';
SELECT * FROM products; -- auto-filtered
// Set tenant context on each request
pool.on('connect', async (client) => {
  // Set after getting connection from pool
});

async function withTenant<T>(tenantId: string, fn: () => Promise<T>): Promise<T> {
  const client = await pool.connect();
  try {
    await client.query(`SET app.tenant_id = $1`, [tenantId]);
    return await fn();
  } finally {
    await client.query('RESET app.tenant_id');
    client.release();
  }
}

每个租户一个架构

// Dynamic schema selection
function getTenantSchema(tenantId: string): string {
  return `tenant_${tenantId.replace(/-/g, '_')}`;
}

async function createTenantSchema(tenantId: string) {
  const schema = getTenantSchema(tenantId);
  await db.query(`CREATE SCHEMA IF NOT EXISTS ${schema}`);
  await db.query(`SET search_path TO ${schema}`);
  await runMigrations(); // Apply schema migrations
}

租户解析策略

策略示例最适用场景
子域名acme.myapp.comB2B SaaS
路径前缀myapp.com/acme/...简单配置
自定义请求头X-Tenant-ID: acmeAPI 优先
JWT 声明{ tenantId: "acme" }经过身份验证的 API

反模式

反模式解决方法
查询中未进行租户筛选使用中间件或 ORM 扩展自动应用筛选
使用未经验证的客户端提供的租户 ID从身份验证令牌或子域名中获取
未测试租户数据隔离编写测试以验证租户间隔离
共享缓存键未添加租户前缀为所有缓存键添加租户 ID 前缀
未按租户进行速率限制按租户进行速率限制,而非全局限制

生产环境检查清单

  • 在所有路由上配置租户解析中间件
  • 通过自动化测试验证数据隔离
  • 为缓存键添加租户 ID 前缀
  • 按租户进行速率限制
  • 使用租户范围限定的后台任务
  • 租户配置和停用流程
  • 防止跨租户查询(通过 RLS 或 ORM 强制执行)

评论

支持 Markdown