emteknetnz / silverstripe-rest-api

Silverstripe的Rest API

安装: 40

依赖项: 0

建议者: 0

安全: 0

星标: 6

关注者: 1

分支: 0

公开问题: 7

类型:silverstripe-vendormodule

0.1.8 2023-11-25 03:28 UTC

This package is auto-updated.

Last update: 2024-09-13 00:57:46 UTC


README

注意:此模块目前为预发布版。API和/或行为可能会随着新预发布版本的标记而更改。

此模块允许您快速轻松地创建安全的REST API端点,这些端点可用于提供数据库记录作为JSON,以及可选地通过API更新数据。

简单继承RestApiEndpoint类,并使用private static array配置定义您的端点。

端点提供一种数据对象类型的数据,例如SiteTree。要为更多数据对象类型提供数据,请添加更多端点。

如果您端点提供非数据对象数据,则此模块不打算取代常规Silverstripe控制器端点。

以下说明假设您已创建一个项目,其中在composer.json中包含silverstripe/recipe-cms,因为一些代码说明包括SiteTree类。您仍然可以使用此模块而不需要silverstripe/recipe-cms,因为silverstripe/framework是唯一的要求,尽管对于publishunpublisharchive操作需要silverstripe/versioned

还有一个常见的陷阱,如果您的数据对象未显示在JSON响应中,那么您可能需要添加一个返回truecanView()到您的数据对象中。或者,您可以直接在端点配置中将CALL_CAN_METHODS设置为CREATE_EDIT_DELETE_ACTION(缺少VIEW)以禁用canView()检查。

内容

安装

composer require emteknetnz/silverstripe-rest-api

在Silverstripe CMS 4和5上均有效。

快速入门

将以下代码片段复制粘贴以快速设置一个公开只读端点,提供SiteTree数据。

这假设您已创建一个项目,其中已安装silverstripe/cms,以便您可以使用SiteTree类。

src/MySiteTreeEndpoint.php

<?php

use emteknetnz\RestApi\Controllers\RestApiEndpoint;
use SilverStripe\CMS\Model\SiteTree;

class MySiteTreeEndpoint extends RestApiEndpoint
{
    private static array $api_config = [
        RestApiEndpoint::PATH => 'api/pages',
        RestApiEndpoint::DATA_CLASS => SiteTree::class,
        RestApiEndpoint::ACCESS => RestApiEndpoint::PUBLIC,
        RestApiEndpoint::FIELDS => [
            'title' => 'Title',
            'absoluteLink' => 'AbsoluteLink',
            'content' => 'Content',
            'lastEdited' => 'LastEdited',
        ],
    ];
}

运行https://mysite.test/dev/build?flush=1

访问https://mysite.test/api/pages以查看端点数据。

访问https://mysite.test/api/pages/1以查看ID为1的页面的端点数据。

查询数据

通过向端点发出的GET请求中添加查询字符串参数来过滤数据

过滤

按精确值进行过滤

?filter=[<字段>]=<值>

使用搜索过滤器,例如PartialMatchFilter,使用

?filter=[<字段>:<SearchFilter>]=<值>

在查询字符串中使用搜索过滤器时,请从其名称中省略'Filter'后缀。例如,要使用StartsWithFilter搜索以"Hello"开头的标题,请在查询字符串中使用StartsWith

?filter=[<标题>:StartsWith]=Hello

使用搜索过滤器修饰符,例如"case"使用

?filter=[<字段>:<SearchFilter>:<修饰符>]=<值>

例如,要返回包含单词“关于”的所有页面,且区分大小写。请注意,如果没有指定,Silverstripe ORM默认使用nocase搜索修饰符。

?filter[title:PartialMatch:case]=About

要使用多个过滤器,例如根据titlelastEdited字段进行筛选

?filter[title:PartialMatch]=rockets&filter[lastEdited:GreaterThan]=2022-01-01

以下是一些可用的搜索过滤器

  • ExactMatch
  • StartsWith
  • EndsWith
  • PartialMatch
  • GreaterThan
  • GreaterThanOrEqual
  • LessThan
  • LessThanOrEqual

以下是一些可用的搜索过滤器修饰符

  • case
  • no-case
  • not

排序

按升序排序字段

?sort=<field>

按降序排序字段

?sort=-<field>

要按多个字段排序,请使用逗号分隔它们

?sort=-<field1>,<field2>

例如,要按发布年份降序排序,然后按标题升序排序

?sort=-publishedYear,title

限制和偏移

限制记录数量

?limit=<number>

偏移记录

?offset=<number>

例如,获取10条记录的第二页

?limit=10&offset=10

默认限制为30,通过查询字符串指定的最大限制为100。这两个限制都可以在端点配置中更改。

HTTP请求和状态码

失败代码

以下失败代码在各种请求中使用

响应体将是包含"success":false节点和描述错误的"message"节点的JSON。

OPTIONS

OPTIONS HTTP请求始终允许,并将返回端点的允许操作列表,位于allow响应头中

GET

GET HTTP请求用于从端点读取数据。您可以通过访问端点URL来查看节点列表,或通过访问带有节点ID的端点URL来查看单个节点。例如。

示例

  • curl -X GET https://mysite.test/api/pages
  • curl -X GET https://mysite.test/api/pages/<id>

HEAD

GET相同,但只返回GET将返回的头部,没有正文

示例

  • curl --head https://mysite.test/api/pages
  • curl --head https://mysite.test/api/pages/123

POST

POST HTTP请求用于创建新记录。请求体应是一个JSON对象,包含创建记录所需的数据,该数据与端点配置匹配,即指定要更新的jsonKey,而不是dataObjectKey

指定字段值是可选的,尽管可能需要根据DataObject验证。

示例

  • curl -X POST https://mysite.test/api/pages -d '{"title":"My title"}'

PATCH

PATCH HTTP请求用于更新指定在URL中的ID的现有记录。请求体应是一个JSON对象,包含更新记录所需的数据,该数据与端点配置匹配,即指定要更新的jsonKey,而不是dataObjectKey

指定字段值是可选的,尽管可能需要根据DataObject验证。

示例

  • curl -X PATCH https://mysite.test/api/pages/123 -d '{"title":"My updated title"}'

DELETE

DELETE HTTP 请求用于删除在 URL 中指定 ID 的现有记录。如果已经将 silverstripe/versioned 模块中的 Versioned 扩展应用到 DataObject 上,那么就会在 DataObject 上调用 doArchive() 方法,将其从网站的草稿版和实时版本中删除。如果没有应用 Versioned 扩展,那么就会在 DataObject 上调用 delete() 方法。

示例

  • curl -X DELETE https://mysite.test/api/pages/123

PUT

PUT HTTP 请求仅用于执行预定义的一系列操作。它不用于创建或更新数据,这在其他 REST API 实现中通常用 PUT 来执行。相反,对于创建操作使用 POST,对于更新操作使用 PATCH

操作只能对现有记录执行。操作参数添加到现有记录的 ID 之后。

以下是一些可用的操作

示例

  • curl -X PUT https://mysite.test/api/pages/123/publish

端点配置选项

端点配置是通过 RestApiEndpoint 子类上的 private array static $api_config 字段来完成的。请记住使用 ?flush=1 来应用新的配置。以下表格包含 RestApiEndpoint 类上可用的配置常量列表。

关系

您的端点可以包含数据对象上的关系数据,例如 has_onehas_manymany_many 关系。关系的配置遵循与顶级配置相同的规则。

例如,有一个名为 Team 的类,它有一个 db 字段 Title 和一个 has_many 关系 Players,而 Player 类有一个 db 字段 LastName,您可以使用以下端点配置来显示每个 Team 上的所有 Players

请注意,当包含关系时,由于无法分页关系数据,所以所有关系都会包含在响应中,而不是默认的 30 条限制。

private static array $api_config = [
    RestApiEndpoint::PATH = 'api/teams';
    RestApiEndpoint::DATA_CLASS => Team::class,
    RestApiEndpoint::FIELDS => [
        // db fields
        'title' => 'Title',
        'yearFounded' => 'YearFounded',
        // has_one relation
        'city' => [
            RestApiEndpoint::RELATION => 'City',
                RestApiEndpoint::FIELDS => [
                    'name' => 'Name',
                ],
            ],
        ],
        // has_many relation
        'players' => [
            RestApiEndpoint::RELATION => 'Players',
            RestApiEndpoint::FIELDS => [
                'lastName' => 'LastName,
            ],
        ],
    ],
];

可以在端点配置中定义的 has_one 关系可以通过使用魔法字段 <jsonField>__ID 通过 POSTPATCH 请求进行设置。

例如,要更新具有 ID77 的现有 Team 数据对象上的 CityID1415

curl -X PATCH https://mysite.test/api/teams/77 -d '{"city__ID":"15"}'

单个字段

包含字段的正常表示法是 <jsonField> => <dataObjectField>。如果您希望限制这些字段,则可以配置单个字段以拥有自己的 ACCESSALLOWED_OPERATIONS。执行此操作时,表示法会更改到数组表示法,其中 DATA_OBJECT_FIELD 是常规表示法中 <dataObjectField>DATA_OBJECT_FIELD

例如,要将 Team 类上的 PrivateField 设置为仅对通过自定义 CAN_ACCESS_PRIVATE_FIELD 权限检查的登录成员可访问

private static array $api_config = [
    RestApiEndpoint::PATH = 'api/teams';
    RestApiEndpoint::DATA_CLASS => Team::class,
    RestApiEndpoint::FIELDS => [
        'title' => 'Title',
        'privateField' => [
            RestApiEndPoint::DATA_OBJECT_FIELD => 'PrivateField',
            RestApiEndpoint::ACCESS => 'CAN_ACCESS_PRIVATE_FIELD',
        ],
    ],
];

CSRF 令牌

如果端点的 ACCESS 设置为除了 PUBLIC 之外的内容,那么请求需要发送一个 x-csrf-token 标头,除非发送了 x-api-token。有效的令牌由 SecurityToken::getSecurityID() 生成。在 CMS 中,它可以通过 window.ss.config.SecurityID; 获取。

例如,以下 JavaScript 代码将在登录 Silverstripe CMS 时包含 x-csrf-token 标头的 GET 请求。

fetch(
    '/api/pages',
    { headers: { 'x-csrf-token': window.ss.config.SecurityID } }
)
    .then(response => response.json())
    .then(responseJson => console.log(responseJson));

当与非公开API端点一起工作时,您可能希望禁用csrf令牌检查,以便您可以在浏览器的地址栏中快速测试GET查询。您可以通过在app/_config.php中调用SecurityToken::disable()来完成此操作,尽管这样做时要非常小心,以免在生产环境中也被禁用。为了安全起见,您可以将此操作包装在一个检查您选择的设置的环境变量的检查中,您可以在您的本地.env文件中设置该变量,例如

use SilverStripe\Core\Environment;
use SilverStripe\Security\SecurityToken;

// ...

if (Environment::getEnv('DISABLE_API_CSRF_TOKEN_CHECK')) {
    SecurityToken::disable();
}

x-csrf-token头在RestApiEndpoint::CSRF_TOKEN_HEADER上作为常量可用。

API令牌

非公开API可以配置为允许成员使用HTTP头进行身份验证,而不是必须登录到CMS。

如果使用API身份验证,用户将仅在请求期间登录,即他们在返回JSON响应之前将被注销。

本模块提供了一个“使用API令牌”权限,即API_TOKEN_AUTHENTICATION,必须将其分配给使用API令牌的用户所属的组。必须将端点配置ALLOW_API_TOKEN设置为true

当用户和端点设置允许使用API令牌时,传递一个包含API令牌值的x-api-token头以进行身份验证。请注意,如果为该用户设置了多因素身份验证(MFA),则API令牌身份验证将绕过MFA。

使用CMS设置API用户和组

创建API用户和组

  1. 以管理员身份登录到CMS
  2. 转到安全部分
  3. 创建一个名为“API用户”的新组
  4. 单击权限选项卡(右上角)
  5. 勾选“使用API令牌” - 这是权限代码API_TOKEN_AUTHENTICATION的标签
  6. 保存组
  7. 单击“添加成员”
  8. 创建一个名为“api-user”的新用户,电子邮件为"api-user@example.com",并使用一个长随机密码
  9. 将其分配给“Api Users”组
  10. 勾选“生成新的API令牌”复选框,然后单击“保存”
  11. 复制生成的API令牌 - 您只会看到一次

其他组权限

“api-user”仍然需要通过所有必要的权限检查,以便API可以正常工作,即canView()检查仍然通过。您可以选择以下两种方法之一

  • 更新“API Users”组以具有必要的权限,或者
  • 将端点配置CHECK_CAN_METHODS设置为NONE,但您必须确保API的ACCESS设置为仅分配给专用API用户的权限代码。

以编程方式更新用户的API令牌

使用$member->refreshApiToken();以编程方式更新用户的API令牌,然后使用$member->write();。返回值是未加密的API令牌。成员的ApiToken字段将是加密的API令牌。

请注意,对于新创建的用户,必须在调用$member->refreshApiToken();之前至少调用一次$member->write(),以确保API令牌被正确加密。

扩展钩子

您可能需要向API添加自定义逻辑,可以使用以下表中的可用扩展钩子。通过将以下方法之一直接添加到您的RestApiEndpoint子类中并使用protected可见性来实现钩子。您也可以使用public可见性在扩展类中实现它们。

例如,以下对onEditBeforeWrite()钩子的实现将在保存之前更新通过PATCH请求更新的数据对象的Content字段,即使该Content字段没有在API中公开。

请注意,要运行此代码示例,您需要登录到CMS以使用它,并在发出请求时传递一个x-csrf-token头。

src/MySiteTreeEndpoint.php

<?php

use emteknetnz\RestApi\Controllers\RestApiEndpoint;
use SilverStripe\CMS\Model\SiteTree;
use SilverStripe\ORM\DataObject;

class MySiteTreeEndpoint extends RestApiEndpoint
{
    private static array $api_config = [
        RestApiEndpoint::PATH => 'api/pages',
        RestApiEndpoint::DATA_CLASS => SiteTree::class,
        RestApiEndpoint::ACCESS => RestApiEndpoint::LOGGED_IN,
        RestApiEndpoint::ALLOWED_OPERATIONS => RestApiEndpoint::VIEW_CREATE_EDIT_DELETE_ACTION,
        RestApiEndpoint::FIELDS => [
            'title' => 'Title',
        ],
    ];

    protected function onEditBeforeWrite(SiteTree $page)
    {
        // You wouldn't normally do this, this is only for demo purposes
        $page->Content .= '<p>This was updated using the API</p>';
    }
}

注意事项

  • 如果你的扩展钩子更新了DataObject或其他DataObject,那么你很可能应该使用不同的扩展钩子,例如在DataObject本身上使用onAfterWrite(),而不是在端点上。这是因为通常情况下,对象是通过API或另一种方式创建/更新/删除的,这通常无关紧要。这些钩子旨在便于实现API特定的代码,例如记录通过API执行的操作。
  • 对于onView*()钩子,如果你正在为响应JSON添加额外数据,请记住,对于任何被添加的DataObject,都要调用canView()
  • 对于这两个onEdit*Write()钩子,$changedFields参数是对象写入之前,通过$obj->getChangedFields()返回的值。