223 lines
5.8 KiB
Markdown
223 lines
5.8 KiB
Markdown
|
|
# 多租户系统实现方案
|
|||
|
|
|
|||
|
|
## 实现概述
|
|||
|
|
|
|||
|
|
已成功实现完整的多租户系统,包括以下核心功能:
|
|||
|
|
|
|||
|
|
1. ✅ **租户管理模块**:创建、查看、更新、删除租户
|
|||
|
|
2. ✅ **数据隔离**:用户、角色、权限、菜单等数据按租户隔离
|
|||
|
|
3. ✅ **租户识别**:支持多种方式识别租户(请求头、子域名、JWT Token)
|
|||
|
|
4. ✅ **超级租户**:可以创建租户并分配菜单
|
|||
|
|
5. ✅ **菜单分配**:超级租户可以为租户分配菜单
|
|||
|
|
|
|||
|
|
## 核心变更
|
|||
|
|
|
|||
|
|
### 1. 数据库Schema变更
|
|||
|
|
|
|||
|
|
- 新增 `Tenant` 表(租户表)
|
|||
|
|
- 新增 `TenantMenu` 表(租户菜单关联表)
|
|||
|
|
- 在以下表添加 `tenantId` 字段:
|
|||
|
|
- `User`
|
|||
|
|
- `Role`
|
|||
|
|
- `Permission`
|
|||
|
|
- `Dict`
|
|||
|
|
- `Config`
|
|||
|
|
- 调整唯一性约束:从全局唯一改为租户内唯一
|
|||
|
|
|
|||
|
|
### 2. 新增模块
|
|||
|
|
|
|||
|
|
- **TenantsModule**: 租户管理模块
|
|||
|
|
- `TenantsController`: 租户CRUD接口
|
|||
|
|
- `TenantsService`: 租户业务逻辑
|
|||
|
|
- `TenantGuard`: 租户识别守卫(可选,当前未全局启用)
|
|||
|
|
- `Tenant`/`TenantId` 装饰器:获取当前租户信息
|
|||
|
|
|
|||
|
|
### 3. 修改的模块
|
|||
|
|
|
|||
|
|
- **AuthModule**:
|
|||
|
|
- 登录时支持租户识别
|
|||
|
|
- JWT Token中包含租户ID
|
|||
|
|
- 用户验证时检查租户匹配
|
|||
|
|
|
|||
|
|
- **UsersModule**:
|
|||
|
|
- 所有操作自动添加租户过滤
|
|||
|
|
- 创建用户时自动关联租户
|
|||
|
|
|
|||
|
|
- **RolesModule**:
|
|||
|
|
- 所有操作自动添加租户过滤
|
|||
|
|
- 创建角色时自动关联租户
|
|||
|
|
|
|||
|
|
- **PermissionsModule**:
|
|||
|
|
- 所有操作自动添加租户过滤
|
|||
|
|
- 创建权限时自动关联租户
|
|||
|
|
|
|||
|
|
- **MenusModule**:
|
|||
|
|
- 用户菜单查询基于租户分配的菜单
|
|||
|
|
- 菜单管理仍为全局(超级租户管理)
|
|||
|
|
|
|||
|
|
## 使用步骤
|
|||
|
|
|
|||
|
|
### 1. 数据库迁移
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
cd backend
|
|||
|
|
npm run prisma:migrate:dev -- --name add_tenant_support
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2. 初始化超级租户
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
npm run init:super-tenant
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
这将创建:
|
|||
|
|
- 超级租户(code: `super`)
|
|||
|
|
- 超级管理员(username: `admin`, password: `admin123`)
|
|||
|
|
- 基础权限
|
|||
|
|
|
|||
|
|
### 3. 创建租户
|
|||
|
|
|
|||
|
|
使用超级管理员登录后,通过API创建租户:
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
POST /api/tenants
|
|||
|
|
Headers:
|
|||
|
|
Authorization: Bearer <token>
|
|||
|
|
X-Tenant-Code: super
|
|||
|
|
Body:
|
|||
|
|
{
|
|||
|
|
"name": "租户A",
|
|||
|
|
"code": "tenant-a",
|
|||
|
|
"menuIds": [1, 2, 3]
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 4. 租户用户登录
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
POST /api/auth/login
|
|||
|
|
Headers:
|
|||
|
|
X-Tenant-Code: tenant-a
|
|||
|
|
Body:
|
|||
|
|
{
|
|||
|
|
"username": "user1",
|
|||
|
|
"password": "password123"
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 租户识别方式
|
|||
|
|
|
|||
|
|
系统支持以下方式识别租户(按优先级):
|
|||
|
|
|
|||
|
|
1. **请求头 `X-Tenant-Id`**: 直接指定租户ID
|
|||
|
|
2. **请求头 `X-Tenant-Code`**: 通过租户编码识别
|
|||
|
|
3. **子域名**: 从Host头提取子域名匹配
|
|||
|
|
4. **JWT Token**: Token中包含的tenantId
|
|||
|
|
|
|||
|
|
## 数据隔离机制
|
|||
|
|
|
|||
|
|
所有数据查询都会自动添加租户过滤条件:
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
// 示例:查询用户
|
|||
|
|
const where = tenantId ? { tenantId } : {};
|
|||
|
|
const users = await prisma.user.findMany({ where });
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
确保:
|
|||
|
|
- 每个租户只能看到自己的数据
|
|||
|
|
- 不同租户的数据完全隔离
|
|||
|
|
- 超级租户可以管理所有租户
|
|||
|
|
|
|||
|
|
## 菜单分配机制
|
|||
|
|
|
|||
|
|
- 菜单是全局的(由超级租户管理)
|
|||
|
|
- 通过 `TenantMenu` 表关联租户和菜单
|
|||
|
|
- 用户只能看到分配给其租户的菜单
|
|||
|
|
- 超级租户可以为租户分配/取消分配菜单
|
|||
|
|
|
|||
|
|
## API接口
|
|||
|
|
|
|||
|
|
### 租户管理
|
|||
|
|
|
|||
|
|
- `POST /api/tenants` - 创建租户
|
|||
|
|
- `GET /api/tenants` - 获取租户列表
|
|||
|
|
- `GET /api/tenants/:id` - 获取租户详情
|
|||
|
|
- `PATCH /api/tenants/:id` - 更新租户(包括菜单分配)
|
|||
|
|
- `DELETE /api/tenants/:id` - 删除租户
|
|||
|
|
- `GET /api/tenants/:id/menus` - 获取租户菜单树
|
|||
|
|
|
|||
|
|
### 其他接口
|
|||
|
|
|
|||
|
|
所有其他接口(用户、角色、权限等)都支持租户隔离,会自动根据请求中的租户信息过滤数据。
|
|||
|
|
|
|||
|
|
## 前端集成
|
|||
|
|
|
|||
|
|
### 1. 请求拦截器添加租户信息
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
service.interceptors.request.use((config) => {
|
|||
|
|
const tenantCode = localStorage.getItem('tenantCode');
|
|||
|
|
if (tenantCode) {
|
|||
|
|
config.headers['X-Tenant-Code'] = tenantCode;
|
|||
|
|
}
|
|||
|
|
return config;
|
|||
|
|
});
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2. 登录后保存租户信息
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
// 登录成功后
|
|||
|
|
localStorage.setItem('tenantCode', response.data.user.tenantCode);
|
|||
|
|
localStorage.setItem('tenantId', response.data.user.tenantId);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 注意事项
|
|||
|
|
|
|||
|
|
1. **数据迁移**: 如果现有系统已有数据,需要将现有数据关联到超级租户
|
|||
|
|
2. **唯一性**: 用户名、邮箱等在租户内唯一,不同租户可以有相同的用户名
|
|||
|
|
3. **超级租户**: 超级租户不能被删除,且拥有所有权限
|
|||
|
|
4. **菜单管理**: 菜单是全局的,但通过分配机制实现租户级别的菜单显示
|
|||
|
|
|
|||
|
|
## 文件清单
|
|||
|
|
|
|||
|
|
### 新增文件
|
|||
|
|
|
|||
|
|
- `backend/src/tenants/` - 租户模块
|
|||
|
|
- `tenants.controller.ts`
|
|||
|
|
- `tenants.service.ts`
|
|||
|
|
- `tenants.module.ts`
|
|||
|
|
- `dto/create-tenant.dto.ts`
|
|||
|
|
- `dto/update-tenant.dto.ts`
|
|||
|
|
- `guards/tenant.guard.ts`
|
|||
|
|
- `decorators/tenant.decorator.ts`
|
|||
|
|
- `backend/scripts/init-super-tenant.ts` - 初始化超级租户脚本
|
|||
|
|
- `backend/docs/TENANT_GUIDE.md` - 详细使用指南
|
|||
|
|
|
|||
|
|
### 修改文件
|
|||
|
|
|
|||
|
|
- `backend/prisma/schema.prisma` - 数据库Schema
|
|||
|
|
- `backend/src/app.module.ts` - 添加TenantsModule
|
|||
|
|
- `backend/src/auth/` - 认证相关修改
|
|||
|
|
- `backend/src/users/` - 用户服务修改
|
|||
|
|
- `backend/src/roles/` - 角色服务修改
|
|||
|
|
- `backend/src/permissions/` - 权限服务修改
|
|||
|
|
- `backend/src/menus/` - 菜单服务修改
|
|||
|
|
|
|||
|
|
## 后续优化建议
|
|||
|
|
|
|||
|
|
1. **租户守卫**: 可以全局启用TenantGuard,自动识别租户
|
|||
|
|
2. **租户配置**: 支持租户级别的系统配置
|
|||
|
|
3. **租户统计**: 添加租户使用统计功能
|
|||
|
|
4. **数据导出**: 支持租户数据导出和备份
|
|||
|
|
5. **租户主题**: 支持租户级别的UI主题定制
|
|||
|
|
|
|||
|
|
## 测试建议
|
|||
|
|
|
|||
|
|
1. 测试租户数据隔离:确保不同租户的数据不会互相访问
|
|||
|
|
2. 测试菜单分配:验证租户只能看到分配的菜单
|
|||
|
|
3. 测试超级租户权限:验证超级租户可以管理所有租户
|
|||
|
|
4. 测试租户识别:验证各种租户识别方式都能正常工作
|
|||
|
|
|