ResourceSpace 11.0 API 使用详解

ResourceSpace 11.0 REST API 实操指南:三步签名算法(源码实证)、可复用测试客户端、get_system_status/get_resource_types/create_resource/upload_file_by_url/update_field/create_collection/delete_resource 全链路实测回显、api_upload_urls 白名单负例正例、单文件挂载 inode 陷阱与 node 存储直查等排障实录。

ResourceSpace数字资产管理文件管理PHP2026/11/01

ResourceSpace 11.0 API 使用详解

目标:讲清 ResourceSpace 11.0 REST API 的认证签名机制、调用方法与常用端点实操。本文所有请求、响应与排障过程均在下述环境中实测完成,签名算法与关键行为均有源码依据。

一、环境信息

  • 操作系统:Ubuntu 24.04.5 LTS(x86_64),宿主机 IP 192.168.1.172
  • ResourceSpace:11.0(SVN releases/11.0 revision 30001),部署于 Docker Compose(应用 8132→80 端口)
  • API 入口:http://192.168.1.172:8132/api/index.php
  • 测试账号:admin(user ref=1)/ q1w2e3r4
  • $api_scramble_key:070147602f0038f487645ac82186c134ad0adb55fd74a606de30c3e0fd8777df(安装向导生成于 include/config.php)
  • 测试客户端:Python 3.12(仅标准库 urllib + hashlib)

二、前置条件

  • $enable_remote_apis = true(config.default.php 2148 行,默认开启);置 false 时仅浏览器 native 模式白名单函数可用
  • 知道一个具备相应权限的用户用户名及其 user ref(数据库 user 表 ref 列)
  • $api_scramble_key(来自 config.php)
  • 需要上传文件的场景:在 config.php 配置 $api_upload_urls 主机白名单(见 5.4)

三、API 总览

端点唯一入口 /api/index.php,通过 function 参数路由到 76 个 api_* 绑定函数(定义于 include/api_bindings.php)。常用函数分类:

分类函数
系统get_system_status、get_resource_types
资源查询get_resource_field_data、do_search、get_resource_path
资源写入create_resource、update_field、upload_file_by_url、delete_resource
集合create_collection、add_resource_to_collection、delete_resources_in_collection
用户/字段管理create_user、create_resource_type_field 等

参数传递:param1、param2…(按函数签名顺序)或直接用参数名。响应为 JSON;GET 请求可加 pretty=true 美化。

四、认证与首次调用

4.1 签名算法(源码实证)

摘自 include/api_functions.php 的 get_api_key() 与 check_api_key():

function get_api_key($user) {
    global $api_scramble_key;
    return hash("sha256", $user . $api_scramble_key);   // $user 是 user ref(ID)
}
// 校验:
$expected = hash("sha256", $userkey . $querystring);

三步签名:

  1. api_key = sha256(user_ref + api_scramble_key)
  2. 构造查询串(不含 sign;authmode/pretty 若传则不参与签名,服务端会剔除后校验)
  3. sign = sha256(api_key + querystring)

最终 URL:/api/index.php?<querystring>&sign=<sign>,查询串中必须带 function 与 user(用户名,非 ref)。

4.2 首次调用:get_system_status

$ python3 api_test.py get_system_status
HTTP 200
{"results":{"required_php_modules":{"status":"FAIL","info":"Missing PHP modules: apcu","severity":0,"severity_text":"Critical"}},"status":"FAIL"}

实测环境镜像未装 apcu 扩展,状态检查如实报 FAIL(severity 0=Critical)——这也是部署自检的实用工具。注意返回的是系统自检状态而非调用失败。

4.3 测试客户端(可直接复用)

#!/usr/bin/env python3
# api_test.py — ResourceSpace API 签名调用客户端
import hashlib, sys, urllib.request, urllib.parse

BASE = "http://192.168.1.172:8132/api/index.php?"
USERNAME = "admin"
USER_REF = "1"   # user 表 ref,非用户名
API_SCRAMBLE_KEY = "0701...(你的 $api_scramble_key)"

def call(params: dict):
    params = {"user": USERNAME, **params}
    qs = urllib.parse.urlencode(params)
    api_key = hashlib.sha256((USER_REF + API_SCRAMBLE_KEY).encode()).hexdigest()
    sign = hashlib.sha256((api_key + qs).encode()).hexdigest()
    with urllib.request.urlopen(BASE + qs + "&sign=" + sign, timeout=120) as r:
        print("HTTP", r.status); print(r.read().decode()[:4000])

if __name__ == "__main__":
    fn = sys.argv[1]
    params = {"function": fn}
    for kv in sys.argv[2:]:
        k, v = kv.split("=", 1); params[k] = v
    call(params)

实测排障:最初版本漏传 user 参数、并误把 user ref 填进 user 字段,服务端一律返回 HTTP 401 Invalid signature。签名校验对查询串逐字符敏感,参数顺序与内容必须与签名时完全一致。

五、各 API 域详解(实测)

5.1 查询资源类型与字段

$ python3 api_test.py get_resource_types
HTTP 200
[{"ref":"1","name":"Photo",...,"resource_type_fields":["52","54","84","85","86","87"]},
 {"ref":"2","name":"Document",...},{"ref":"3","name":"Video",...},{"ref":"4","name":"Audio",...}]

$ python3 api_test.py get_resource_field_data resource=2
HTTP 200
[{"resource_type_field":8,"name":"title","type":0,"value":"CLI Direct Test",...},
 {"resource_type_field":18,"name":"caption","type":1,"value":"Uploaded via REST API",...}, ...]

字段 ref 与名称对照:8=title(文本)、9=extract、18=caption、51=originalfilename、73/74/75=关键词类字段。数据库直查对照:select ref,name,type from resource_type_field;

5.2 创建资源

$ python3 api_test.py create_resource resource_type=1
HTTP 200
2            # 返回新资源 ref

create_resource 签名:($resource_type, $archive = 999, $url = "", $no_exif = false, $revert = false, $autorotate = false, $metadata = "")。只建记录不传文件;archive 传 999 表示用系统默认状态。

5.3 通过 URL 上传文件(白名单机制实测)

upload_file_by_url 要求源站主机名在 $api_upload_urls 白名单内(api_validate_upload_url():未设置该变量时放行所有主机;设置为空数组则全部拒绝)。实测两步:

# 负例:config.php 中 $api_upload_urls = array();(空数组)
$ python3 api_test.py upload_file_by_url ref=2 url='http://192.168.1.172:8152/test.png'
HTTP 200
false                       # 直接拒绝,无错误信息

# config.php 改为 $api_upload_urls = array('192.168.1.172'); 并重建容器后
$ python3 api_test.py upload_file_by_url ref=2 url='http://192.168.1.172:8152/test.png'
HTTP 200
"2"                         # 成功,返回资源 ref

上传成功后文件落盘到 filestore(evenspread 目录结构):

/var/www/html/filestore/2_<hash10>/2_<hash10>.png

重要行为实测:上传会触发元数据提取流程,可能覆盖此前通过 update_field 写入的字段值(实测 PNG 无 title 元数据,先前写入的 title 被清空)。推荐顺序:create_resource → upload_file_by_url → update_field。

Docker 单文件挂载排障实录:修改 config.php 白名单时用了 sed -i,它以"写新文件+rename"方式替换文件、inode 变化,导致单文件 bind mount 失联——容器内仍是旧配置(宿主 inode 6684846 vs 容器 6946838),白名单修改不生效。验证方法:docker exec <app> grep api_upload_urls /var/www/html/include/config.php 与宿主 ls -i 对比 inode。修复:docker compose up -d --force-recreate app;预防:改宿主配置文件用 cat > file 原地写入(保 inode)或每次改完重建容器。

5.4 更新字段

$ python3 api_test.py update_field resource=2 field=18 value="Uploaded via REST API"
HTTP 200
true

field 可传 ref(8/18/51…)或字段名("title")。字段值的真实存储是 node 体系(11.0 全字段经 node):验证写入用数据库直查时查 node 与 resource_node 两表:

$ docker exec resourcespace-11.0-db mariadb -uroot -p*** --batch resourcespace \
  -e "select ref,resource_type_field,name from node where resource_type_field in (8,18);
      select resource,node from resource_node where resource=2"
ref  resource_type_field  name
283  8                    CLI Direct Test
...

实测排障:核对 resource_node 时误用列名 node_ref(实际列名是 node),SQL 报错被 2>/dev/null 吞掉、输出为空,险些误判为"关联丢失"。直查时先 describe resource_node; 确认列名。

5.5 搜索与下载路径

$ python3 api_test.py do_search search="API Test" fetchrows=5
HTTP 200
[]

do_search 签名 ($search, $restypes = "", $order_by = "", $offset = 0, $fetchrows = -1, ...)。实测提示:搜索结果依赖关键词索引与后台任务状态,fetchrows 限制返回行数。本实测环境中资源新建后立即搜索返回空——关键词索引/预览生成由 batch/cron.php 驱动(docker exec resourcespace-11.0-app php /var/www/html/batch/cron.php 手动触发),生产环境配置周期 cron 后再验证。get_resource_path(resource=2&getfilepath=true)同理依赖预览文件就绪,预览未生成时返回空串。

5.6 集合操作与删除

$ python3 api_test.py create_collection name="API Test Collection" forupload=false
HTTP 200
3                                    # 返回集合 ref
$ python3 api_test.py add_resource_to_collection resource=2 collection=3
HTTP 200
(成功返回确认信息)
$ python3 api_test.py delete_resource resource=2
HTTP 200
true

六、分页/过滤/排序约定

  • 分页:do_search 用 $offset + $fetchrows;fetchrows=-1 表示全部
  • 排序:order_by 传字段 ref 或 relevance、title、creation_date 等
  • 过滤:restypes 逗号分隔资源类型 ref;搜索串支持 RS 检索语法(引号短语、AND/OR)
  • pretty=true 美化 JSON,不参与签名

七、常见问题

Q1:HTTP 401 "Invalid signature"?

按频次排查:① user 参数没传或传了 user ref(必须传用户名,签名里的 api_key 才用 ref);② 参与签名的查询串与实际请求不一致(参数顺序、URL 编码差异);③ sign/authmode/pretty 被计入签名(服务端会剔除这三个,客户端不要签它们)。

Q2:upload_file_by_url 返回 false?

URL 主机不在 $api_upload_urls 白名单(空数组=全拒绝,未设置=全放行,见 5.3)。改 config.php 后记得单文件 bind mount 的 inode 陷阱(5.3 排障实录)。

Q3:update_field 返回 true 但前台/搜索没变化?

值已写入 node 体系(用 5.4 的 SQL 验证),但关键词索引与 resource 表搜索列的刷新由后台任务承担。跑一次 docker exec <app> php /var/www/html/batch/cron.php 或等待周期任务;另注意上传文件会触发元数据提取覆盖字段(5.3 行为提示),写字段放在上传之后。

Q4:哪些函数不可用?

$enable_remote_apis=false 时所有 userkey 签名调用被拒(HTTP 403 "API not enabled.");插件可能注册额外 api_* 绑定,实际可用清单以 include/api_bindings.php 与所装插件为准。

八、参考链接

ResourceSpace 11.0 API 使用详解 - Quzuo