# 多租户系统实现指南 ## 概述 本系统实现了完整的多租户架构,支持: - 每个租户独立的数据隔离(用户、角色、权限、菜单等) - 每个租户独立的访问链接(通过租户编码或域名) - 超级租户可以创建和管理其他租户 - 超级租户可以为租户分配菜单 ## 数据库设计 ### 核心表结构 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` 请求头提取子域名 - 匹配租户的 `code` 或 `domain` 字段 3. **JWT Token方式** - Token中包含 `tenantId` 字段 - 登录时自动关联租户 4. **登录参数方式** - 登录接口支持 `tenantCode` 参数 ## 使用流程 ### 1. 数据库迁移 首先需要生成并执行数据库迁移: ```bash # 生成迁移文件 npm run prisma:migrate:dev -- --name add_tenant_support # 执行迁移 npm run prisma:migrate ``` ### 2. 初始化超级租户 运行初始化脚本创建超级租户: ```bash npm run init:super-tenant ``` 这将创建: - 超级租户(code: `super`) - 超级管理员用户(username: `admin`, password: `admin123`) - 超级管理员角色 - 基础权限 ### 3. 创建普通租户 使用超级租户的管理员账号登录后,通过租户管理接口创建新租户: ```bash POST /api/tenants Headers: Authorization: Bearer X-Tenant-Code: super Body: { "name": "租户A", "code": "tenant-a", "domain": "tenant-a.example.com", "description": "租户A的描述", "menuIds": [1, 2, 3] // 分配的菜单ID列表 } ``` ### 4. 为租户分配菜单 超级租户可以为租户分配菜单: ```bash PATCH /api/tenants/:id Headers: Authorization: Bearer X-Tenant-Code: super Body: { "menuIds": [1, 2, 3, 4, 5] } ``` ### 5. 租户用户登录 租户用户登录时需要指定租户: ```bash POST /api/auth/login Body: { "username": "user1", "password": "password123", "tenantCode": "tenant-a" // 可选,也可以从请求头获取 } ``` 或者在请求头中指定: ```bash POST /api/auth/login Headers: X-Tenant-Code: tenant-a Body: { "username": "user1", "password": "password123" } ``` ### 6. 访问租户数据 所有API请求都会自动根据租户ID过滤数据: ```bash GET /api/users Headers: Authorization: Bearer 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. 请求拦截器 在前端请求拦截器中添加租户信息: ```typescript // 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. 登录时保存租户信息 ```typescript // 登录成功后 localStorage.setItem('tenantCode', response.data.user.tenantCode); localStorage.setItem('tenantId', response.data.user.tenantId); ``` ### 3. 租户切换 如果需要支持租户切换,可以在前端实现租户选择器,切换时更新localStorage中的租户信息并重新加载数据。 ## 权限控制 ### 超级租户权限 超级租户的用户拥有所有权限,包括: - 创建、查看、更新、删除租户 - 为租户分配菜单 - 管理所有租户的数据(如果需要在超级租户中查看所有租户数据) ### 普通租户权限 普通租户的用户只能: - 管理自己租户内的数据 - 查看分配给租户的菜单 - 无法访问其他租户的数据 ## 注意事项 1. **数据隔离**: 所有查询都会自动添加租户过滤条件,确保数据隔离 2. **唯一性**: 用户名、邮箱等在租户内唯一,不同租户可以有相同的用户名 3. **菜单管理**: 菜单是全局的(由超级租户管理),但通过 `TenantMenu` 表分配给各个租户 4. **超级租户**: 超级租户不能被删除,且拥有所有权限 5. **迁移数据**: 如果现有系统已有数据,需要编写迁移脚本将现有数据关联到超级租户 ## 迁移现有数据 如果系统已有数据,需要将现有数据迁移到超级租户: ```sql -- 假设超级租户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. 租户数据导出和备份