由于 Strapi 是一个无头内容管理系统 (CMS),因此为内容创建数据结构是使用该软件的最重要方面之一。模型定义了数据结构的表示。
Strapi 中有两种不同类型的模型:
- 内容类型,可以是集合类型或单一类型,具体取决于它们管理的条目数,
- 以及可在多种内容类型中重复使用的数据结构组件。
如果您刚刚开始使用,可以直接在管理面板中使用 内容类型构建器 生成一些模型,非常方便。用户界面接管了大量验证任务,并展示了创建内容数据结构的所有可用选项。然后可以使用此文档在代码级别查看生成的模型映射。
模型创建
内容类型和组件模型的创建和存储方式不同。
内容类型
Strapi 中的内容类型可以通过以下方式创建:
- 使用 管理面板中的内容类型构建器,
- 或使用 Strapi 的交互式 CLI
strapi generate
命令。
内容类型使用以下文件:
这些模型文件存储在 ./src/api/[api-name]/content-types/[content-type-name]/
中,这些文件夹中的任何 JavaScript 或 JSON 文件都将作为内容类型的模型加载(请参阅 项目结构)。
在启用 TypeScript 的项目中,可以使用 ts:generate-types
命令生成架构类型(例如,npm run strapi ts:generate-types
或 yarn strapi ts:generate-types
)。
组件
无法使用 CLI 工具创建组件模型。使用 Content-type Builder 或手动创建它们。
组件模型存储在 ./src/components
文件夹中。每个组件都必须位于子文件夹中,并以组件所属的类别命名(请参阅项目结构)。
模型架构
模型的 schema.json
文件包含:
- settings,例如模型所代表的内容类型或应存储数据的表名,
- information,主要用于在管理面板中显示模型并通过 REST 和 GraphQL API 访问它,
- attributes,描述模型的数据结构,
- 和 options 用于定义模型上的特定行为。
模型设置
可以使用以下参数配置模型的常规设置:
参数 | 类型 | 描述 |
---|---|---|
collectionName | 字符串 | 应存储数据的数据库表名称 |
kind 可选, 仅适用于内容类型 | 字符串 | 定义内容类型是否为:
|
{
"kind": "collectionType",
"collectionName": "Restaurants_v1"
}
模型信息
模型架构中的 info
键描述用于在管理面板中显示模型并通过 Content API 访问它的信息。它包括以下参数:
参数 | 类型 | 说明 |
---|---|---|
displayName | 字符串 | 管理面板中使用的默认名称 |
singularName | 字符串 | 内容类型名称的单数形式。 用于生成 API 路由和数据库/表集合。 应为短横线大小写。 |
pluralName | 字符串 | 内容类型名称的复数形式。 用于生成 API 路由和数据库/表集合。 应为短横线大小写。 |
description | 字符串 | 模型的说明 |
{
"info": {
"displayName": "Restaurant",
"singularName": "restaurant",
"pluralName": "restaurants",
"description": ""
}
}
模型属性
模型的数据结构由属性列表组成。每个属性都有一个 type
参数,该参数描述其性质并将属性定义为 Strapi 使用的简单数据或更复杂的结构。
有许多类型的属性可用:
- 标量类型(例如字符串、日期、数字、布尔值等),
- Strapi 特定类型,例如:
media
用于通过 媒体库 上传的文件relation
用于描述内容类型之间的 关系customField
用于描述 自定义字段 及其特定键component
用于定义 组件(即,可用于多种内容类型的数据结构)dynamiczone
用于定义 动态区域(即,基于组件列表的灵活空间)- 以及
locale
和localizations
类型,仅由 国际化 (i18n) 使用plugin](/dev-docs/plugins/i18n)
属性的 type
参数应为以下值之一:
类型类别 | 可用类型 |
---|---|
字符串类型 |
|
日期类型 |
|
数字类型 |
|
其他泛型类型 |
|
Strapi 独有的特殊类型 | |
国际化 (i18n) 相关类型 仅当安装了 i18n 插件 时才可使用 |
|
切勿将自定义属性命名为“locale”,因为它可能会干扰并破坏 i18n 功能。
验证
可以使用以下参数将基本验证应用于属性:
参数 | 类型 | 说明 | 默认 |
---|---|---|---|
required | 布尔值 | 如果为 true ,则为此属性添加必需的验证器 | false |
max | 整数 | 检查值是否大于或等于给定的最大值 | - |
min | 整数 | 检查值是否小于或等于给定的最小值 | - |
minLength | 整数 | 字段输入值的最小字符数 | - |
maxLength | 整数 | 字段输入值的最大字符数 | - |
private | 布尔值 | 如果为 true ,则将从服务器响应中删除该属性。💡 这对于隐藏敏感数据很有用。 | false |
configurable | 布尔值 | 如果为 false ,则该属性无法从 Content-type Builder 插件进行配置。 | true |
{
// ...
"attributes": {
"title": {
"type": "string",
"minLength": 3,
"maxLength": 99,
"unique": true
},
"description": {
"default": "My description",
"type": "text",
"required": true
},
"slug": {
"type": "uid",
"targetField": "title"
}
// ...
}
}
验证
可以使用以下参数将基本验证应用于属性:
参数 | 类型 | 说明 | 默认 |
---|---|---|---|
required | 布尔值 | 如果为 true ,则为此属性添加必需的验证器 | false |
max | 整数 | 检查值是否大于或等于给定的最大值 | - |
min | 整数 | 检查值是否小于或等于给定的最小值 | - |
minLength | 整数 | 字段输入值的最小字符数 | - |
maxLength | 整数 | 字段输入值的最大字符数 | - |
private | 布尔值 | 如果为 true ,则将从服务器响应中删除该属性。💡 这对于隐藏敏感数据很有用。 | false |
configurable | 布尔值 | 如果为 false ,则该属性无法从 Content-type Builder 插件进行配置。 | true |
{
// ...
"attributes": {
"title": {
"type": "string",
"minLength": 3,
"maxLength": 99,
"unique": true,
"column": {
"unique": true // 强制执行数据库唯一性
}
},
"description": {
"default": "My description",
"type": "text",
"required": true,
"column": {
"defaultTo": "My description", // 设置数据库级别默认值
"notNullable": true // 在数据库级别强制执行,即使对于草稿也是如此
}
},
"rating": {
"type": "decimal",
"default": 0,
"column": {
"defaultTo": 0,
"type": "decimal", // 使用本机十进制类型,但允许自定义精度
"args": [
6, 1 // 使用自定义精度和比例
]
}
}
// ...
}
}
uid
type
uid
类型用于根据 2 个可选参数自动预填充管理面板中字段值的唯一标识符 (UID)(例如文章的 slug):
targetField
(字符串):如果使用,则使用定义为目标的字段值自动生成 UID。options
(字符串):如果使用,则根据传递给 底层uid
生成器 的一组选项生成 UID。生成的uid
必须与以下正则表达式模式匹配:/^[A-Za-z0-9-_.~]*$
。
关系
关系将内容类型链接在一起。关系在模型的 属性 中明确定义,其类型为 relation'
,并接受以下附加参数:
参数 | 描述 |
---|---|
relation | 这些值之间的关系类型:
|
target | 接受字符串值作为目标内容类型的名称 |
mappedBy 和 inversedBy 可选 | 在双向关系中,拥有方声明 inversedBy 键,而反向方声明 mappedBy 键 |
One-to-one
当一个条目只能链接到另一个条目时,一对一关系非常有用。
它们可以是单向的,也可以是双向的。在单向关系中,只能查询其中一个模型及其链接项。
单向用例示例:
- 博客文章属于某个类别。
- 查询文章可以检索其类别,
- 但查询类别不会检索所拥有的文章。
{
// …
"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 would be replaced by the actual attribute name
"type": "customField",
"customField": "plugin::color-picker.color",
"options": {
"format": "hex"
}
}
}
// …
}
组件
组件字段在内容类型和组件结构之间创建关系。组件在模型的 属性 中明确定义,其类型为 type: 'component'
,并接受以下附加参数:
参数 | 类型 | 说明 |
---|---|---|
repeatable | 布尔值 | 可能为 true 或 false ,具体取决于组件是否可重复 |
component | 字符串 | 按照以下格式定义相应的组件:<category>.<componentName> |
{
"attributes": {
"openinghours": {
"type": "component",
"repeatable": true,
"component": "restaurant.openinghours"
}
}
}
动态区域
动态区域基于混合的 组件 列表创建一个灵活的空间,用于编写内容。
动态区域在模型的 属性 中明确定义,其类型为 type: 'dynamiczone'
。它们还接受 components
数组,其中每个组件应按照以下格式命名:<category>.<componentName>
。
{
"attributes": {
"body": {
"type": "dynamiczone",
"components": ["article.slider", "article.content"]
}
}
}
模型选项
options
键用于定义特定行为并接受以下参数:
参数 | 类型 | 描述 |
---|---|---|
privateAttributes | 字符串数组 | 允许将一组属性视为私有属性,即使它们实际上并未在模型中定义为属性。它可用于将它们从 API 响应时间戳中删除。 模型中定义的 privateAttributes 与全局 Strapi 配置中定义的 privateAttributes 合并。 |
draftAndPublish | 布尔值 | 启用草稿和发布功能。 默认值: true (如果内容类型是从交互式 CLI 创建的,则为 false )。 |
{
"options": {
"privateAttributes": ["id", "createdAt"],
"draftAndPublish": true
}
}
生命周期钩子
生命周期钩子是调用 Strapi 查询时触发的函数。通过管理面板管理内容或使用“查询”开发自定义代码时,它们会自动触发。
生命周期钩子可以通过声明或编程方式进行自定义。
直接使用 knex 库而不是 Strapi 函数时不会触发生命周期钩子。
请参阅 错误处理 文档,了解如何从生命周期钩子中抛出错误。
可用的生命周期事件
以下生命周期事件可用:
beforeCreate
beforeCreateMany
afterCreate
afterCreateMany
beforeUpdate
beforeUpdateMany
afterUpdate
afterUpdateMany
beforeDelete
beforeDeleteMany
afterDelete
afterDeleteMany
beforeCount
afterCount
beforeFindOne
afterFindOne
beforeFindMany
afterFindMany
Hook event
object
生命周期钩子是采用 event
参数的函数,该参数是一个具有以下键的对象:
键 | 类型 | 说明 |
---|---|---|
action | 字符串 | 已触发的生命周期事件(参见 list) |
model | 字符串数组 (uid) | 将监听其事件的内容类型的 uid 数组。 如果未提供此参数,则在所有内容类型上监听事件。 |
params | 对象 | 接受以下参数:
|
result | 对象 | 可选,仅适用于 afterXXX 事件包含操作的结果。 |
state | Object | 查询状态,可用于在查询的 beforeXXX 和 afterXXX 事件之间共享状态。 |
声明式和编程式用法
要配置内容类型生命周期钩子,请在 ./src/api/[api-name]/content-types/[content-type-name]/
文件夹中创建一个 lifecycles.js
文件。
每个事件侦听器都按顺序调用。它们可以是同步的,也可以是异步的。
声明式用法
// ./src/api/[api-name]/content-types/[content-type-name]/lifecycles.js
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;
},
}
编程使用
使用数据库层 API,还可以注册订阅者并以编程方式监听事件:
module.exports = {
async bootstrap({ strapi }) {
// 注册订阅者
strapi.db.lifecycles.subscribe({
models: [], // 可选;
beforeCreate(event) {
const { data, where, select, populate } = event.params
event.state = 'doStuffAfterWards'
},
afterCreate(event) {
if (event.state === 'doStuffAfterWards') {
// ...
}
const { result, params } = event
// 对结果进行处理
},
})
// 通用订阅用于通用处理
strapi.db.lifecycles.subscribe((event) => {
if (event.action === 'beforeCreate') {
// 进行处理
}
})
}
}