library-picturebook-activity/docs/TENANT_IMPLEMENTATION.md
2025-12-09 11:10:36 +08:00

223 lines
5.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 多租户系统实现方案
## 实现概述
已成功实现完整的多租户系统,包括以下核心功能:
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. 测试租户识别:验证各种租户识别方式都能正常工作