跳到主要内容

使用 REST API

Flarum 提供了 REST API,它不仅被我们的单页应用使用着,也可供外部程序调用。

我们的 API 采用 JSON:API 规范定义的最佳实践。

信息

若要扩展 REST API 以实现新的应用,请查看开发者文档《API 与数据流》。

身份验证

我们的单页应用使用会话 Cookies 进行 API 的身份验证。 外部程序可使用 API 密钥访问令牌 的无状态身份验证。

GET 端点无需身份验证即可使用, 但此时只会返回游客可见的内容。 为防止 CSRF 攻击,其他端点均需身份验证方可使用。

API 密钥

脚本、工具与集成应用和 Flarum 的交互应当采用 API 秘钥作为首选方案。

创建

目前没有用于管理 API 密钥的操作界面,您只能在数据库 api_keys 表中手动创建。

以下参数可在创建时提供:

  • key:秘钥。您需要自行生成一个独一无二的长字符串(推荐长度 40 的字母数字组合),秘钥将作为 Authorization 请求头的值。
  • user_id:用户 ID,可选项。 如果设置了此值,秘钥会被充当为指定的用户。

以下属性会被自动填充,部分作为保留字段尚未使用:

  • id:主键。由 MySQL 自动递增填写。
  • allowed_ips:IP 白名单。保留字段,尚未使用。
  • scopes:范围。保留字段,尚未使用。
  • created_at:创建时间。虽然可设置任意日期值,但理应表示秘钥的创建日期。
  • last_activity_at:上次使用时间。秘钥被使用时自动更新。

使用

发起 API 请求时,将秘钥添加到 Authorization 请求头即可。 如需指定扮演用户角色,可在请求头末尾添加。

Authorization: Token 你的_API_秘钥_值; userId=1

如果在数据库中为密钥设置了 user_id 值,请求头中的 userId= 将被忽略。 否则,任何有效的用户 ID 都可起作用。

访问令牌

访问令牌(Access Tokens)是基于用户的短效令牌。

这些令牌用于 Cookie 会话, 使用他们与常规会话别无二致。 用户的上次在线时间会随访问令牌的使用而更新。

创建

所有用户均可创建访问令牌。 要创建令牌,请使用 /api/token 端点并提供用户凭证:

POST /api/token HTTP/1.1

{
"identification": "张三",
"password": "张三的密码"
}

HTTP/1.1 200 OK

{
"token": "YACub2KLfe8mfmHPcUKtt6t2SMJOGPXnZbqhc3nX",
"userId": "1"
}

我们目前存在 3 种令牌类型,其中 2 种可以通过 REST API 创建。

  • session 令牌在 1 小时没有活动后即会过期。 这是默认的令牌类型。
  • session_remember 令牌在 5 年没有活动后即会过期。 在请求体中添加 remember=1 属性即可获取这种令牌。
  • developer 令牌永不过期。 目前只可通过数据库手动创建这种令牌。

所有令牌将在用户注销时一并失效(包括 developer 令牌,不过我们计划改变这种状况)。

使用

发起 API 请求时,将上一步取得的 token 添加到 Authorization 请求头即可:

Authorization: Token YACub2KLfe8mfmHPcUKtt6t2SMJOGPXnZbqhc3nX

CSRF 保护

多数 POST/PUT/DELETE API 端点都有 跨站请求伪造(Cross-site request forgery,缩写 CSRF)保护功能。 因此,要发出无状态请求,必须进行身份验证。

使用 API 密钥或访问令牌时,可跳过 CSRF 保护。

端点

这部分文档仍在编写中。 我们正在研究如何为端点实现自动化文档。

每个扩展程序都可自行添加新的端点和属性,因此提供涵盖所有端点的文档。 若要查看端点,您可以使用浏览器的开发者工具检查单页应用发起的请求。

下面是一些常用接口的示例。 为方便阅读,部分 JSON 字段已略去。

列出全部主题帖

GET /api/discussions
{
"links": {
"first": "https://flarum.tld/api/discussions",
"next": "https://flarum.tld/api/discussions?page%5Boffset%5D=20"
},
"data": [
{
"type": "discussions",
"id": "227",
"attributes": {
"title": "出师表",
"slug": "227-chu-shi-biao",
"commentCount": 10,
"participantCount": 3,
"createdAt": "0227-01-01T00:10:30+08:00",
"lastPostedAt": "0227-01-05T00:10:30+08:00",
"lastPostNumber": 10,
"canReply": true,
"canRename": true,
"canDelete": true,
"canHide": true,
"isHidden": true,
"hiddenAt": "0227-01-01T00:10:30+08:00",
"lastReadAt": "0227-01-01T00:10:30+08:00",
"lastReadPostNumber": 2,
"isApproved": true,
"canTag": true,
"isLocked": false,
"canLock": true,
"isSticky": false,
"canSticky": true,
"canMerge": true,
"subscription": null
},
"relationships": {
"user": {
"data": {
"type": "users",
"id": "1"
}
},
"lastPostedUser": {
"data": {
"type": "users",
"id": "64"
}
},
"tags": {
"data": [
{
"type": "tags",
"id": "3"
}
]
},
"firstPost": {
"data": {
"type": "posts",
"id": "668"
}
}
}
},
{
"type": "discussions",
"id": "234",
"attributes": {
// [...]
},
"relationships": {
// [...]
}
},
// [...] 更多主题
],
"included": [
{
"type": "users",
"id": "1",
"attributes": {
"username": "Zhugeliang",
"displayName": "诸葛亮",
"avatarUrl": null,
"slug": "1"
}
},
{
"type": "users",
"id": "64",
"attributes": {
"username": "Flarum",
"displayName": "Flarum",
"avatarUrl": "https://flarum.tld/assets/avatars/Z4hEncw0ndVqZ8be.png",
"slug": "64"
}
},
{
"type": "tags",
"id": "3",
"attributes": {
"name": "欢迎",
"description": "在这里发送一些有趣的玩意儿",
"slug": "welcome",
"color": "#888",
"backgroundUrl": null,
"backgroundMode": null,
"icon": "fas fa-bullhorn",
"discussionCount": 30,
"position": 1,
"defaultSort": null,
"isChild": false,
"isHidden": false,
"lastPostedAt": "2022-01-05T10:20:30+00:00",
"canStartDiscussion": true,
"canAddToDiscussion": true,
"isRestricted": false
}
},
{
"type": "posts",
"id": "668",
"attributes": {
"number": 1,
"createdAt": "2022-01-01T10:20:30+00:00",
"contentType": "comment",
"contentHtml": "<p>你好,世界!</p>"
}
},
// [...] 其他包含内容
]
}

发布主题

POST /api/discussions
{
"data":{
"type": "discussions",
"attributes": {
"title": "这里是标题",
"content": "你好,世界!"
},
"relationships": {
"tags": {
"data": [
{
"type": "tags",
"id": "1"
}
]
}
}
}
}

服务器响应含有新主题的 ID:

{
"data": {
"type": "discussions",
"id": "42",
"attributes": {
"title": "这里是标题",
"slug": "42-gu-ding-lian-jie",
"commentCount": 1
// [...] 其他属性
},
"relationships": {
"posts": {
"data": [
{
"type": "posts",
"id": "58"
}
]
},
"user": {
"data": {
"type": "users",
"id": "1"
}
},
// [...] 其他关联内容
}
},
"included":[
{
"type": "posts",
"id": "38",
"attributes": {
"number": 1,
"contentType": "comment",
"contentHtml": "\u003Cp\u003E你好,世界!\u003C\/p\u003E"
// [...] 其他属性
}
}
// [...] 其他包含内容
]
}

注册用户

POST /api/users
{
"data": {
"attributes": {
"username": "YongHuMing",
"email": "[email protected]",
"password": "MiMa"
}
}
}

错误

Flarum 使用不同的 HTTP 状态码进行错误应答,所有错误表述均符合 JSON:API 错误规范

下面是您使用 REST API 时可能遇到的一些常见错误:

CSRF 令牌不匹配

如果您收到了消息为 csrf_token_mismatch 的 400 HTTP 错误,这意味着 Authorization 请求头缺失或无效,导致 Flarum 尝试通过会话 Cookie 进行身份验证。

{
"errors": [
{
"status": "400",
"code": "csrf_token_mismatch"
}
]
}

验证错误

验证错误会返回一个 HTTP 状态码 422 的响应。 无效字段的名称会以 pointer 值返回。 在同一时间,单个字段可能出现多个错误。

{
"errors": [
{
"status": "422",
"code": "validation_error",
"detail": "The username has already been taken.", // 用户名已被使用
"source":{
"pointer":"\/data\/attributes\/username"
}
},
{
"status": "422",
"code": "validation_error",
"detail": "The email has already been taken.", // 邮箱地址已被使用
"source": {
"pointer":"\/data\/attributes\/email"
}
}
]
}