模型
页面摘要: 模型通过内容类型和可复用组件来定义 Strapi 的内容结构。本文档逐步介绍了如何在内容类型构建器或 CLI 中创建这些模型,以及如何使用可选的生命周期钩子管理模式文件。
由于 Strapi 是一个无头内容管理系统(CMS),为内容创建内容结构是使用该软件最重要的方面之一。模型定义了内容结构的表示形式。
Strapi 中有 2 种不同类型的模型:
- 内容类型,根据它们管理的条目数量,可以是集合类型或单一类型,
- 以及可以在多个内容类型中复用的组件。
如果你刚刚开始,使用管理面板中的内容类型构建器生成一些模型会很方便。用户界面接管了许多验证任务,并展示了创建内容结构时可用的所有选项。然后可以使用本文档在代码层面查看生成的模型映射。
模型创建
内容类型和组件模型的创建和存储方式不同。
内容类型
Strapi 中的内容类型可以通过以下方式创建:
- 使用管理面板中的内容类型构建器,
- 或使用 Strapi 的交互式 CLI
strapi generate命令。
内容类型使用以下文件:
这些模型文件存储在 ./src/api/[api-name]/content-types/[content-type-name]/ 中,在这些文件夹中找到的任何 JavaScript 或 JSON 文件都会被加载为内容类型的模型(请参阅项目结构)。
内容类型的存储方式不取决于它们在管理面板中的显示方式。内容类型可以分组到文件夹中,这些文件夹在单独的内容结构文件中进行描述。
在启用了 TypeScript 的项目中,可以使用 ts:generate-types 命令生成模式类型定义。
组件 {#components-creation}
组件模型无法通过 CLI 工具创建。请使用内容类型构建器或手动创建它们。
组件模型存储在 ./src/components 文件夹中。每个组件都必须位于一个以该组件所属类别命名的子文件夹内(请参阅项目结构)。
组件也接受一个可选的预览图片,在动态区域选择器中显示 以替代其图标(请参阅组件预览图片)。
模型模式
模型的 schema.json 文件由以下部分组成:
- 设置,例如模型所表示的内容类型种类,或数据应存储的表名,
- 信息,主要用于在管理面板中显示模型并通过 REST 和 GraphQL API 访问它,
- 属性,用于描述模型的内容结构,
- 以及用于在模型上定义特定行为的选项。
模型设置
模型的通用设置可以使用以下参数进行配置:
| 参数 | 类型 | 描述 |
|---|---|---|
collectionName | String | 数据应存储的数据库表名 |
kind |
可选, 仅适用于内容类型 | String | 定义内容类型是:
- 集合类型(
collectionType) - 或单一类型(
singleType) |
// ./src/api/[api-name]/content-types/restaurant/schema.json
{
"kind": "collectionType",
"collectionName": "Restaurants_v1",
}
模型信息
模型模式中的 info 键描述了用于在管理面板中显示模型并通过内容 API 访问它的信息。它包含以下参数:
| 参数 | 类型 | 描述 |
|---|---|---|
displayName | String | 在管理面板中使用的默认名称 |
singularName | String | 内容类型名称的单数形式。 |
用于生成 API 路由以及数据库/表集合。
应为 kebab-case(短横线命名)。 |
| pluralName | String | 内容类型名称的复数形式。
用于生成 API 路由以及数据库/表集合。
应为 kebab-case。 |
| description | String | 模型的描述 |
| icon | String | 用于在管理面板中表示模型的 Strapi 图标 名称 |
| preview | String | 用于在管理面板中表示组件图片的路径或 URL。
仅适用于组件。请参阅 组件预览图片。 |
此 preview 参数与预览功能无关。
该功能用于预览前端内容,使用的是 config/admin 的 preview 对象。
"info": {
"displayName": "Restaurant",
"singularName": "restaurant",
"pluralName": "restaurants",
"description": ""
},
组件预览图片
组件在其 info 对象中接受一个可选的 preview 参数。它指向一张用于在动态区域选择器中表示组件的图片。
preview 参数接受:
- 指向放置在项目
public目录中的图片的根相对路径,例如/_component-screenshots/hero-section.png。该图片由public中间件 提供,它不会提供以/uploads/开头的路径。 - 指向外部图片主机的绝对 URL。
媒体库图片是从 /uploads/ 提供的,因此它们不能用作预览图片。请将文件提交到 public 目录中。
{
"info": {
"displayName": "Hero Section",
"icon": "layout",
"preview": "/_component-screenshots/hero-section.png"
}
}
当省略 preview,或图片加载失败时,管理面板会回退到组件的 icon。
preview 参数必须在组件的架构文件中手动设置。内容类型构建器目前还无法上传预览图片。
模型属性
模型的内容结构由一组属性组成。每个属性都有一个 type 参数,用于描述其性质,并将该属性定义为简单的数据片段或 Strapi 使用的更复杂的结构。
可以使用多种类型的属性:
- 标量类型(例如字符串、日期、数字、布尔值等),
- Strapi 特定的类型,例如:
属性的 type 参数应为以下值之一:
| 类型类别 | 可用类型 |
|---|---|
| 字符串类型 |
-
string -
text -
richtext -
enumeration -
email -
password -
uid| | 日期类型 | -
date -
time -
datetime -
timestamp| | 数字类型 | -
integer -
biginteger -
float -
decimal| | 其他通用类型 | -
boolean -
json| | Strapi 特有的特殊类型 | -
media -
dynamiczone| | 国际化(i18n)相关类型
仅当内容类型上启用了 i18n 时才能使用|
localelocalizations|
验证
可以使用以下参数对属性应用基础验证:
| 参数 | 类型 | 描述 | 默认值 |
|---|---|---|---|
required | Boolean | 如果为 true,则为此属性添加必填验证器 | false |
max | Integer | 检查值是否大于或等于给定的最大值 | - |
min | Integer | 检查值是否小于或等于给定的最小值 | - |
minLength | Integer | 字段输入值的最小字符数 | - |
maxLength | Integer | 字段输入值的最大字符数 | - |
private | Boolean | 如果为 true,该属性将从服务器响应中移除。 |
💡 这可用于隐藏敏感数据。 | false |
| configurable | Boolean | 如果为 false,则该属性无法从内容类型构建器插件进行配置。 | true |
{
// ...
"attributes": {
"title": {
"type": "string",
"minLength": 3,
"maxLength": 99,
"unique": true
},
"description": {
"default": "My description",
"type": "text",
"required": true
},
"slug": {
"type": "uid",
"targetField": "title"
}
// ...
}
}
数据库验证与设置
这些设置应保留给高级用法,因为它们可能会破坏某些功能。目前没有计划让这些设置变得稳定。
数据库验证与设置是在模式迁移期间直接传递给 tableBuilder Knex.js 函数的自定义选项。数据库验证允许在设置自定义列设置方面实现高级程度的控制。以下选项按属性设置在 column: {} 对象中:
| 参数 | 类型 | 描述 | 默认值 |
|---|---|---|---|
name | string | 更改数据库中列的名称 | - |
defaultTo | string | 设置数据库的 defaultTo,通常与 notNullable 一起使用 | - |
notNullable | boolean | 设置数据库的 notNullable,确保列不能为 null | false |
unsigned | boolean | 仅适用于数字列,取消取负值的能力,但将最大长度翻倍 | false |
unique | boolean | 对已发布的条目强制实施数据库级别的惟一性。当启用草稿与发布(Draft & Publish)时,草稿保存会跳过检查,因此重复项仅在发布时才会失败。 | false |
type | string | 更改数据库类型,如果 type 带有参数,应在 args 中传递它们 | - |
args | array | 传递给用于更改 type 等内容的 Knex.js 函数的参数 | [] |
当启用草稿与发布时,Strapi 会刻意跳过正在保存为草稿的条目的 unique 验证。因此,重复项在发布之前一直不会被检测到,此时即使 UI 此前为草稿显示过"已保存文档",数据库约束也会触发错误。
为避免意外的发布失败:
- 在必须保持全局唯一的内容类型上禁用草稿与发布,
- 或添加自定义验证(例如生命周期钩子或中间件),在保存之前检查草稿重复项,
- 或依赖自动生成的唯一标识符,例如
uid字段以及文档编辑规范。
{
// ...
"attributes": {
"title": {
"type": "string",
"minLength": 3,
"maxLength": 99,
"unique": true,
"column": {
"unique": true // enforce database unique also
}
},
"description": {
"default": "My description",
"type": "text",
"required": true,
"column": {
"defaultTo": "My description", // set database level default
"notNullable": true // enforce required at database level, even for drafts
}
},
"rating": {
"type": "decimal",
"default": 0,
"column": {
"defaultTo": 0,
"type": "decimal", // using the native decimal type but allowing for custom precision
"args": [
6,1 // using custom precision and scale
]
}
}
// ...
}
}
uid 类型
uid 类型用于根据 2 个可选参数,在管理面板中自动预填字段值为唯一标识符(UID)(例如文章的别名 slug):
targetField(字符串):如果使用了它,则用作目标的字段的值会被用来自动生成 UID。options(字符串):如果使用了它,则 UID 是基于传递给 底层uid生成器 的一组选项生成的。生成的uid必须匹配以下正则表达式模式:/^[A-Za-z0-9-_.~]*$。
关联关系
关联关系将内容类型链接在一起。Strapi 同时支持单条目关联(单向和一对一)以及多处关联(其中至少一侧可以指向多个条目,包括一对多、多对一、多对多和多向)。多处关联在数据库层以数组形式持久化,并在内容 API 响应中作为数组返回。
关联通过在模型的属性中显式定义 type: 'relation' 来声明,并接受以下附加参数:
| 参数 | 描述 |
|---|---|
relation | 以下值之一的关联类型: |
oneToOneoneToManymanyToOnemanyToMany| |target| 接受字符串值作为目标内容类型的名称 | |mappedBy和inversedBy
可选 | 在双向关联中,拥有方声明 inversedBy 键,而被反向方声明 mappedBy 键 |
一对一
一对一关系适用于一个条目只能链接到另一个条目的情况。
它们可以是单向的或双向的。在单向关系中,只有其中一个模型可以与其关联项一起被查询。
单向用例示例:
- 一篇博客文章属于一个分类。
- 查询文章可以获取其分类,
- 但查询分类不会获取其所属的文章。
// …
attributes: {
category: {
type: 'relation',
relation: 'oneToOne',
target: 'category',
},
},
// …
双向用例示例:
- 一篇博客文章属于一个分类。
- 查询文章可以获取其分类,
- 并且查询分类也会获取其所属的文章。
// …
attributes: {
category: {
type: 'relation',
relation: 'oneToOne',
target: 'category',
inversedBy: 'article',
},
},
// …
// …
attributes: {
article: {
type: 'relation',
relation: 'oneToOne',
target: 'article',
mappedBy: 'category',
},
},
// …
一对多
一对多关系适用于以下情况:
- 内容类型 A 的一个条目链接到另一个内容类型 B 的多个条目,
- 而内容类型 B 的一个条目只链接到内容类型 A 的一个条目。
一对多关系始终是双向的,并且通常与相应的一对多(应为多对一)关系一起定义:
示例: 一个人可以拥有很多植物,但一株植物只被一个人拥有。
// …
attributes: {
owner: {
type: 'relation',
relation: 'manyToOne',
target: 'api::person.person',
inversedBy: 'plants',
},
},
// …
// …
attributes: {
plants: {
type: 'relation',
relation: 'oneToMany',
target: 'api::plant.plant',
mappedBy: 'owner',
},
},
// …
多对一
多对一关系适用于将多个条目链接到一个条目的情况。
它们可以是单向的或双向的。在单向关系中,只有其中一个模型可以与其关联项一起被查询。
单向用例示例:
一本书可以由多位作者合著。
// …
attributes: {
author: {
type: 'relation',
relation: 'manyToOne',
target: 'author',
},
},
// …
双向用例示例:
一篇文章只属于一个分类,但一个分类有多篇文章。
// …
attributes: {
author: {
type: 'relation',
relation: 'manyToOne',
target: 'category',
inversedBy: 'article',
},
},
// …
// …
attributes: {
books: {
type: 'relation',
relation: 'oneToMany',
target: 'article',
mappedBy: 'category',
},
},
// …
多对多
多对多关系适用于以下情况:
- 内容类型 A 的一个条目链接到内容类型 B 的多个条目,
- 并且内容类型 B 的一个条目也链接到内容类型 A 的多个条目。
多对多关系可以是单向的或双向的。在单向关系中,只有其中一个模型可以与其关联项一起被查询。
单向用例示例:
// …
attributes: {
categories: {
type: 'relation',
relation: 'manyToMany',
target: 'category',
},
},
// …
双向用例示例:
一篇文章可以有多个标签,而一个标签可以被分配给多篇文章。
// …
attributes: {
tags: {
type: 'relation',
relation: 'manyToMany',
target: 'tag',
inversedBy: 'articles',
},
},
// …
// …
attributes: {
articles: {
type: 'relation',
relation: 'manyToMany',
target: 'article',
mappedBy: 'tag',
},
},
// …
自定义字段
自定义字段通过向内容类型添加新类型的字段来扩展 Strapi 的能力。自定义字段通过在模型的属性中显式定义 type: customField 来声明。
自定义字段属性还表现出以下特性:
- 一个
customField属性,其值作为唯一标识符,用于指示应使用哪个已注册的自定义字段。其值遵循以下格式:- 如果自定义字段由插件创建,则为
plugin::plugin-name.field-name格式 - 或用于当前 Strapi 应用特定的自定义字段的
global::field-name格式
- 如果自定义字段由插件创建,则为
- 以及取决于注册自定义字段时定义的附加参数(请参阅自定义字段文档)。
{
// …
"attributes": {
"attributeName": { // attributeName 将替换为实际的属性名称
"type": "customField",
"customField": "plugin::color-picker.color",
"options": {
"format": "hex"
}
}
}
// …
}
组件 {#components-json}
组件字段在内容类型和组件结构之间创建关联。组件通过在模型的属性中显式定义 type: 'component' 来声明,并接受以下附加参数:
| 参数 | 类型 | 描述 |
|---|---|---|
repeatable | Boolean | 根据组件是否可重复,值可以是 true 或 false |
component | String | 定义对应的组件,遵循以下格式: |
<category>.<componentName> |
{
"attributes": {
"openinghours": {
"type": "component",
"repeatable": true,
"component": "restaurant.openinghours"
}
}
}
这些参数设置在使用该组件的内容类型的属性上。
设置在组件本身的参数(例如其预览图片)属于它自己的
info 对象(请参阅组件预览图片)。
动态区域
动态区域创建了一个灵活的空间,用于基于混合的组件列表来组合内容。
动态区域通过在模型的属性中显式定义 type: 'dynamiczone' 来声明。它们还接受一个 components 数组,其中每个组件都应遵循以下格式命名:<category>.<componentName>。
{
"attributes": {
"body": {
"type": "dynamiczone",
"components": ["article.slider", "article.content"]
}
}
}
模型选项
options 键用于定义特定行为,并接受以下参数:
| 参数 | 类型 | 描述 |
|---|---|---|
privateAttributes | Array of strings | 允许将一组属性视为私有,即使它们实际上并未在模型中定义为属性。它可以用于从 API 响应中移除它们(例如时间戳)。 |
模型中定义的 privateAttributes 会与全局 Strapi 配置中定义的 privateAttributes 合并。 |
| draftAndPublish | Boolean | 启用草稿与发布功能。
默认值:true(如果使用交互式 CLI 创建内容类型则为 false)。 |
| populateCreatorFields | Boolean | 在 REST API 返回的响应中填充 createdBy 和 updatedBy 字段(更多详情请参阅指南)。
默认值:false。 |
{
"options": {
"privateAttributes": ["id", "createdAt"],
"draftAndPublish": true
}
}
插件选项
pluginOptions 是一个可选对象,允许插件为模型或特定属性存储配置。
| 键 | 值 | 描述 |
|---|---|---|
i18n | localized: true | 启用本地化。 |
content-manager | visible: false | 在管理面板中从内容管理器隐藏。 |
content-type-builder | visible: false | 在管理面板中从内容类型构建器隐藏。 |
{
"attributes": {
"name": {
"pluginOptions": {
"i18n": {
"localized": true
}
},
"type": "string",
"required": true
},
"slug": {
"pluginOptions": {
"i18n": {
"localized": true
}
},
"type": "uid",
"targetField": "name",
"required": true
}
// …additional attributes
}
}
生命周期钩子
生命周期钩子是在调用 Strapi 查询时触发的函数。当通过管理面板管理内容或使用 queries 开发自定义代码时,它们会自动触发。
生命周期钩子可以通过声明式或编程式进行自定义。
当直接使用 knex 库而不是 Strapi 函数时,不会触发生命周期钩子。
文档服务 API 会根据调用的方法触发各种数据库生命周期钩子。有关完整参考,请参阅文档服务 API:生命周期钩子。批量操作的 liftcycle 钩子(createMany、updateMany、deleteMany)永远不会被文档服务 API 方法触发。文档服务中间件 也可以被实现。
可用的生命周期事件
提供以下生命周期事件:
beforeCreatebeforeCreateManyafterCreateafterCreateManybeforeUpdatebeforeUpdateManyafterUpdateafterUpdateManybeforeDeletebeforeDeleteManyafterDeleteafterDeleteManybeforeCountafterCountbeforeFindOneafterFindOnebeforeFindManyafterFindMany
钩子的 event 对象
生命周期钩子是接收 event 参数的函数,该对象包含以下键:
| 键 | 类型 | 描述 |
|---|---|---|
action | String | 已触发的生命周期事件(请参阅列表) |
model | Array of strings (uid) | 要监听其事件的 content-types 的 uid 数组。 |
| 如果未提供此参数,则监听所有内容类型的事件。 | ||
params | Object | 接受以下参数: |
dataselectwhereorderBylimitoffsetpopulate| |result| Object | 可选,仅适用于afterXXX事件
包含操作的结果。 |
| state | Object | 查询状态,可用于在查询的 beforeXXX 和 afterXXX 事件之间共享状态。 |
声明式与编程式用法
要配置内容类型的生命周期钩子,请在 ./src/api/[api-name]/content-types/[content-type-name]/ 文件夹中创建一个 lifecycles.js 文件。
每个事件监听器按顺序调用。它们可以是同步的或异步的。
JavaScript
module.exports = {
beforeCreate(event) {
const { data, where, select, populate } = event.params;
// let's do a 20% discount everytime
event.params.data.price = event.params.data.price * 0.8;
},
afterCreate(event) {
const { result, params } = event;
// do something to the result;
},
};
TypeScript
export default {
beforeCreate(event) {
const { data, where, select, populate } = event.params;
// let's do a 20% discount everytime
event.params.data.price = event.params.data.price * 0.8;
},
afterCreate(event) {
const { result, params } = event;
// do something to the result;
},
};
使用数据库层 API,也可以注册一个订阅者并以编程方式监听事件:
module.exports = {
async bootstrap({ strapi }) {
// registering a subscriber
strapi.db.lifecycles.subscribe({
models: [], // optional;
beforeCreate(event) {
const { data, where, select, populate } = event.params;
event.state = 'doStuffAfterWards';
},
afterCreate(event) {
if (event.state === 'doStuffAfterWards') {
}
const { result, params } = event;
// do something to the result
},
});
// generic subscribe for generic handling
strapi.db.lifecycles.subscribe((event) => {
if (event.action === 'beforeCreate') {
// do something
}
});
}
}