mcpField
A
api-cms-strapi

api-cms-strapi

github.com/agents-inc开发2643 次安装2 天前更新
代码仓库

关于 api-cms-strapi

api-cms-strapi 是用于使用 Strapi 开发应用的技能;Strapi 是开源无头 CMS。内容涵盖模式驱动的内容、自动生成的 API、服务端 Document Service 用法及后端定制。

主要功能

  1. 01内容类型模式设计
  2. 02使用 qs 构建 REST 查询
  3. 03v5 Document Service API
  4. 04自定义控制器与路由
  5. 05身份验证与权限
  6. 06生命周期钩子与服务

适用场景

  1. 01定义内容类型与关系
  2. 02按筛选和填充参数查询 REST API
  3. 03实现自定义后端逻辑
  4. 04配置 JWT 访问与权限

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/agents-inc/skills --skill api-cms-strapi

安装后在 Claude Code 中输入/api-cms-strapi确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

metadata.yaml
reference.md
examples/
auth.md
backend.md
core.md

详细介绍

Strapi 模式

快速指南: 将 Strapi 用作开源无头 CMS,根据内容类型架构自动生成 REST/GraphQL API。在 v5 中,使用 Document Service API(strapi.documents())访问后端数据,而不要使用已弃用的 Entity Service。REST API 响应采用扁平格式(data.fieldName,而不是 data.attributes.fieldName)。默认不会填充关联和媒体——始终传入 populate。使用 qs 构建复杂的查询字符串。内容类型默认是私有的;通过 Users & Permissions 插件或 API 令牌配置权限。


<critical_requirements>

重要:使用此技能前

所有代码都必须遵循项目在 CLAUDE.md 中的约定(kebab-case、命名导出、导入顺序、import type、命名常量)

(在 Strapi v5 中,所有后端数据访问都必须使用 strapi.documents('api::content-type.content-type')(Document Service API)——Entity Service API 已被移除)

(需要关联、媒体、组件或动态区域时,必须始终传入 populate——Strapi 默认不返回任何关联)

(必须使用 qs 库构建包含筛选、填充和排序的复杂 REST API 查询字符串——手动拼接字符串会导致嵌套参数出错)

(在自定义控制器中,必须使用 this.sanitizeQuery(ctx)、this.sanitizeOutput() 和 this.validateQuery(ctx) 清理并验证输入和输出)

(必须通过管理面板或配置为每个内容类型端点设置权限——所有路由默认都是私有的)

</critical_requirements>


自动检测: Strapi、strapi、@strapi/strapi、createCoreController、createCoreService、createCoreRouter、Document Service、strapi.documents、content-type、schema.json、api::、plugin::、生命周期钩子、Users & Permissions、/api/auth/local、populate、qs.stringify

适用场景:

  • 使用 Strapi 作为无头 CMS 构建内容管理应用
  • 定义包含字段、关联、组件和动态区域的内容类型架构(schema.json)
  • 使用筛选、填充、排序和分页查询 REST API
  • 创建自定义控制器、服务、路由、策略或中间件
  • 使用 Document Service API,通过草稿/发布工作流执行后端 CRUD 操作
  • 使用 Users & Permissions 插件实现 JWT 身份验证
  • 为内容类型添加生命周期钩子以执行副作用
  • 为内容架构生成 TypeScript 类型

涵盖的关键模式:

  • 内容类型架构定义(schema.json)
  • 使用 qs 查询 REST API(筛选、填充、排序、分页)
  • Document Service API(findMany、findOne、create、update、delete、publish、unpublish)
  • 自定义控制器、服务、路由、策略和中间件
  • 生命周期钩子(beforeCreate、afterUpdate 等)
  • JWT 身份验证(注册、登录、已验证请求)
  • TypeScript 类型生成

不适用场景:

  • 非 Strapi 的 CMS 平台(使用对应 CMS 的专用技能)
  • 绕过 Strapi API 层直接查询数据库(使用 Strapi 的 Document Service)
  • 需要原始 SQL 的复杂事务逻辑(Strapi 对数据库进行了抽象)

详细资源:

核心与 REST API:

  • examples/core.md -- 内容类型架构、REST API 查询、Document Service API、错误处理

后端自定义:

  • examples/backend.md -- 自定义控制器、服务、路由、策略、中间件、生命周期钩子、Document Service 中间件

身份验证:


## 理念

Strapi 是一个基于 Node.js(Koa)构建的开源无头 CMS,可根据内容类型架构自动生成 RESTful 和 GraphQL API。内容通过 JSON 架构定义,经由管理面板管理,并通过生成的 API 端点访问。

核心原则:

  1. 架构驱动的内容 —— 内容类型定义在 schema.json 文件中,用于描述字段、关系、组件和动态区域。管理面板中的 Content-Type Builder 提供可视化编辑器,但架构本质上是存储在代码仓库中的代码。
  2. 自动生成 API —— 每种内容类型都会自动获得 CRUD REST 端点(/api/:pluralApiId)和可选的 GraphQL 支持。标准操作无需手动创建路由或控制器。
  3. Document Service API(v5) —— 用于在自定义代码、插件和生命周期钩子中访问内容的后端 API。它取代了 v4 的 Entity Service。它使用 documentId(而非数据库 id)作为主要标识符。
  4. 草稿与发布 —— 内容类型可采用草稿/发布工作流。Document Service 默认使用 status: 'draft';已发布内容需要设置 status: 'published' 或显式调用 publish()。
  5. 权限优先 —— 所有内容类型端点默认均为私有。必须通过管理面板(Users & Permissions 插件)或 API 令牌明确授予访问权限。
  6. 后端自定义 —— 控制器、服务、路由、策略和中间件都可自定义。Strapi 遵循基于 Koa 构建的 MVC 类模式。

核心模式

模式 1:内容类型架构

内容类型定义在 ./src/api/[api-name]/content-types/[content-type-name]/schema.json 文件中。多文档内容(文章、产品)使用 collectionType,单文档内容(站点设置、首页)使用 singleType。

{
  "kind": "collectionType",
  "info": {
    "singularName": "article",
    "pluralName": "articles",
    "displayName": "Article"
  },
  "options": { "draftAndPublish": true },
  "attributes": {
    "title": { "type": "string", "required": true },
    "slug": { "type": "uid", "targetField": "title" },
    "author": {
      "type": "relation",
      "relation": "manyToOne",
      "target": "api::author.author",
      "inversedBy": "articles"
    },
    "blocks": {
      "type": "dynamiczone",
      "components": ["blocks.hero", "blocks.rich-text"]
    }
  }
}

关键字段: uid 根据 targetField 自动生成 slug,relation 类型通过 inversedBy/mappedBy 定义基数,component 嵌入可复用区块,dynamiczone 支持混合组件类型。完整的集合类型和单一类型示例请参阅 examples/core.md。


模式 2:使用 qs 查询 REST API

REST API 支持复杂的查询参数,可用于筛选、填充、排序和分页。始终使用 qs 库构建查询字符串。

import qs from "qs";

const query = qs.stringify(
  {
    filters: { publishedAt: { $notNull: true } },
    populate: {
      author: { fields: ["name"] },
      categories: { fields: ["name", "slug"] },
    },
    sort: ["publishedAt:desc"],
    pagination: { page: 1, pageSize: 25 },
  },
  { encodeValuesOnly: true },
);
const response = await fetch(`${STRAPI_URL}/api/articles?${query}`);
const { data, meta } = await response.json();

要点: encodeValuesOnly 可避免括号编码问题;筛选器使用运算符($eq、$notNull、$containsi、$or、$and);使用带 fields 的定向 populate 可避免过度获取数据(生产环境中切勿使用 populate=*);动态区域组件使用 on 语法。完整的筛选、填充和分页示例请参阅 examples/core.md。


模式 3:Document Service API(后端)

Document Service API 用于在自定义控制器、服务、生命周期钩子和插件中从服务器端访问内容。它取代了 v4 的 Entity Service(已在 v5 中移除)。

const CONTENT_TYPE_UID = "api::article.article";

// Find published content (default is 'draft')
const articles = await strapi.documents(CONTENT_TYPE_UID).findMany({
  status: "published",
  filters: { categories: { slug: { $eq: "news" } } },
  populate: { author: true },
});

// CRUD uses documentId (not database id)
const article = await strapi
  .documents(CONTENT_TYPE_UID)
  .findOne({ documentId });
const created = await strapi
  .documents(CONTENT_TYPE_UID)
  .create({ data: { title: "New" } });
await strapi.documents(CONTENT_TYPE_UID).publish({ documentId });

要点: strapi.documents(uid) 取代了 strapi.entityService(已在 v5 中移除);documentId 是跨语言环境及草稿/已发布版本的持久标识符;默认状态为 'draft'(需显式传入 status: 'published');提供专用的 publish()/unpublish()/discardDraft() 方法。完整的 CRUD 和发布/取消发布示例请参阅 examples/core.md。


模式 4:自定义控制器

扩展或替换自动生成的控制器操作。控制器负责处理请求/响应逻辑,并将工作委托给服务。使用 factories 中的 createCoreController 来继承清理辅助方法。

// src/api/article/controllers/article.ts
export default factories.createCoreController(
  "api::article.article",
  ({ strapi }) => ({
    async find(ctx) {
      await this.validateQuery(ctx);
      const sanitizedQuery = await this.sanitizeQuery(ctx);
      const { results, pagination } = await strapi
        .service("api::article.article")
        .find(sanitizedQuery);
      const sanitizedResults = await this.sanitizeOutput(results, ctx);
      return this.transformResponse(sanitizedResults, { pagination });
    },
  }),
);

优点: validateQuery + sanitizeQuery + sanitizeOutput 可强制执行基于角色的字段访问;transformResponse() 会将输出封装在 { data, meta } 信封中。

完整的控制器示例(包括自定义操作 findBySlug、incrementViews、search)请参阅 examples/backend.md。


模式 5:自定义路由

定义自定义路由,以公开自定义控制器操作。自定义路由文件与核心路由器放在一起。

// src/api/article/routes/custom-article.ts
export default {
  routes: [
    {
      method: "GET",
      path: "/articles/slug/:slug",
      handler: "api::article.article.findBySlug",
      config: { auth: false },
    },
  ],
};

优点: 与核心路由器分开存放,handler 使用完整 UID,通过 auth: false 允许公开访问,可为每条路由附加策略和中间件

有关使用 only 限制路由、附加中间件和配置策略,请参阅 examples/backend.md。


模式 6:生命周期钩子

为内容类型操作注册副作用。钩子定义在与内容类型架构放在一起的 lifecycles.ts 文件中。

// src/api/article/content-types/article/lifecycles.ts
export default {
  async beforeCreate(event) {
    /* event.params.data -- mutate to transform input */
  },
  async afterCreate(event) {
    /* event.result -- the created document */
  },
  async beforeUpdate(event) {
    /* event.params.data -- mutate before save */
  },
  async afterDelete(event) {
    /* event.result -- cleanup related resources */
  },
};

优点: 钩子会在 Document Service 操作时自动触发,beforeXxx 可以修改 event.params.data,afterXxx 可以获取 event.result

注意: 在 v5 中,对于横切关注点(如审计日志记录、缓存失效),建议使用在 src/index.ts 中注册的 Document Service 中间件(strapi.documents.use((ctx, next) => { ... }))。生命周期钩子仍可用于内容类型特定的副作用。

有关生命周期示例,请参阅 examples/backend.md,其中包括生成 slug、以编程方式订阅和审计日志记录。


模式 7:服务

服务包含可由控制器调用的可复用业务逻辑。使用 createCoreService 继承默认 CRUD 功能并添加自定义方法。

export default factories.createCoreService(
  "api::article.article",
  ({ strapi }) => ({
    async findPublished(filters = {}) {
      return strapi
        .documents("api::article.article")
        .findMany({ status: "published", filters });
    },
  }),
);
// Called via: strapi.service("api::article.article").findPublished()

优点: 继承默认 CRUD 功能,自定义方法封装可复用的查询逻辑,可通过 strapi.service(uid) 从控制器调用

有关复杂服务示例,请参阅 examples/backend.md,其中包括相关联文章查询和发布时发送通知的模式。


模式 8:策略

策略是只读函数,用于允许或拒绝对路由的访问。它们返回 true(允许)或 false(拒绝),且不能修改请求。

// src/api/article/policies/is-owner.ts
export default async (policyContext, config, { strapi }) => {
  const article = await strapi.documents("api::article.article").findOne({
    documentId: policyContext.params.documentId,
    populate: { author: true },
  });
  return article?.author?.documentId === policyContext.state.user?.documentId;
};

优点: 只读检查(不会修改请求),可在核心路由器配置中按操作应用

有关将策略应用于路由、速率限制策略和多策略链式调用,请参阅 examples/backend.md。


<decision_framework>

决策框架

REST API 与 Document Service API
Where is your code running?
+-- Client-side (browser, frontend app)
|   +-- Use REST API (fetch /api/:pluralApiId)
+-- Server-side (custom controller, service, plugin, lifecycle hook)
    +-- Use Document Service API (strapi.documents(uid))
填充策略
Do you need related data?
+-- NO --> Don't pass populate (leaner response)
+-- YES --> What level of control?
    +-- All relations, 1 level --> populate: '*' (convenient but over-fetches)
    +-- Specific relations --> populate: { relation: { fields: [...] } }
    +-- Nested relations --> populate: { relation: { populate: { nested: true } } }
    +-- Filtered relations --> populate: { relation: { filters: { ... } } }
内容类型种类
How many documents of this type exist?
+-- Many (articles, products, users) --> collectionType
+-- One (site settings, homepage, footer) --> singleType
自定义控制器与默认控制器
Do default CRUD endpoints meet your needs?
+-- YES --> Use auto-generated routes (no custom controller needed)
+-- NO --> What do you need?
    +-- Modified default behavior --> Override find/findOne/create/update/delete in createCoreController
    +-- Entirely new endpoint --> Add custom action + custom route
    +-- Access control logic --> Add a policy to the route config
    +-- Request transformation --> Add a route middleware
草稿与发布
Does content need editorial review before going live?
+-- YES --> Enable draftAndPublish: true in schema
|   +-- Document Service defaults to status: 'draft'
|   +-- Use publish()/unpublish() to manage lifecycle
|   +-- REST API returns published content by default
+-- NO --> Disable draftAndPublish (content is always live)
身份验证方式
Who is consuming the API?
+-- End users (login/register) --> Users & Permissions plugin (JWT)
+-- External services/scripts --> API tokens (admin panel > Settings > API Tokens)
+-- Admin panel users --> Admin API tokens (separate from content API)

</decision_framework>


<red_flags>

危险信号

高优先级问题:

  • 在 v5 中使用 strapi.entityService——Entity Service 已移除。所有后端数据访问都应使用 strapi.documents()(Document Service API)。
  • 未填充关联数据——Strapi 默认不会返回关联、媒体、组件或动态区域。忘记使用 populate 会导致字段为 null 或缺失,看起来像是数据丢失。
  • 在生产环境中使用 populate=*——会获取一层深度内的所有关联,其中可能包含用户无权查看的数据。请使用指定字段的定向填充。
  • 手动构造查询字符串——手动构建复杂的筛选/填充 URL 会因特殊字符和嵌套参数而出错。请使用 qs 库。
  • 自定义控制器中缺少清理操作——跳过 sanitizeQuery()、sanitizeOutput() 和 validateQuery() 会绕过权限检查,并暴露私有字段。

中优先级问题:

  • 混淆 documentId 与 id——在 v5 中,documentId 是跨语言区域和草稿/已发布版本的持久标识符。数据库 id 是内部整数。REST API 和 Document Service 使用 documentId。
  • 未设置权限——默认情况下,所有内容类型端点都是私有的。若未在管理面板中配置权限,API 请求会返回 403。
  • 假设默认状态为已发布——Document Service API 默认使用 status: 'draft'。若要获取已发布内容,必须显式传入 status: 'published'。
  • 使用 publicationState 参数(v4 语法)——v5 中已由 status 参数以及专用的 publish()/unpublish() 方法取代。
  • 在 qs.stringify 中忘记 encodeValuesOnly: true——若不设置此选项,qs 会对数组方括号索引进行编码,而 Strapi 的解析器可能无法正确处理。

常见错误:

  • 未等待相当于 .commit() 的操作——Document Service 方法会返回 Promise。务必对其使用 await。
  • 组件中缺少 _type 或 _key——通过 API 创建/更新动态区域和组件字段时,需要使用 __component 标识符。
  • 将 Strapi URL 硬编码——API 基础 URL 应使用环境变量(STRAPI_URL);硬编码 localhost:1337 会导致生产环境出错。
  • 混用分页方法——只能使用 page/pageSize 或 start/limit,不要在同一查询中同时使用两者。

注意事项与边缘情况:

  • v5 响应格式为扁平结构——v4 将数据嵌套在 data.attributes 中;v5 则将字段直接放在 data 对象上。现有的 v4 前端代码会因此出错。
  • uid 字段类型存储 slug——uid 字段类型会根据目标字段自动生成 URL 安全的 slug。可直接将 slug 字段作为顶层属性查询(无需嵌套访问器)。
  • Document Service 不会触发批量生命周期钩子——beforeCreateMany、afterDeleteMany 等属于数据库级钩子。Document Service 操作只会触发单文档钩子。
  • v5 中的生命周期钩子属于数据库级别——生命周期钩子在数据库层触发,因此单个 Document Service 操作(例如 publish())可能会触发多个数据库级钩子。对于日志记录、验证和缓存失效等横切关注点,建议使用 Document Service 中间件(strapi.documents.use()),它在 Document Service 抽象层运行。
  • 动态区域的 populate 使用 on 语法——若要填充动态区域中的特定组件,请使用 populate: { blocks: { on: { 'blocks.hero': { populate: '*' } } } }。
  • 媒体字段在内部作为关联处理——媒体上传存储在 upload 插件中,并通过关联引用。它们遵循与其他关联相同的填充规则。
  • 默认情况下,REST API 不会显示草稿更改——REST API 返回已发布内容。若要查看草稿,需要具有相应权限的令牌以及 status=draft 参数。

</red_flags>


<critical_reminders>

关键提醒

所有代码都必须遵循 CLAUDE.md 中的项目规范(kebab-case、命名导出、导入排序、import type、命名常量)

(在 Strapi v5 中,所有后端数据访问都必须使用 strapi.documents('api::content-type.content-type')(Document Service API)——Entity Service API 已移除)

(需要关联、媒体、组件或动态区域时,必须始终传入 populate——Strapi 默认不会返回任何关联)

(必须使用 qs 库构建包含筛选条件、populate 和排序的复杂 REST API 查询字符串——手动拼接字符串会破坏嵌套参数)

(在自定义控制器中,必须使用 this.sanitizeQuery(ctx)、this.sanitizeOutput() 和 this.validateQuery(ctx) 清理并验证输入与输出)

(必须通过管理面板或配置为每个内容类型端点设置权限——默认情况下,所有路由都是私有的)

不遵循这些规则会导致数据缺失(未使用 populate)、安全漏洞(未清理数据)、查询失效(未使用 qs)以及 403 错误(未设置权限)。

</critical_reminders>

评论

支持 Markdown