模型 Models

Strapi 模型(即内容类型、组件和动态区域)定义了数据结构的表示。

由于 Strapi 是一个无头内容管理系统 (CMS),因此为内容创建数据结构是使用该软件的最重要方面之一。模型定义了数据结构的表示。

Strapi 中有两种不同类型的模型:

  • 内容类型,可以是集合类型或单一类型,具体取决于它们管理的条目数,
  • 以及可在多种内容类型中重复使用的数据结构组件。

如果您刚刚开始使用,可以直接在管理面板中使用 内容类型构建器 生成一些模型,非常方便。用户界面接管了大量验证任务,并展示了创建内容数据结构的所有可用选项。然后可以使用此文档在代码级别查看生成的模型映射。

模型创建

内容类型和组件模型的创建和存储方式不同。

内容类型

Strapi 中的内容类型可以通过以下方式创建:

内容类型使用以下文件:

  • schema.json 用于模型的 schema 定义。(使用任一方法创建内容类型时自动生成)
  • lifecycles.js 用于 生命周期钩子。此文件必须手动创建。

这些模型文件存储在 ./src/api/[api-name]/content-types/[content-type-name]/ 中,这些文件夹中的任何 JavaScript 或 JSON 文件都将作为内容类型的模型加载(请参阅 项目结构)。

在启用 TypeScript 的项目中,可以使用 ts:generate-types 命令生成架构类型(例如,npm run strapi ts:generate-typesyarn strapi ts:generate-types)。

组件

无法使用 CLI 工具创建组件模型。使用 Content-type Builder 或手动创建它们。

组件模型存储在 ./src/components 文件夹中。每个组件都必须位于子文件夹中,并以组件所属的类别命名(请参阅项目结构)。

模型架构

模型的 schema.json 文件包含:

  • settings,例如模型所代表的内容类型或应存储数据的表名,
  • information,主要用于在管理面板中显示模型并通过 REST 和 GraphQL API 访问它,
  • attributes,描述模型的数据结构,
  • options 用于定义模型上的特定行为。

模型设置

可以使用以下参数配置模型的常规设置:

参数类型描述
collectionName字符串应存储数据的数据库表名称
kind

可选,
仅适用于内容类型
字符串定义内容类型是否为:
  • 集合类型(collectionType
  • 或单一类型(singleType
api-name
json
{
  "kind": "collectionType",
  "collectionName": "Restaurants_v1"
}

模型信息

模型架构中的 info 键描述用于在管理面板中显示模型并通过 Content API 访问它的信息。它包括以下参数:

参数类型说明
displayName字符串管理面板中使用的默认名称
singularName字符串内容类型名称的单数形式。
用于生成 API 路由和数据库/表集合。

应为短横线大小写。
pluralName字符串内容类型名称的复数形式。
用于生成 API 路由和数据库/表集合。

应为短横线大小写。
description字符串模型的说明
./src/api/[api-name
json
{
  "info": {
    "displayName": "Restaurant",
    "singularName": "restaurant",
    "pluralName": "restaurants",
    "description": ""
  }
}

模型属性

模型的数据结构由属性列表组成。每个属性都有一个 type 参数,该参数描述其性质并将属性定义为 Strapi 使用的简单数据或更复杂的结构。

有许多类型的属性可用:

  • 标量类型(例如字符串、日期、数字、布尔值等),
  • Strapi 特定类型,例如:
  • media 用于通过 媒体库 上传的文件
  • relation 用于描述内容类型之间的 关系
  • customField 用于描述 自定义字段 及其特定键
  • component 用于定义 组件(即,可用于多种内容类型的数据结构)
  • dynamiczone 用于定义 动态区域(即,基于组件列表的灵活空间)
  • 以及 localelocalizations 类型,仅由 国际化 (i18n) 使用plugin](/dev-docs/plugins/i18n)

属性的 type 参数应为以下值之一:

类型类别可用类型
字符串类型
  • string
  • text
  • richtext
  • enumeration
  • email
  • password
  • uid
日期类型
  • date
  • time
  • datetime
  • timestamp
数字类型
  • integer
  • biginteger
  • float
  • decimal
其他泛型类型
  • boolean
  • json
Strapi 独有的特殊类型
国际化 (i18n) 相关类型

仅当安装了 i18n 插件 时才可使用
  • locale
  • localizations

切勿将自定义属性命名为“locale”,因为它可能会干扰并破坏 i18n 功能。

验证

可以使用以下参数将基本验证应用于属性:

参数类型说明默认
required布尔值如果为 true,则为此属性添加必需的验证器false
max整数检查值是否大于或等于给定的最大值-
min整数检查值是否小于或等于给定的最小值-
minLength整数字段输入值的最小字符数-
maxLength整数字段输入值的最大字符数-
private布尔值如果为 true,则将从服务器响应中删除该属性。

💡 这对于隐藏敏感数据很有用。
false
configurable布尔值如果为 false,则该属性无法从 Content-type Builder 插件进行配置。true
./src/api/[api-name
json
{
  // ...
  "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
./src/api/[api-name
json
{
  // ...
  "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这些值之间的关系类型:
  • oneToOne
  • oneToMany
  • manyToOne
  • manyToMany
target接受字符串值作为目标内容类型的名称
mappedByinversedBy

可选
在双向关系中,拥有方声明 inversedBy 键,而反向方声明 mappedBy
One-to-one

当一个条目只能链接到另一个条目时,一对一关系非常有用。

它们可以是单向的,也可以是双向的。在单向关系中,只能查询其中一个模型及其链接项。

单向用例示例:

  • 博客文章属于某个类别。
  • 查询文章可以检索其类别,
  • 但查询类别不会检索所拥有的文章。
./src/api/[api-name
json
{
  // …
  "attributes": {
    "category": {
      "type": "relation",
      "relation": "oneToOne",
      "target": "category"
    }
  }
  // …
}

双向用例示例:

  • 博客文章属于某个类别。
  • 查询文章可以检索其类别,
  • 查询类别也会检索其所属的文章。
./src/api/[api-name
json
{
  // …
  "attributes": {
    "category": {
      "type": "relation",
      "relation": "oneToOne",
      "target": "category",
      "inversedBy": "article"
    }
  }
  // …
}
./src/api/[api-name
json
{
// …
  "attributes": {
    "article": {
      "type": "relation",
      "relation": "oneToOne",
      "target": "article",
      "mappedBy": "category"
    }
  }
// …
}
一对多

一对多关系在以下情况下很有用:

  • 内容类型 A 中的条目链接到另一个内容类型 B 的多个条目,
  • 而内容类型 B 中的条目仅链接到内容类型 A 的一个条目。

一对多关系始终是双向的,通常用相应的多对一关系定义:

示例:一个人可以拥有多株植物,但一株植物只能由一个人拥有。

./src/api/[api-name
json
{
  // …
  "attributes": {
    "owner": {
      "type": "relation",
      "relation": "manyToOne",
      "target": "api::person.person",
      "inversedBy": "plants"
    }
  }
  // …
}
./src/api/person/models/schema.json
json
{
  // …
  "attributes": {
    "plants": {
      "type": "relation",
      "relation": "oneToMany",
      "target": "api::plant.plant",
      "mappedBy": "owner"
    }
  }
  // …
}
多对一

多对一关系可用于将多个条目链接到一个条目。

它们可以是单向的,也可以是双向的。在单向关系中,只能使用其中一个模型的链接项进行查询。

单向用例示例:

一本书可以由多位作者撰写。

./src/api/[api-name
json
{
  // …
  "attributes": {
    "author": {
      "type": "relation",
      "relation": "manyToOne",
      "target": "author"
    }
  }
  // …
}

双向用例示例:

一篇文章只属于一个类别,但一个类别有多篇文章。

./src/api/[api-name
json
{
  // …
  "attributes": {
    "author": {
      "type": "relation",
      "relation": "manyToOne",
      "target": "category",
      "inversedBy": "article"
    }
  }
  // …
}
./src/api/[api-name
json
{
  // …
  "attributes": {
    "books": {
      "type": "relation",
      "relation": "oneToMany",
      "target": "article",
      "mappedBy": "category"
    }
  }
  // …
}
多对多

多对多关系在以下情况下很有用:

  • 内容类型 A 的条目链接到内容类型 B 的许多条目,
  • 内容类型 B 的条目也链接到内容类型 A 的许多条目。

多对多关系可以是单向的,也可以是双向的。在单向关系中,只能使用其链接项查询其中一个模型。

单向用例示例:

json
{
  // …
  "attributes": {
    "categories": {
      "type": "relation",
      "relation": "manyToMany",
      "target": "category"
    }
  }
  // …
}

双向用例示例:

一篇文章可以有多个标签,一个标签可以分配给多篇文章。

/src/api/[api-name
json
{
  // …
  "attributes": {
    "tags": {
      "type": "relation",
      "relation": "manyToMany",
      "target": "tag",
      "inversedBy": "articles"
    }
  }
  // …
}
./src/api/[api-name
json
{
  // …
  "attributes": {
    "articles": {
      "type": "relation",
      "relation": "manyToMany",
      "target": "article",
      "mappedBy": "tag"
    }
  }
  // …
}

自定义字段

自定义字段 通过向内容类型添加新类型的字段来扩展 Strapi 的功能。自定义字段在模型的 属性 中明确定义,其类型为 type: customField。 自定义字段的属性还接受:

自定义字段的属性还显示以下特性:

  • customField 属性,其值充当唯一标识符,以指示应使用哪个已注册的自定义字段。其值如下:
  • 如果插件创建了自定义字段,则为 plugin::plugin-name.field-name 格式
  • 或特定于当前 Strapi 应用程序的自定义字段的 global::field-name 格式
  • 以及取决于注册自定义字段时定义的内容的其他参数(请参阅 自定义字段文档)。
./src/api/[apiName
json
{
  // …
  "attributes": {
    "attributeName": { // attributeName would be replaced by the actual attribute name
      "type": "customField",
      "customField": "plugin::color-picker.color",
      "options": {
        "format": "hex"
      }
    }
  }
  // …
}

组件

组件字段在内容类型和组件结构之间创建关系。组件在模型的 属性 中明确定义,其类型为 type: 'component',并接受以下附加参数:

参数类型说明
repeatable布尔值可能为 truefalse,具体取决于组件是否可重复
component字符串按照以下格式定义相应的组件:
<category>.<componentName>
./src/api/[apiName
json
{
  "attributes": {
    "openinghours": {
      "type": "component",
      "repeatable": true,
      "component": "restaurant.openinghours"
    }
  }
}

动态区域

动态区域基于混合的 组件 列表创建一个灵活的空间,用于编写内容。

动态区域在模型的 属性 中明确定义,其类型为 type: 'dynamiczone'。它们还接受 components 数组,其中每个组件应按照以下格式命名:<category>.<componentName>

./src/api/[api-name
json
{
  "attributes": {
    "body": {
      "type": "dynamiczone",
      "components": ["article.slider", "article.content"]
    }
  }
}

模型选项

options 键用于定义特定行为并接受以下参数:

参数类型描述
privateAttributes字符串数组允许将一组属性视为私有属性,即使它们实际上并未在模型中定义为属性。它可用于将它们从 API 响应时间戳中删除。

模型中定义的 privateAttributes 与全局 Strapi 配置中定义的 privateAttributes 合并。
draftAndPublish布尔值启用草稿和发布功能。

默认值:true(如果内容类型是从交互式 CLI 创建的,则为 false)。
./src/api/[api-name
json
{
  "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对象接受以下参数:
  • data
  • select
  • where
  • orderBy
  • limit
  • offset
  • populate
result对象可选,仅适用于 afterXXX 事件

包含操作的结果。
stateObject查询状态,可用于在查询的 beforeXXXafterXXX 事件之间共享状态。

声明式和编程式用法

要配置内容类型生命周期钩子,请在 ./src/api/[api-name]/content-types/[content-type-name]/ 文件夹中创建一个 lifecycles.js 文件。

每个事件侦听器都按顺序调用。它们可以是同步的,也可以是异步的。

声明式用法

js
JavaScript
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,还可以注册订阅者并以编程方式监听事件:

./src/index.js
js
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') {
        // 进行处理
      }
    })
  }
}