library-picturebook-activity/backend/docs/TENANT_GUIDE.md
2025-11-23 14:04:20 +08:00

6.8 KiB
Raw Permalink Blame History

多租户系统实现指南

概述

本系统实现了完整的多租户架构,支持:

  • 每个租户独立的数据隔离(用户、角色、权限、菜单等)
  • 每个租户独立的访问链接(通过租户编码或域名)
  • 超级租户可以创建和管理其他租户
  • 超级租户可以为租户分配菜单

数据库设计

核心表结构

  1. Tenant租户表

    • id: 租户ID
    • name: 租户名称
    • code: 租户编码(唯一,用于访问链接)
    • domain: 租户域名(可选,用于子域名访问)
    • isSuper: 是否为超级租户0-否1-是)
    • validState: 有效状态1-有效2-失效)
  2. TenantMenu租户菜单关联表

    • tenantId: 租户ID
    • menuId: 菜单ID
    • 用于关联租户和菜单,实现菜单分配
  3. 其他表添加租户字段

    • User: 添加 tenantId 字段
    • Role: 添加 tenantId 字段
    • Permission: 添加 tenantId 字段
    • Dict: 添加 tenantId 字段
    • Config: 添加 tenantId 字段

唯一性约束调整

  • User.username: 从全局唯一改为 (tenantId, username) 唯一
  • User.email: 从全局唯一改为 (tenantId, email) 唯一
  • Role.name/code: 从全局唯一改为 (tenantId, name/code) 唯一
  • Permission.code: 从全局唯一改为 (tenantId, code) 唯一
  • 其他类似字段也做了相应调整

租户识别机制

系统支持多种方式识别租户:

  1. 请求头方式(推荐)

    • X-Tenant-Code: 租户编码
    • X-Tenant-Id: 租户ID
  2. 子域名方式

    • Host 请求头提取子域名
    • 匹配租户的 codedomain 字段
  3. JWT Token方式

    • Token中包含 tenantId 字段
    • 登录时自动关联租户
  4. 登录参数方式

    • 登录接口支持 tenantCode 参数

使用流程

1. 数据库迁移

首先需要生成并执行数据库迁移:

# 生成迁移文件
npm run prisma:migrate:dev -- --name add_tenant_support

# 执行迁移
npm run prisma:migrate

2. 初始化超级租户

运行初始化脚本创建超级租户:

npm run init:super-tenant

这将创建:

  • 超级租户code: super
  • 超级管理员用户username: admin, password: admin123
  • 超级管理员角色
  • 基础权限

3. 创建普通租户

使用超级租户的管理员账号登录后,通过租户管理接口创建新租户:

POST /api/tenants
Headers:
  Authorization: Bearer <token>
  X-Tenant-Code: super
Body:
{
  "name": "租户A",
  "code": "tenant-a",
  "domain": "tenant-a.example.com",
  "description": "租户A的描述",
  "menuIds": [1, 2, 3]  // 分配的菜单ID列表
}

4. 为租户分配菜单

超级租户可以为租户分配菜单:

PATCH /api/tenants/:id
Headers:
  Authorization: Bearer <token>
  X-Tenant-Code: super
Body:
{
  "menuIds": [1, 2, 3, 4, 5]
}

5. 租户用户登录

租户用户登录时需要指定租户:

POST /api/auth/login
Body:
{
  "username": "user1",
  "password": "password123",
  "tenantCode": "tenant-a"  // 可选,也可以从请求头获取
}

或者在请求头中指定:

POST /api/auth/login
Headers:
  X-Tenant-Code: tenant-a
Body:
{
  "username": "user1",
  "password": "password123"
}

6. 访问租户数据

所有API请求都会自动根据租户ID过滤数据

GET /api/users
Headers:
  Authorization: Bearer <token>
  X-Tenant-Code: tenant-a

返回的数据只会包含该租户的用户。

API接口

租户管理接口

  • POST /api/tenants - 创建租户(需要 tenant:create 权限)
  • GET /api/tenants - 获取租户列表(需要 tenant:read 权限)
  • GET /api/tenants/:id - 获取租户详情(需要 tenant:read 权限)
  • PATCH /api/tenants/:id - 更新租户(需要 tenant:update 权限)
  • DELETE /api/tenants/:id - 删除租户(需要 tenant:delete 权限)
  • GET /api/tenants/:id/menus - 获取租户的菜单树(需要 tenant:read 权限)

其他接口

所有其他接口(用户、角色、权限等)都支持租户隔离,会自动根据请求中的租户信息过滤数据。

前端集成

1. 请求拦截器

在前端请求拦截器中添加租户信息:

// utils/request.ts
service.interceptors.request.use(
  (config: InternalAxiosRequestConfig) => {
    const token = getToken();
    const tenantCode = getTenantCode(); // 从localStorage或store获取
    
    if (token && config.headers) {
      config.headers.Authorization = `Bearer ${token}`;
    }
    
    if (tenantCode && config.headers) {
      config.headers['X-Tenant-Code'] = tenantCode;
    }
    
    return config;
  },
  (error) => {
    return Promise.reject(error);
  }
);

2. 登录时保存租户信息

// 登录成功后
localStorage.setItem('tenantCode', response.data.user.tenantCode);
localStorage.setItem('tenantId', response.data.user.tenantId);

3. 租户切换

如果需要支持租户切换可以在前端实现租户选择器切换时更新localStorage中的租户信息并重新加载数据。

权限控制

超级租户权限

超级租户的用户拥有所有权限,包括:

  • 创建、查看、更新、删除租户
  • 为租户分配菜单
  • 管理所有租户的数据(如果需要在超级租户中查看所有租户数据)

普通租户权限

普通租户的用户只能:

  • 管理自己租户内的数据
  • 查看分配给租户的菜单
  • 无法访问其他租户的数据

注意事项

  1. 数据隔离: 所有查询都会自动添加租户过滤条件,确保数据隔离
  2. 唯一性: 用户名、邮箱等在租户内唯一,不同租户可以有相同的用户名
  3. 菜单管理: 菜单是全局的(由超级租户管理),但通过 TenantMenu 表分配给各个租户
  4. 超级租户: 超级租户不能被删除,且拥有所有权限
  5. 迁移数据: 如果现有系统已有数据,需要编写迁移脚本将现有数据关联到超级租户

迁移现有数据

如果系统已有数据,需要将现有数据迁移到超级租户:

-- 假设超级租户ID为1
UPDATE users SET tenant_id = 1 WHERE tenant_id IS NULL;
UPDATE roles SET tenant_id = 1 WHERE tenant_id IS NULL;
UPDATE permissions SET tenant_id = 1 WHERE tenant_id IS NULL;
-- 其他表类似

故障排查

  1. 租户识别失败: 检查请求头是否正确设置或检查JWT token中是否包含tenantId
  2. 数据查询为空: 确认租户ID正确且数据确实属于该租户
  3. 权限不足: 确认用户角色有相应权限,且角色属于正确的租户

扩展功能

未来可以考虑的扩展:

  1. 租户级别的配置(每个租户可以有自己的系统配置)
  2. 租户级别的主题和品牌定制
  3. 租户级别的功能开关
  4. 租户使用统计和监控
  5. 租户数据导出和备份