钉钉 AI 客服:多租户架构设计

2 阅读1分钟

钉钉 AI 客服:多租户架构设计

多租户是 SaaS 化 AI 客服的核心技术。


一、多租户概念

单租户:每个客户独立部署 多租户:共享部署,数据隔离

对比单租户多租户
成本
隔离完全逻辑
维护复杂简单

二、数据隔离

2.1 数据库隔离

-- 方案1:独立数据库
CREATE DATABASE tenant_1;
CREATE DATABASE tenant_2;

-- 方案2:共享数据库,schema 隔离
CREATE SCHEMA tenant_1;
CREATE SCHEMA tenant_2;

-- 方案3:共享表,tenant_id 隔离
ALTER TABLE chats ADD COLUMN tenant_id VARCHAR(64);
CREATE INDEX idx_tenant ON chats(tenant_id);

2.2 查询过滤

// 中间件自动注入 tenant_id
app.use((req, res, next) => {
  req.tenantId = req.headers['x-tenant-id'];
  next();
});

// 查询时自动过滤
async function getChats(tenantId) {
  return db.query('SELECT * FROM chats WHERE tenant_id = ?', [tenantId]);
}

三、配置管理

3.1 租户配置表

CREATE TABLE tenants (
  id VARCHAR(64) PRIMARY KEY,
  name VARCHAR(128),
  plan VARCHAR(32), -- basic/pro/enterprise
  config JSON,
  created_at TIMESTAMP
);

3.2 配置加载

async function getTenantConfig(tenantId) {
  const cached = cache.get(`config:${tenantId}`);
  if (cached) return cached;
  
  const config = await db.query(
    'SELECT config FROM tenants WHERE id = ?',
    [tenantId]
  );
  
  cache.set(`config:${tenantId}`, config, 3600);
  return config;
}

四、资源限制

4.1 套餐限制

const PLAN_LIMITS = {
  basic: {
    dailyChats: 100,
    knowledgeBaseItems: 100,
    users: 5
  },
  pro: {
    dailyChats: 1000,
    knowledgeBaseItems: 1000,
    users: 50
  },
  enterprise: {
    dailyChats: -1, // 无限
    knowledgeBaseItems: -1,
    users: -1
  }
};

4.2 限流实现

async function checkLimit(tenantId, resource) {
  const usage = await getUsage(tenantId, resource);
  const limit = PLAN_LIMITS[tenant.plan][resource];
  
  if (limit > 0 && usage >= limit) {
    throw new Error('超出套餐限制');
  }
}

五、计费系统

5.1 用量统计

async function recordUsage(tenantId, resource, amount = 1) {
  await redis.incrby(`usage:${tenantId}:${resource}:${today()}`, amount);
}

async function getUsage(tenantId, resource) {
  return await redis.get(`usage:${tenantId}:${resource}:${today()}`) || 0;
}

5.2 账单生成

async function generateBill(tenantId, month) {
  const usage = await getMonthlyUsage(tenantId, month);
  const plan = await getTenantPlan(tenantId);
  
  const bill = {
    baseFee: PLAN_PRICES[plan],
    usageFees: calculateUsageFees(usage),
    total: baseFee + usageFees
  };
  
  return bill;
}

六、安全性

6.1 数据隔离验证

// 所有查询必须包含 tenant_id
function validateQuery(sql) {
  if (!sql.includes('tenant_id')) {
    throw new Error('查询缺少 tenant_id');
  }
}

6.2 API 鉴权

app.use((req, res, next) => {
  const token = req.headers['authorization'];
  const tenant = verifyToken(token);
  
  if (!tenant) {
    return res.status(401).json({ error: '无效令牌' });
  }
  
  req.tenantId = tenant.id;
  next();
});

七、监控运维

7.1 租户监控

async function getTenantMetrics(tenantId) {
  return {
    dailyChats: await getUsage(tenantId, 'chats'),
    avgResponseTime: await getAvgResponseTime(tenantId),
    errorRate: await getErrorRate(tenantId)
  };
}

7.2 告警规则

if (tenant.errorRate > 0.05) {
  await sendAlert(`租户 ${tenantId} 错误率过高`);
}

项目地址:GitHub - dingtalk-connector-pro 有问题欢迎 Issue 或评论区交流