钉钉 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 或评论区交流