多租户插件
该插件通过在你的 Admin Panel 中设置,为应用实现多租户功能。具体做法是为所有指定集合添加 tenant
字段。你的前端应用随后可以按租户查询数据。你必须添加 Tenants 集合,以便控制每个租户可用的字段。
核心功能
- 为每个指定集合添加
tenant
字段 - 在管理面板添加租户选择器,允许你在租户间切换
- 按所选租户筛选列表视图结果
- 按所选租户筛选关联字段
- 支持创建类似"全局"的集合,每个租户一个文档
- 自动为新文档分配租户
警告
默认情况下,该插件会在租户删除时清理相关文档。你应确保在租户集合上设置严格的访问控制,防止未授权用户删除租户。
你可以通过在插件选项中设置 cleanupAfterTenantDelete
为 false
来禁用此行为。
安装
使用 pnpm、npm 或 Yarn 等 JavaScript 包管理器安装插件:
pnpm add @payloadcms/plugin-multi-tenant
选项
该插件接受一个包含以下属性的对象:
type MultiTenantPluginConfig<ConfigTypes = unknown> = {
/**
* 当租户被删除后,插件会尝试清理相关文档
* - 移除包含该租户ID的文档
* - 从用户中移除该租户
*
* @default true
*/
cleanupAfterTenantDelete?: boolean
/**
* 自动配置
*/
collections: {
[key in CollectionSlug]?: {
/**
* 设置为 `true` 如果你希望该集合表现为全局集合
*
* @default false
*/
isGlobal?: boolean
/**
* 设置为 `false` 如果你想手动应用 baseListFilter
*
* @default true
*/
useBaseListFilter?: boolean
/**
* 设置为 `false` 如果你想手动处理集合访问而不应用多租户约束
*
* @default true
*/
useTenantAccess?: boolean
}
}
/**
* 启用调试模式
* - 在管理界面中使租户字段在适用集合中可见
*
* @default false
*/
debug?: boolean
/**
* 启用多租户插件
*
* @default true
*/
enabled?: boolean
/**
* 添加到所有启用租户的集合中的字段配置
*/
tenantField?: {
access?: RelationshipField['access']
/**
* 添加到所有启用租户的集合中的字段名称
*
* @default 'tenant'
*/
name?: string
}
/**
* 添加到用户集合中的字段配置
*
* 如果 `includeDefaultField` 为 `false`,你必须手动在用户集合中包含该字段
* 这在你想自定义字段或将字段放置在特定位置时很有用
*/
tenantsArrayField?:
| {
/**
* 数组字段的访问配置
*/
arrayFieldAccess?: ArrayField['access']
/**
* 数组字段的名称
*
* @default 'tenants'
*/
arrayFieldName?: string
/**
* 租户字段的名称
*
* @default 'tenant'
*/
arrayTenantFieldName?: string
/**
* 当 `includeDefaultField` 为 `true` 时,字段会自动添加到用户集合中
*/
includeDefaultField?: true
/**
* 包含在租户数组字段上的额外字段
*/
rowFields?: Field[]
/**
* 租户字段的访问配置
*/
tenantFieldAccess?: RelationshipField['access']
}
| {
arrayFieldAccess?: never
arrayFieldName?: string
arrayTenantFieldName?: string
/**
* 当 `includeDefaultField` 为 `false` 时,你必须手动在用户集合中包含该字段
*/
includeDefaultField?: false
rowFields?: never
tenantFieldAccess?: never
}
/**
* 自定义租户选择器标签
*
* 可以是字符串,也可以是键为i18n代码、值为字符串标签的对象
*/
tenantSelectorLabel?:
| Partial<{
[key in AcceptedLanguages]?: string
}>
| string
/**
* 租户集合的slug
*
* @default 'tenants'
*/
tenantsSlug?: string
/**
* 判断用户是否有权访问_所有_租户的函数
*
* 适用于超级管理员类型的用户
*/
userHasAccessToAllTenants?: (
user: ConfigTypes extends { user: unknown } ? ConfigTypes['user'] : User,
) => boolean
/**
* 选择不向租户集合添加访问约束
*/
useTenantsCollectionAccess?: boolean
/**
* 选择不包含 baseListFilter 来按所选租户过滤租户
*/
useTenantsListFilter?: boolean
/**
* 选择不包含 baseListFilter 来按所选租户过滤用户
*/
useUsersTenantFilter?: boolean
}
基本用法
在你的 Payload Config 的 plugins
数组中,使用 options 调用插件:
import { buildConfig } from 'payload'
import { multiTenantPlugin } from '@payloadcms/plugin-multi-tenant'
import type { Config } from './payload-types'
const config = buildConfig({
collections: [
{
slug: 'tenants',
admin: {
useAsTitle: 'name'
}
fields: [
// 记住,这些字段由你定义
// 以下仅为建议/示例
{
name: 'name',
type: 'text',
required: true,
},
{
name: 'slug',
type: 'text',
required: true,
},
{
name: 'domain',
type: 'text',
required: true,
}
],
},
],
plugins: [
multiTenantPlugin<Config>({
collections: {
pages: {},
navigation: {
isGlobal: true,
}
},
}),
],
})
export default config
前端使用
该插件为你搭建了按租户分离数据所需的一切。你可以在前端应用中使用 tenant
字段来过滤启用集合中的数据。
在前端,你可以通过以下方式按租户查询和约束数据:
const pagesBySlug = await payload.find({
collection: 'pages',
depth: 1,
draft: false,
limit: 1000,
overrideAccess: false,
where: {
// 你的约束条件取决于
// 你在租户集合中添加的字段
// 这里我们假设租户集合中存在一个 slug 字段
// 如上例所示
'tenant.slug': {
equals: 'gold',
},
},
})
NextJS 重写规则
使用 NextJS 的重写功能和 /[tenantDomain]/[slug]
这样的路由结构,我们可以针对请求的域名进行特定的路由重写:
async rewrites() {
return [
{
source: '/((?!admin|api)):path*',
destination: '/:tenantDomain/:path*',
has: [
{
type: 'host',
value: '(?<tenantDomain>.*)',
},
],
},
];
}
React Hooks
以下是该插件导出的 hooks,你可以将它们导入到自定义组件中使用。
useTenantSelection
你可以这样导入该 hook:
import { useTenantSelection } from '@payloadcms/plugin-multi-tenant/client'
...
const tenantContext = useTenantSelection()
该 hook 返回以下上下文:
type ContextType = {
/**
* 可供选择的选项数组
*/
options: OptionObject[]
/**
* 当前选中的租户 ID
*/
selectedTenantID: number | string | undefined
/**
* 在切换租户时阻止页面刷新
*
* 如果在查看"全局"内容时不希望切换租户时刷新页面,请设置为 true
*/
setPreventRefreshOnChange: React.Dispatch<React.SetStateAction<boolean>>
/**
* 设置选中的租户 ID
*
* @param args.id - 要选择的租户 ID
* @param args.refresh - 是否在切换租户后刷新页面
*/
setTenant: (args: {
id: number | string | undefined
refresh?: boolean
}) => void
}