一、超管端设计优化 - 文档管理SOP体系建立,docs目录重组 - 统一用户管理:跨租户全局视角,合并用户管理+公众用户 - 活动监管全模块重构:全部活动(统计卡片+阶段筛选+SuperDetail详情页)、报名数据/作品数据/评审进度(两层合一扁平列表)、成果发布(去Tab+统计+隐藏写操作) - 菜单精简:移除评委管理/评审规则/通知管理 - Bug修复:租户编辑丢失隐藏菜单、pageSize限制、主色统一 二、UGC绘本创作社区P0 - 数据库:10张新表(user_works/user_work_pages/work_tags等) - 子女账号独立化:Child升级为独立User,家长切换+独立登录 - 用户作品库:CRUD+发布审核,8个API - AI创作流程:提交→生成→保存到作品库,4个API - 作品广场:首页改造为推荐流,标签+搜索+排序 - 内容审核(超管端):作品审核+作品管理+标签管理 - 活动联动:WorkSelector作品选择器 - 布局改造:底部5Tab(发现/创作/活动/作品库/我的) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
7.1 KiB
7.1 KiB
多租户系统实现指南
概述
本系统实现了完整的多租户架构,支持:
- 每个租户独立的数据隔离(用户、角色、权限、菜单等)
- 每个租户独立的访问链接(通过租户编码或域名)
- 超级租户可以创建和管理其他租户
- 超级租户可以为租户分配菜单
数据库设计
核心表结构
-
Tenant(租户表)
id: 租户IDname: 租户名称code: 租户编码(唯一,用于访问链接)domain: 租户域名(可选,用于子域名访问)isSuper: 是否为超级租户(0-否,1-是)validState: 有效状态(1-有效,2-失效)
-
TenantMenu(租户菜单关联表)
tenantId: 租户IDmenuId: 菜单ID- 用于关联租户和菜单,实现菜单分配
-
其他表添加租户字段
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)唯一- 其他类似字段也做了相应调整
租户识别机制
系统支持多种方式识别租户:
-
请求头方式(推荐)
X-Tenant-Code: 租户编码X-Tenant-Id: 租户ID
-
子域名方式
- 从
Host请求头提取子域名 - 匹配租户的
code或domain字段
- 从
-
JWT Token方式
- Token中包含
tenantId字段 - 登录时自动关联租户
- Token中包含
-
登录参数方式
- 登录接口支持
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中的租户信息并重新加载数据。
权限控制
超级租户权限
超级租户的用户拥有所有权限,包括:
- 创建、查看、更新、删除租户
- 为租户分配菜单
- 管理所有租户的数据(如果需要在超级租户中查看所有租户数据)
普通租户权限
普通租户的用户只能:
- 管理自己租户内的数据
- 查看分配给租户的菜单
- 无法访问其他租户的数据
注意事项
- 数据隔离: 所有查询都会自动添加租户过滤条件,确保数据隔离
- 唯一性: 用户名、邮箱等在租户内唯一,不同租户可以有相同的用户名
- 菜单管理: 菜单是全局的(由超级租户管理),但通过
TenantMenu表分配给各个租户 - 超级租户: 超级租户不能被删除,且拥有所有权限
- 迁移数据: 如果现有系统已有数据,需要编写迁移脚本将现有数据关联到超级租户
迁移现有数据
如果系统已有数据,需要将现有数据迁移到超级租户:
-- 假设超级租户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;
-- 其他表类似
故障排查
- 租户识别失败: 检查请求头是否正确设置,或检查JWT token中是否包含tenantId
- 数据查询为空: 确认租户ID正确,且数据确实属于该租户
- 权限不足: 确认用户角色有相应权限,且角色属于正确的租户
扩展功能
未来可以考虑的扩展:
- 租户级别的配置(每个租户可以有自己的系统配置)
- 租户级别的主题和品牌定制
- 租户级别的功能开关
- 租户使用统计和监控
- 租户数据导出和备份