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