Directus 12.5 API 使用详解
Directus 12.5.0 REST/GraphQL API 全流程 curl 实测:登录/refresh 轮换/静态令牌/登出四种凭证、Collections 与 Items CRUD、filter/sort/limit/offset/search/aggregate 查询体系、文件上传与资产下载、用户角色权限边界(v12 Policy 模型)、GraphQL 权限裁剪与类型差异对比,含 INVALID_CREDENTIALS/TOKEN_EXPIRED/FORBIDDEN 等错误码实录,回显逐字照抄(2026-10-19)。
Directus 12.5 API 使用详解
目标:用 curl 把 Directus 12.5.0 的对外 API 完整跑通一遍——认证(登录/刷新/静态令牌/登出)、Collections 与 Items CRUD、查询参数体系(fields/filter/sort/limit/offset/search/aggregate/meta)、文件上传与资产下载、用户角色权限边界、GraphQL 与 REST 对比,全部 JSON 回显逐字照抄自 2026-10-19 在本机 8103 实例上的实测。实例部署见 《Directus Docker Compose 部署指南》,参数含义见 《Directus 12.5 配置参数详解》。
一、环境信息
| 项目 | 值 |
|---|---|
| 服务器 IP | 192.168.146.128(网卡 ens33) |
| 操作系统 | Ubuntu 22.04.4 LTS(内核 6.8.0-138-generic) |
| Directus 实例 | directus/directus:12.5.0,API 基址 http://192.168.146.128:8103(应用内监听 8055) |
| 数据库 / 缓存 | PostgreSQL 17 / Redis 7 |
| 管理员 | admin@example.com / q1w2e3r4,user id 7103b67b-b376-4ffb-ac0d-14c5ccfd17db |
| 实测普通账号 | viewer@example.com / q1w2e3r4(仅 articles 只读权限),角色 c1-viewer |
| 实测业务集合 | articles(id/title/status,实测时含 id=1、2 两条) |
| 实测静态令牌 | svc-ops-token-2026(挂在管理员账号上) |
| 请求工具 | curl 7.81.0 |
| 敏感值处理 | access_token 为 15 分钟有效的 JWT,发布时均已过期,长 token 标注处截断 |
实测约定:命令中 $TOKEN 为管理员登录返回的 access_token(取值方法见第四章);HTTP 状态码与 JSON 回显逐字照抄,长响应标注「节选」。
二、前置条件
2.1 端点与协议
- REST 基址:
http://192.168.146.128:8103,全部走 HTTP/1.1 + JSON; - GraphQL 端点:同端口
/graphql(POST),订阅走 WebSocket/websocket(启动日志实测GraphQL Subscriptions started at 0.0.0.0:8055/graphql); - 探活:
GET /server/ping免鉴权。
2.2 客户端工具
系统自带 curl 7.81.0 即可完成全部实测;JSON 美化用 python3 -m json.tool。无额外依赖。
2.3 实测业务数据准备
articles 集合在《配置参数详解》实测时已创建(id/title/status 三个字段),本章在其上做 CRUD;id=1 标题为「缓存实测第四版」(draft),id=2 标题为「API 实测文章」(published)。
三、API 总览
3.1 端点族
| 端点族 | 前缀 | 鉴权 | 本章章节 |
|---|---|---|---|
| 认证 | /auth/login、/auth/refresh、/auth/logout | 邮箱口令 / refresh_token | 四 |
| 服务器 | /server/ping、/server/info | ping 免鉴权,info 需 token | 四 |
| 集合定义 | /collections | Bearer token | 五 |
| 条目数据 | /items/<collection> | Bearer token + 权限 | 六 |
| 文件 | /files、/assets/<id> | Bearer token + 权限 | 七 |
| 用户与权限 | /users、/roles、/policies | Bearer token(管理权限) | 八 |
| GraphQL | /graphql | Bearer token + 权限 | 九 |
3.2 三种凭证怎么选
| 凭证 | 形态 | 有效期 | 适用 |
|---|---|---|---|
| access_token(登录换得) | JWT,Authorization: Bearer 头 | 15 分钟(ACCESS_TOKEN_TTL 默认,回显 expires=900000ms 佐证) | 浏览器/交互场景,配合 refresh 轮换 |
| refresh_token | 不透明随机串 | 7 天(REFRESH_TOKEN_TTL 默认) | 免登出期间长期续期;一次性轮换 |
| 静态 token(用户 token 字段) | 自定义字符串,Authorization: Bearer 头 | 长期有效直至撤销 | 服务端集成、CI 脚本 |
3.3 统一错误结构
所有错误返回统一 JSON 结构:{"errors":[{"message":"...","extensions":{"code":"错误码"}}]},常用错误码实测收集:INVALID_CREDENTIALS(口令错/refresh 失效,401)、TOKEN_EXPIRED(JWT 过期,401)、INVALID_TOKEN(签名校验失败,403)、FORBIDDEN(无权限,403)、GRAPHQL_VALIDATION(GraphQL 校验,400)、INTERNAL_SERVER_ERROR(服务端异常,500)。
四、认证与首次调用
4.1 登录换令牌
curl -s -X POST http://192.168.146.128:8103/auth/login \
-H 'Content-Type: application/json' \
-d '{"email":"admin@example.com","password":"q1w2e3r4"}' \
-w "\nHTTP %{http_code}\n"
实测回显(access_token 为 15 分钟有效登录态,发布时已过期,照抄):
{"data":{"expires":900000,"refresh_token":"FgXS8KwcPNPc28sSiS0uAfELMkau7j3jlO99U-KuHhxvaWGRSnwYMYEggMbQVGIb","access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjcxMDNiNjdiLWIzNzYtNGZmYi1hYzBkLTE0YzVjY2ZkMTdkYiIsInJvbGUiOiIzMzdkZmNkYy1jNTMyLTQ5NGItOTk2Yy04NjY5ZmJjMGUzNjgiLCJhcHBfYWNjZXNzIjp0cnVlLCJhZG1pbl9hY2Nlc3MiOnRydWUsImlhdCI6MTc5MjM3MjExNCwiZXhwIjoxNzkyMzczMDE0LCJpc3MiOiJkaXJlY3R1cyJ9.1bQS5xilW0YXFTfbjMup46F59sxa5VJcEwSu7dJsSNg"}}
HTTP 200
expires: 900000(毫秒)即 15 分钟,与 ACCESS_TOKEN_TTL 默认值一致。
4.2 错误口令(错误码实测)
curl -s -X POST http://192.168.146.128:8103/auth/login \
-H 'Content-Type: application/json' \
-d '{"email":"admin@example.com","password":"wrong-pass"}' \
-w "\nHTTP %{http_code}\n"
实测回显:
{"errors":[{"message":"Invalid user credentials.","extensions":{"code":"INVALID_CREDENTIALS"}}]}
HTTP 401
4.3 首次调用 /users/me
TOKEN=<登录回显中的 access_token>
curl -s http://192.168.146.128:8103/users/me -H "Authorization: Bearer $TOKEN" | python3 -m json.tool
实测回显(节选):
{
"data": {
"id": "7103b67b-b376-4ffb-ac0d-14c5ccfd17db",
"first_name": "Admin",
"email": "admin@example.com",
"status": "active",
"role": "337dfcdc-c532-494b-996c-8669fbc0e368",
"provider": "default",
"policies": []
}
}
4.4 refresh 轮换(一次性)
RT=<登录回显中的 refresh_token>
curl -s -X POST http://192.168.146.128:8103/auth/refresh \
-H 'Content-Type: application/json' \
-d "{\"refresh_token\":\"$RT\",\"mode\":\"json\"}" \
-o /tmp/refresh.json -w 'HTTP %{http_code}\n'
python3 -c "import json; d=json.load(open('/tmp/refresh.json'))['data']; print('expires:', d['expires']); print('access_token:', d['access_token'][:60]+'...(截断)')"
实测回显:
HTTP 200
expires: 900000
access_token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjcxMDNiNjdiLWI...(截断)
refresh_token 是一次性轮换的:用旧值再刷一次,实测立即失效:
{"errors":[{"message":"Invalid user credentials.","extensions":{"code":"INVALID_CREDENTIALS"}}]}
HTTP 401
客户端逻辑必须保存每次 refresh 返回的新 refresh_token。
4.5 静态 token(服务端集成推荐)
给用户设置 token 字段即签发静态令牌(长期有效,撤销 = 置空):
curl -s -X PATCH http://192.168.146.128:8103/users/7103b67b-b376-4ffb-ac0d-14c5ccfd17db \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"token":"svc-ops-token-2026"}' -o /dev/null -w 'PATCH HTTP %{http_code}\n'
实测回显:
PATCH HTTP 200
用静态令牌调用(无需登录,头形态与 JWT 相同):
curl -s http://192.168.146.128:8103/users/me -H "Authorization: Bearer svc-ops-token-2026" \
| python3 -c "import json,sys; d=json.load(sys.stdin)['data']; print('email:',d['email'],'id:',d['id'])"
实测回显:
email: admin@example.com id: 7103b67b-b376-4ffb-ac0d-14c5ccfd17db
4.6 登出
curl -s -X POST http://192.168.146.128:8103/auth/logout \
-H 'Content-Type: application/json' \
-d "{\"refresh_token\":\"$RT\"}" -o /dev/null -w 'HTTP %{http_code}\n'
实测回显:
HTTP 204
登出后再用该 refresh_token 续期,实测被拒(INVALID_CREDENTIALS,401)——会话已销毁。
4.7 探活与服务器信息
curl -s http://192.168.146.128:8103/server/ping -w " HTTP %{http_code}\n"
curl -s http://192.168.146.128:8103/server/info -H "Authorization: Bearer $TOKEN" | python3 -m json.tool
实测回显(info 节选):
pong HTTP 200
{
"data": {
"project": {
"project_name": "Directus",
"project_color": "#6644FF",
"default_language": "en-US"
}
}
}
五、集合(Collections)API
集合定义即数据模型:一个集合对应库中一张表。列出全部集合:
curl -s "http://192.168.146.128:8103/collections?limit=3" -H "Authorization: Bearer $TOKEN" \
| python3 -c "import json,sys; d=json.load(sys.stdin)['data']; print(len(d),'个集合'); print([c['collection'] for c in d[:4]])"
实测回显:
35 个集合
['articles', 'directus_access', 'directus_activity', 'directus_collections']
⚠️ 注意实测发现:请求带了
limit=3,实际返回了全部 35 个集合——/collections 端点在 12.5 对 limit 参数未生效(其余 /items 端点 limit 正常,见第六章)。
返回里 directus_* 为系统集合(用户/角色/文件/权限/流程等),业务集合与系统集合同库同 API。查单个集合定义:
curl -s "http://192.168.146.128:8103/collections/articles" -H "Authorization: Bearer $TOKEN" \
| python3 -c "import json,sys; c=json.load(sys.stdin)['data']; print('collection:',c['collection'],'| schema:',c['schema']['name'],'| icon:',c['meta']['icon'])"
实测回显:
collection: articles | schema: articles | icon: article
六、条目(Items)CRUD
6.1 创建(POST)
curl -s -X POST http://192.168.146.128:8103/items/articles \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"title":"API 实测文章","status":"published"}' \
-w "\nHTTP %{http_code}\n"
实测回显:
{"data":{"id":2,"title":"API 实测文章","status":"published"}}
HTTP 200
6.2 查询参数体系(filter/sort/limit/offset/fields/search)
filter 是 JSON 对象经 URL 编码传入,配合 sort/limit/offset 使用:
curl -s "http://192.168.146.128:8103/items/articles?filter=%7B%22status%22%3A%7B%22_eq%22%3A%22published%22%7D%7D&sort=-id&fields=id,title&limit=1" \
-H "Authorization: Bearer $TOKEN" -w "\nHTTP %{http_code}\n"
实测回显(filter 为 {"status":{"_eq":"published"}},按 id 倒序取第一条):
{"data":[{"id":3,"title":"API 实测第二篇"}]}
HTTP 200
offset 翻页(跳过 1 条取下一条):
{"data":[{"id":2,"title":"API 实测文章"}]}
search 全文关键词(等效于各字段模糊匹配):
curl -s "http://192.168.146.128:8103/items/articles?search=%E7%AC%AC%E4%BA%8C%E7%AF%87&fields=id,title" -H "Authorization: Bearer $TOKEN"
实测回显:
{"data":[{"id":3,"title":"API 实测第二篇"}]}
6.3 计数与元信息(aggregate / meta)
计数用 aggregate(注意必须用对象写法):
curl -s "http://192.168.146.128:8103/items/articles?aggregate=%7B%22count%22%3A%22*%22%7D" -H "Authorization: Bearer $TOKEN"
实测回显:
{"data":[{"count":"3"}]}
⚠️ 实测发现:官方旧示例的简写
?aggregate=count:*在 12.5 上返回 500({"errors":[{"message":"Cannot assign to read only property '0' of string 'count:*'","extensions":{"code":"INTERNAL_SERVER_ERROR"}}]}),必须用对象式{"count":"*"}。
总条数元信息用 meta 参数:
curl -s "http://192.168.146.128:8103/items/articles?meta=filter_count&fields=id&limit=2" -H "Authorization: Bearer $TOKEN"
实测回显:
{"meta":{"filter_count":3},"data":[{"id":1},{"id":2}]}
💡 实测观察:响应头里并未输出 Content-Range(尽管 CORS 默认 Expose-Headers 含 Content-Range),分页总数以
meta=filter_count为准。
6.4 单条读取 / 更新 / 删除
curl -s "http://192.168.146.128:8103/items/articles/2?fields=id,title,status" -H "Authorization: Bearer $TOKEN" -w "\nHTTP %{http_code}\n"
实测回显:
{"data":{"id":2,"title":"API 实测文章","status":"published"}}
HTTP 200
更新(PATCH,只传变更字段):
curl -s -X PATCH http://192.168.146.128:8103/items/articles/2 \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"title":"API 实测文章(已修订)"}' -w "\nHTTP %{http_code}\n"
实测回显:
{"data":{"id":2,"title":"API 实测文章(已修订)","status":"published"}}
HTTP 200
删除(DELETE,无响应体):
curl -s -X DELETE http://192.168.146.128:8103/items/articles/3 -H "Authorization: Bearer $TOKEN" -o /dev/null -w 'HTTP %{http_code}\n'
实测回显:
HTTP 204
删除后再查同一条,实测返回 403 而非 404:
{"errors":[{"message":"You don't have permission to access this.","extensions":{"code":"FORBIDDEN"}}]}
HTTP 403
访问不存在的集合同样是 403(Directus 把「不存在」并入「无权限」,避免探测):
{"errors":[{"message":"You don't have permission to access collection \"nonexistent\" or it does not exist. Queried in root.","extensions":{"reason":"You don't have permission to access collection \"nonexistent\" or it does not exist. Queried in root.","code":"FORBIDDEN"}}]}
HTTP 403
七、文件 API
7.1 上传(multipart/form-data)
printf 'Directus D5 api test file 2026-10-19\n' > /tmp/d5-file.txt
curl -s -X POST http://192.168.146.128:8103/files \
-H "Authorization: Bearer $TOKEN" \
-F "file=@/tmp/d5-file.txt;type=text/plain" \
-w "\nHTTP %{http_code}\n" \
| python3 -c "
import sys, json
lines = sys.stdin.read().rstrip('\n').split('\n')
d = json.loads(lines[0])['data']
print(json.dumps({k: d[k] for k in ('id','storage','filename_download','type','filesize','uploaded_on')}, ensure_ascii=False))
print(lines[-1])"
实测回显:
{"id": "1017d537-07c9-4227-a078-34a0642bb44b", "storage": "local", "filename_download": "d5-file.txt", "type": "text/plain", "filesize": "37", "uploaded_on": "2026-10-19T01:10:39.217Z"}
HTTP 200
storage: local 表示落在本地存储位置(见《配置参数详解》第十章的落盘核对)。
7.2 文件列表
curl -s "http://192.168.146.128:8103/files?fields=id,title,filename_download&sort=-uploaded_on&limit=2" -H "Authorization: Bearer $TOKEN"
实测回显:
{"data":[{"id":"1017d537-07c9-4227-a078-34a0642bb44b","title":"D5 File","filename_download":"d5-file.txt"},{"id":"fba3d2d4-8fb5-4bf5-8076-d7010209dd4b","title":"C1 Upload","filename_download":"c1-upload.txt"}]}
7.3 资产下载
文件内容经 /assets/<id> 读取,?filename= 可改下载名:
curl -s "http://192.168.146.128:8103/assets/1017d537-07c9-4227-a078-34a0642bb44b?filename=renamed-test.txt" \
-H "Authorization: Bearer $TOKEN" -w " HTTP %{http_code}\n"
实测回显:
Directus D5 api test file 2026-10-19
HTTP 200
八、用户、角色与权限
8.1 用户列表与字段权限
curl -s "http://192.168.146.128:8103/users?fields=id,first_name,email,status&limit=2" -H "Authorization: Bearer $TOKEN"
实测回显:
{"data":[{"id":"7103b67b-b376-4ffb-ac0d-14c5ccfd17db","first_name":"Admin","email":"admin@example.com","status":"active"},{"id":"b012bfcf-f600-42d8-a59f-cf14abc16ccb","first_name":"Viewer","email":"viewer@example.com","status":"active"}]}
⚠️ 实测发现:查 /roles 时若带
fields=admin_access,管理员也会被拒:
{"errors":[{"message":"You don't have permission to access field \"admin_access\" in collection \"directus_roles\" or it does not exist. Queried in root.","extensions":{"code":"FORBIDDEN"}}]}
HTTP 403
安全字段查询正常:
curl -s "http://192.168.146.128:8103/roles?fields=id,name&limit=3" -H "Authorization: Bearer $TOKEN"
{"data":[{"id":"05a409d1-3cbb-4a92-b72b-596838eb08cb","name":"c1-viewer"},{"id":"337dfcdc-c532-494b-996c-8669fbc0e368","name":"Administrator"}]}
8.2 角色与 Policy(v12 新模型)
v12 的权限挂在 policy 上,policy 再关联到角色或用户(库表为 directus_access)。查角色时 policies 字段返回的是关联行 ID 而非 policy 本身(实测发现):
curl -s "http://192.168.146.128:8103/roles?fields=id,name,policies&limit=2" -H "Authorization: Bearer $TOKEN"
实测回显(45c68d98…、b135f9fe… 实为 directus_access 行 ID):
{"data":[{"id":"05a409d1-3cbb-4a92-b72b-596838eb08cb","name":"c1-viewer","policies":["45c68d98-dfd8-453a-84c1-71af80900c9e"]},{"id":"337dfcdc-c532-494b-996c-8669fbc0e368","name":"Administrator","policies":["b135f9fe-e8e3-4618-ad2e-a793f5c9a32c"]}]}
要拿真实 policy ID,用嵌套字段 policies.policy:
curl -s "http://192.168.146.128:8103/roles?fields=id,name,policies.policy&limit=2" -H "Authorization: Bearer $TOKEN"
实测回显:
{"data":[{"id":"05a409d1-3cbb-4a92-b72b-596838eb08cb","name":"c1-viewer","policies":[{"policy":"649260da-449a-49eb-8cf5-0ad87664f0b9"}]},{"id":"337dfcdc-c532-494b-996c-8669fbc0e368","name":"Administrator","policies":[{"policy":"5f57ab2d-d3c5-45bf-81c4-c66f2538abe0"}]}]}
policy 本身列表:
curl -s "http://192.168.146.128:8103/policies?fields=id,name&limit=5" -H "Authorization: Bearer $TOKEN"
实测回显:
{"data":[{"id":"5f57ab2d-d3c5-45bf-81c4-c66f2538abe0","name":"Administrator"},{"id":"649260da-449a-49eb-8cf5-0ad87664f0b9","name":"c1-viewer-policy"},{"id":"abf8a154-5b1c-4a46-ac9c-7300570f4f17","name":"$t:public_label"}]}
⚠️ 实测发现:通过 API 直接对角色写
policies别名字段(PATCH /roles/<id> 带 policies 数组)在本实例返回 403;本批权限实验最终以 directus_access 关联落库完成绑定并生效(见《配置参数详解》)。做权限自动化时建议直接以 /policies + 权限记录为操作对象。
8.3 权限边界实测
无权限的普通账号(viewer@example.com,仅 articles 只读)访问管理端点:
curl -s "http://192.168.146.128:8103/users?fields=id&limit=1" -H "Authorization: Bearer $VTOKEN" -w "\nHTTP %{http_code}\n"
实测回显:
{"errors":[{"message":"You don't have permission to access collection \"directus_users\" or it does not exist. Queried in root.","extensions":{"code":"FORBIDDEN"}}]}
HTTP 403
访问已授权集合正常:
{"data":[{"id":1,"title":"缓存实测第四版"}]}
HTTP 200
九、GraphQL 与 REST 对比
9.1 查询
curl -s -X POST http://192.168.146.128:8103/graphql \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"query":"{ articles(sort: \"id\", limit: 2) { id title status } }"}' \
-w "\nHTTP %{http_code}\n"
实测回显:
{"data":{"articles":[{"id":"1","title":"缓存实测第四版","status":"draft"},{"id":"2","title":"API 实测文章(已修订)","status":"published"}]}}
HTTP 200
⚠️ 实测发现两点:
sort参数必须传字符串(sort: "id"),传数组[id]会报"String cannot represent a non string value: id"(GRAPHQL_VALIDATION,400);- GraphQL 返回的
id是字符串("1"),REST 返回的是整数(1)——双端消费方做类型对接时注意。
9.2 变更(mutation)
curl -s -X POST http://192.168.146.128:8103/graphql \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"query":"mutation { create_articles_item(data: {title: \"GraphQL 创建的条目\", status: \"published\"}) { id title } }"}' \
-w "\nHTTP %{http_code}\n"
实测回显:
{"data":{"create_articles_item":{"id":"4","title":"GraphQL 创建的条目"}}}
HTTP 200
变更命名规则:create_<集合单数>_item / update_<集合单数>_item / delete_<集合单数>_item。实测该条目随后用 REST DELETE /items/articles/4 删除成功(204),证明 REST 与 GraphQL 写同一份数据。
9.3 权限裁剪 schema(重点差异)
无权限账号查询未授权集合时,GraphQL 不是返回 FORBIDDEN,而是该字段根本不出现在 schema 里:
curl -s -X POST http://192.168.146.128:8103/graphql \
-H "Authorization: Bearer $VTOKEN" -H 'Content-Type: application/json' \
-d '{"query":"{ users { id email } }"}' -w "\nHTTP %{http_code}\n"
实测回显:
{"errors":[{"message":"GraphQL validation error.","extensions":{"errors":[{"message":"Cannot query field \"users\" on type \"Query\".","locations":[{"line":1,"column":3}]}],"code":"GRAPHQL_VALIDATION"}}]}
HTTP 400
同一账号查询已授权集合正常:
{"data":{"articles":[{"id":"1","title":"缓存实测第四版"},{"id":"2","title":"API 实测文章(已修订)"},{"id":"4","title":"GraphQL 创建的条目"}]}}
HTTP 200
9.4 REST 与 GraphQL 选型
| 维度 | REST | GraphQL |
|---|---|---|
| 学习成本 | 低,URL + 查询参数 | 中,需学 schema/字段裁剪 |
| 取数灵活性 | fields 参数一级/嵌套 | 强(任意嵌套、别名、一次多资源) |
| 错误语义 | FORBIDDEN 403 | schema 级裁剪(无字段可查) |
| 类型细节 | id 按字段类型(整数) | id 统一字符串 |
| 缓存/网关 | URL 级缓存友好(可配 X-Data-Cache 头,见配置篇 8.3) | POST 为主,URL 缓存不适用 |
简单 CRUD/集成优先 REST;前端需要一次取多层关联数据时用 GraphQL。
十、分页/过滤/排序通用约定
REST 查询参数在 /items、/users、/files 等端点通用(/collections 实测对 limit 不生效,见第五章):
| 参数 | 形态 | 实测示例与结果 |
|---|---|---|
| fields | 逗号分隔,支持嵌套 policies.policy | fields=id,title 只返回指定列(6.2 实测) |
| filter | URL 编码 JSON,操作符 _eq/_neq/_in/_like/_gte/... | {"status":{"_eq":"published"}}(6.2 实测) |
| search | 关键词串 | search=第二篇 命中 title 含关键词的条目(6.2 实测) |
| sort | 逗号分隔字段,- 前缀倒序 | sort=-id(6.2 实测) |
| limit / offset | 整数 | limit=1&offset=1 翻页(6.2 实测) |
| meta | filter_count / result_count 等 | meta=filter_count 返回 {"filter_count":3}(6.3 实测) |
| aggregate | 对象式 | {"count":"*"} 返回 {"count":"3"}(6.3 实测) |
十一、常见问题
11.1 401 与 403 的判定
实测收集的错误码特征:INVALID_CREDENTIALS(401,口令错或 refresh_token 无效)、TOKEN_EXPIRED(401,JWT 超过 15 分钟——实测 viewer 令牌到期后出现 {"errors":[{"message":"Token expired.","extensions":{"code":"TOKEN_EXPIRED"}}]})、INVALID_TOKEN(403,签名校验不过,如 SECRET 轮换后的旧令牌,见配置篇 5.2)、FORBIDDEN(403,无权限或资源不存在)。处理:401 走重新登录或 refresh;403 检查权限或资源是否存在。
11.2 删除的条目查不到却报 403
Directus 对「不存在」与「无权限」统一返回 FORBIDDEN(6.4 实测),这是防探测设计。排障时先确认资源 ID 存在性(列表端点核对),再查权限。
11.3 refresh_token 为什么突然失效
refresh_token 是一次性轮换的(4.4 实测:旧值再刷立即 INVALID_CREDENTIALS)。并发场景两个请求同时刷新,后到者失败——客户端要原子地保存最新 refresh_token。
11.4 aggregate 报 500
?aggregate=count:* 简写在 12.5 实测返回 INTERNAL_SERVER_ERROR,改用对象式 ?aggregate={"count":"*"}(URL 编码后 %7B%22count%22%3A%22*%22%7D)。
11.5 GraphQL 的 sort 报 String cannot represent
sort 传数组会校验失败,必须传字符串(多字段用逗号分隔的字符串,如 "sort": "-date_created,id")。
十二、参考链接
- 《Directus Docker Compose 部署指南》——API 实例的部署
- 《Directus 12.5 配置参数详解》——ACCESS_TOKEN_TTL/CACHE/CORS 等参数实测
- 《Directus 12.5 介绍与选型指南》——产品定位与架构
- 官方 API 入门:https://directus.com/docs/getting-started/use-the-api
- 官方 REST API 参考:https://directus.com/docs/reference/api/introduction
- 官方 GraphQL 参考:https://directus.com/docs/reference/graphql
- 官方认证参考:https://directus.com/docs/reference/authentication