XWiki 18.8 API 使用详解

XWiki 18.8.0 REST API 使用详解:/rest 端点形态与 Basic 认证、页面/附件/搜索/对象/标签全域 curl 逐条实测、multipart 附件 CSRF form_token 踩坑实录、401/403/404/405/500 错误码对照与 JVM 堆内存核验。

XWikiXWikiWiki知识库2026/10/22

XWiki 18.8 API 使用详解

目标:面向需要通过 API 对 XWiki 18.8.0 做二次开发、内容批量维护与自动化运维的读者,基于已部署的 XWiki 18.8.0(Apache Tomcat 10.1.61 + PostgreSQL 16,服务端口 8148)实测环境,逐域实测 REST API 的认证方式与页面、附件、搜索、对象、标签等核心端点,沉淀真实请求与响应回显,并给出错误码对照、排障实录与 JVM 进程核验方法。本文所有请求与响应均用 curl 在下述环境逐条实测,逐字照抄。

一、环境信息

  • 操作系统:Ubuntu 22.04.4 LTS
  • XWiki:18.8.0(响应头 XWiki-Version: 18.8.0 实测;应用服务器 Apache Tomcat 10.1.61,运行时 OpenJDK 21)
  • 数据库:PostgreSQL 16.15
  • 部署形态:《XWiki 18.8 Docker Compose 部署指南》 完成的单机部署,服务监听 8148 端口(HTTP)
  • API 形态:XWiki RESTful API(随平台内置,无需额外安装组件);默认表示格式 XML,可选 JSON
  • 实测工具:curl 7.81.0(服务器本机直连 http://localhost:8148)
  • 实测管理员账号:admin / q1w2e3r4

二、前置条件

2.1 服务可达与 REST 端点形态

本章确认要做的三件事:服务是否存活、REST 入口前缀是什么、认证是否可用。XWiki 18.8 的 REST API 随平台内置,路径规则是「上下文路径 + /rest + 资源路径」;本文环境按 ROOT 上下文部署(xwiki.webapppath 为空,见 《XWiki 18.8 配置参数详解》第 6.4 节),所以 REST 前缀是 /rest:

# 服务健康自检(302 重定向到首页为正常)
curl -s -o /dev/null -w '%{http_code} -> %{redirect_url}\n' http://localhost:8148/

实测回显:

302 -> http://localhost:8148/bin/view/Main/
# 带上下文前缀的 REST 入口(ROOT 部署下预期 404)
curl -s -o /dev/null -w '%{http_code}\n' http://localhost:8148/xwiki/rest/wikis
# 正确前缀
curl -s -o /dev/null -w '%{http_code}\n' http://localhost:8148/rest/wikis

实测回显:

404
200

💡 说明:若把 XWiki 以 war 包部署在 /xwiki 上下文(或部署时设置 CONTEXT_PATH=xwiki),REST 前缀相应变为 /xwiki/rest。两种形态的资源路径部分完全相同,本文统一以实测环境的 /rest 书写。

2.2 认证方式

XWiki REST 支持三种认证:匿名、HTTP Basic、会话 Cookie(JSESSIONID)。运维脚本与二次开发推荐 Basic(无状态、不依赖会话)。写操作的 CSRF Token 要求有一个重要例外——multipart 附件上传必须额外携带 XWiki-Form-Token 请求头(第七章实测),其余 XML/JSON 形态的写请求 Basic 认证即可。

2.3 客户端工具

  • curl(本文全部示例工具);
  • jq(处理 JSON 表示时可选用);
  • 浏览器可直接访问 GET 类端点查看 XML。

三、API 总览

3.1 协议与表示格式

REST 资源按 wikis → spaces → pages →(history/attachments/objects/tags/children/translations) 层级组织,HTTP 动词语义:GET 读取、PUT 创建或更新(幂等)、POST 仅用于少量子资源创建(页面对象、multipart 附件)、DELETE 删除。默认返回 XML(application/xml;charset=UTF-8),请求头带 Accept: application/json 可得 JSON(实测 application/json;charset=UTF-8)。

3.2 版本

REST 模块随平台内置发布,无独立版本端点;版本以认证后任意响应的 XWiki-Version 头为准,实测为 18.8.0(见 4.4 节响应头全文)。

3.3 认证与限流约定

  • Basic 认证全程可用;错误口令的请求在应用日志留 WARN 记录(第十二章实录)。
  • XWiki 默认对 REST 无内置限流(实测连续数十次请求无 429),生产环境如需限流应在反向代理层配置。
  • 写操作并发:同一页面的并发写按「编辑锁 + 版本号递增」处理,REST 侧每次成功写操作都会生成一个新版本(6.5 节实测 1.1 → 8.1)。

3.4 资源与方法总览(本文全部实测)

资源端点(前缀 /rest)方法用途实测结果
/wikisGET列出全部 wiki200
/wikis/{wiki}GET单 wiki 资源与关联链接200
/wikis/{wiki}/userGET当前认证身份200
/wikis/{wiki}/spacesGET空间列表200
/wikis/{wiki}/spaces/{space}/pagesGET空间内页面列表200
/wikis/{wiki}/pagesGET全站页面列表(支持 number/start)200
/wikis/{wiki}/spaces/{space}/pages/{page}GET/PUT/DELETE读 / 创建或更新 / 删除页面200/201/202/204
/wikis/{wiki}/spaces/{space}/pages/{page}/historyGET页面版本历史200
/wikis/{wiki}/spaces/{space}/pages/{page}/attachmentsGET/POST附件列表 / multipart 上传200/201
/wikis/{wiki}/spaces/{space}/pages/{page}/attachments/{filename}GET/PUT/DELETE附件内容回读 / 原始体上传 / 删除200/202/204
/wikis/{wiki}/spaces/{space}/pages/{page}/objectsGET/POST对象列表 / 新建对象200/201
/wikis/{wiki}/spaces/{space}/pages/{page}/tagsGET/PUT读取 / 覆盖设置标签200/202
/wikis/{wiki}/search?query=GET站内搜索200
/wikis/{wiki}/query?q=GET旧查询插件入口200(本环境返回空集合)
/wikis/{wiki}/classesGET类定义列表200
/wikis/{wiki}/classes/{class}GET单个类及其属性200
/wikis/{wiki}/modificationsGET全站最近修改200

⚠️ 注意:页面子资源的正确路径形态是 /spaces/{space}/pages/{page}。旧文档常见的 /pages/{space}/{page} 写法不匹配资源模板,会得到 Tomcat 层 404(6.2 节实录)。

四、认证与首次调用

4.1 健康检查

见 2.1 节:/ 返回 302 跳转 /bin/view/Main/,/rest/wikis 返回 200 即为就绪。巡检脚本建议以这两个状态码做探活。

4.2 匿名身份探测

不带认证调用身份端点,确认当前匿名身份:

curl -s http://localhost:8148/rest/wikis/xwiki/user

实测回显:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?><user xmlns:xwiki="http://www.xwiki.org" xmlns="http://www.xwiki.org/user"><id>XWiki.XWikiGuest</id><firstName>Guest</firstName><avatarUrl>http://localhost:8148/resources/icons/xwiki/noavatar.png?cache-version=1790683364000</avatarUrl><global>true</global><displayName>Unknown User</displayName><company></company><about></company><email></email><phone></phone><address></address><blog></blog><blogFeed></blogFeed></user>

(displayName 等空标签为原始回显保留。)匿名身份为 XWiki.XWikiGuest,只读公开内容。

4.3 Basic 认证身份

curl -s -u admin:q1w2e3r4 http://localhost:8148/rest/wikis/xwiki/user

实测回显:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?><user xmlns:xwiki="http://www.xwiki.org" xmlns="http://www.xwiki.org/user"><xwiki:link href="http://localhost:8148/rest/wikis/xwiki/spaces/XWiki/pages/admin" rel="http://www.xwiki.org/rel/page"/><xwiki:link href="http://localhost:8148/rest/wikis/xwiki/spaces/XWiki/pages/admin/history" rel="http://www.xwiki.org/rel/history"/><id>xwiki:XWiki.admin</id><avatarUrl>http://localhost:8148/resources/icons/xwiki/noavatar.png?cache-version=1790683364000</avatarUrl><global>true</global><xwikiRelativeUrl>http://localhost:8148/bin/view/XWiki/admin</xwikiRelativeUrl><xwikiAbsoluteUrl>http://localhost:8148/bin/view/XWiki/admin</xwikiAbsoluteUrl><displayName>admin</displayName><company></company><about></company><email>admin@example.com</email><phone></phone><address></address><blog></blog><blogFeed></blogFeed></user>

身份变为 xwiki:XWiki.admin(前缀为 wiki 名),说明 Basic 认证生效。

4.4 响应头速览(含版本与 form_token)

curl -sI -u admin:q1w2e3r4 http://localhost:8148/rest/wikis

实测回显:

HTTP/1.1 200
Content-Script-Type: text/javascript
Set-Cookie: JSESSIONID=6A80DE1D3C9B8F6976204003E5CBD616; Path=/; HttpOnly
XWiki-User: xwiki:XWiki.admin
XWiki-Version: 18.8.0
XWiki-Form-Token: 0Whzdzxgi7QenAAicoebIw
Content-Type: application/xml;charset=UTF-8
Content-Language: en
Content-Length: 738
Date: Thu, 22 Oct 2026 02:05:06 GMT

逐头解读:XWiki-User 确认认证身份;XWiki-Version 是版本取证依据;XWiki-Form-Token 是会话级 CSRF 令牌——multipart 附件上传必须原样带回(第七章);Set-Cookie 的 JSESSIONID 如需会话方式调用可自行维护,脚本场景用 Basic 可忽略。

五、Wiki 与空间域

5.1 wiki 列表

curl -s http://localhost:8148/rest/wikis

实测回显(匿名可读):

<?xml version="1.0" encoding="UTF-8" standalone="yes"?><wikis xmlns="http://www.xwiki.org"><link href="http://localhost:8148/rest/wikis/query" rel="http://www.xwiki.org/rel/query"/><wiki><link href="http://localhost:8148/rest/wikis/xwiki/spaces" rel="http://www.xwiki.org/rel/spaces"/><link href="http://localhost:8148/rest/wikis/xwiki/classes" rel="http://www.xwiki.org/rel/classes"/><link href="http://localhost:8148/rest/wikis/xwiki/modifications" rel="http://www.xwiki.org/rel/modifications"/><link href="http://localhost:8148/rest/wikis/xwiki/search" rel="http://www.xwiki.org/rel/search"/><link href="http://localhost:8148/rest/wikis/xwiki/query" rel="http://www.xwiki.org/rel/query"/><id>xwiki</id><name>xwiki</name></wiki></wikis>

只有一个主 wiki xwiki(farmfarm 模式未启子 wiki 时属正常)。<link rel=.../> 是超媒体链接,指明该 wiki 下可继续访问的子资源,可直接顺着调用。

5.2 单 wiki 资源

curl -s -u admin:q1w2e3r4 http://localhost:8148/rest/wikis/xwiki

实测回显(节选,保留起始结构):

<?xml version="1.0" encoding="UTF-8" standalone="yes"?><wiki xmlns="http://www.xwiki.org"><link href="http://localhost:8148/rest/wikis/xwiki/spaces" rel="http://www.xwiki.org/rel/spaces"/><link href="http://localhost:8148/rest/wikis/xwiki/classes" rel="http://www.xwiki.org/rel/classes"/><link href="http://localhost:8148/rest/wikis/xwiki/modifications" rel="http://www.xwiki.org/rel/modifications"/>

(输出为节选,后续为描述与更多链接元素。)

5.3 空间列表

curl -s -u admin:q1w2e3r4 http://localhost:8148/rest/wikis/xwiki/spaces

实测回显(节选,第一个空间):

<?xml version="1.0" encoding="UTF-8" standalone="yes"?><spaces xmlns="http://www.xwiki.org"><space><link href="http://localhost:8148/rest/wikis/xwiki/spaces/Help/pages" rel="http://www.xwiki.org/rel/pages"/><link href="http://localhost:8148/rest/wikis/xwiki/spaces/Help/pages/WebHome" rel="http://www.xwiki.org/rel/home"/><link href="http://localhost:8148/rest/wikis/xwiki/spaces/Help/search" rel="http://www.xwiki.org/rel/search"/><id>xwiki:Help</id><wiki>xwiki</wiki><name>Help</name><home>xwiki:Help.WebHome</home><xwikiRelativeUrl>http://localhost:8148/bin/view/Help/</xwikiRelativeUrl>

(输出为节选;标准 flavor 自带 Help、Sandbox、Main、XWiki 等空间,后续元素以此类推。)

5.4 空间内页面列表

curl -s -u admin:q1w2e3r4 'http://localhost:8148/rest/wikis/xwiki/spaces/Help/pages?number=2'

实测回显(节选,number=2 只返回 2 条):

<pageSummary><link href="http://localhost:8148/rest/wikis/xwiki/spaces/Help/pages/WebHome" rel="http://www.xwiki.org/rel/page"/><id>xwiki:Help.WebHome</id><fullName>Help.WebHome</fullName><wiki>xwiki</wiki><space>Help</space><name>WebHome</name>

(输出为节选;fullName 命名规则为 空间.页面名。)

六、页面域

6.1 全站页面列表与分页

分页参数是 number(页大小)与 start(起始下标)。实测 number=2&start=0 恰好返回 2 条:

curl -s -u admin:q1w2e3r4 'http://localhost:8148/rest/wikis/xwiki/pages?number=2&start=0' | grep -o '<pageSummary>' | wc -l

实测回显:

2

⚠️ 注意:limit/offset 参数不生效(实测 ?limit=3 返回了远超 3 条的完整列表)。XWiki REST 的分页参数固定为 number/start,写脚本时不要套用其他产品的习惯参数。

6.2 获取单页

curl -s -u admin:q1w2e3r4 http://localhost:8148/rest/wikis/xwiki/spaces/Main/pages/WebHome

实测回显(节选,保留主要字段):

<?xml version="1.0" encoding="UTF-8" standalone="yes"?><page xmlns="http://www.xwiki.org"><link href="http://localhost:8148/rest/wikis/xwiki/spaces/Main/pages/WebHome/history" rel="http://www.xwiki.org/rel/history"/><link href="http://localhost:8148/rest/wikis/xwiki/spaces/Main/pages/WebHome/children" rel="http://www.xwiki.org/rel/children"/><link href="http://localhost:8148/rest/syntaxes" rel="http://www.xwiki.org/rel/syntaxes"/><link href="http://localhost:8148/rest/wikis/xwiki/spaces/Main/pages/WebHome" rel="self"/><id>xwiki:Main.WebHome</id><fullName>Main.WebHome</fullName><wiki>xwiki</wiki><space>Main</space><name>WebHome</name><title>Home</title><rawTitle>Home</rawTitle><parent></parent><parentId></parentId><version>1.1</version><author>XWiki.Admin</author><xwikiRelativeUrl>http://localhost:8148/bin/view/Main/</xwikiRelativeUrl><xwikiAbsoluteUrl>http://localhost:8148/bin/view/Main/</xwikiAbsoluteUrl><translations default="en"><translation language="en"><link href="http://localhost:8148/rest/wikis/xwiki/spaces/Main/pages/WebHome" rel="http://www.xwiki.org/rel/page"/><link href="http://localhost:8148/rest/wikis/xwiki/spaces/Main/pages/WebHome/history" rel="http://www.xwiki.org/rel/history"/></translation>

(输出为节选;后续为 no/de 等语言翻译变体链接与正文 content 元素。)字段解读:version 当前版本、author 最后编辑者、translations 列出该页全部语言变体;content 元素为 XWiki/2.1 语法源码,批量迁移内容时读它即可。

⚠️ 注意:/rest/wikis/xwiki/pages/Main/WebHome 这种「page 集合下拼路径」的旧写法返回 Tomcat 404 错误页(实测 HTML 错误页,Apache Tomcat/10.1.61 页脚),务必使用 /spaces/{space}/pages/{page} 形态。

JSON 表示(Accept: application/json):

curl -s -u admin:q1w2e3r4 -H 'Accept: application/json' \
  http://localhost:8148/rest/wikis/xwiki/spaces/Sandbox/pages/APITestPage \
  -o /dev/null -w '%{http_code} %{content_type}\n'

实测回显:

200 application/json;charset=UTF-8

JSON 响应体(节选,起始 300 字节):

{"links":[{"href":"http://localhost:8148/rest/wikis/xwiki/spaces/Sandbox","rel":"http://www.xwiki.org/rel/space","type":null,"hrefLang":null},{"href":"http://localhost:8148/rest/wikis/xwiki/spaces/Sandbox/pages/APITestPage/history","rel":"http://www.xwiki.org/rel/history","type":null,"hrefLang":null

(输出为节选。同一资源在 XML/JSON 两种表示间字段名一致,按 Accept 头切换即可。)

6.3 创建页面(PUT)

PUT 对不存在的页面即创建,返回 201:

curl -s -u admin:q1w2e3r4 -X PUT -H 'Content-Type: application/xml' \
  -d '<?xml version="1.0" encoding="UTF-8"?><page xmlns="http://www.xwiki.org"><title>API 测试页</title><content>这是 REST API 创建的测试内容 v1。</content></page>' \
  http://localhost:8148/rest/wikis/xwiki/spaces/Sandbox/pages/APITestPage \
  -o /dev/null -w '%{http_code}\n'

实测回显:

201

创建后立即 GET 可见(实测回显节选):

<id>xwiki:Sandbox.APITestPage</id><fullName>Sandbox.APITestPage</fullName><wiki>xwiki</wiki><space>Sandbox</space><name>APITestPage</name><title>API 测试页</title><rawTitle>API 测试页</rawTitle><parent></parent><parentId></parentId><version>1.1</version><author

中文标题与中文正文均正常落库,无需额外编码处理。

6.4 更新页面内容(PUT)

PUT 对已存在页面即更新,返回 202(Accepted,异步落库语义):

curl -s -u admin:q1w2e3r4 -X PUT -H 'Content-Type: application/xml' \
  -d '<?xml version="1.0" encoding="UTF-8"?><page xmlns="http://www.xwiki.org"><content>这是 REST API 更新后的内容 v2。</content></page>' \
  http://localhost:8148/rest/wikis/xwiki/spaces/Sandbox/pages/APITestPage \
  -o /dev/null -w '%{http_code}\n'

实测回显:

202

回读确认版本与内容(实测回显):

<version>2.1
<author>XWiki.admin
<content>这是 REST API 更新后的内容 v2。

版本号从 1.1 推进到 2.1(大版本.小版本,小版本在 REST 内容更新下同样递增),编辑者为认证账号。> 💡 说明:PUT 请求体中未给出的字段(如 title)保持原值不变,按需给出字段即可增量更新。

6.5 页面版本历史

curl -s -u admin:q1w2e3r4 http://localhost:8148/rest/wikis/xwiki/spaces/Sandbox/pages/APITestPage/history | grep -o '<version>[^<]*'

实测回显(该测试页删除前,历经创建、两次内容更新、两次标签 PUT、评论对象 POST、两次附件写后共 8 个版本):

<version>8.1
<version>7.1
<version>6.1
<version>5.1
<version>4.1
<version>3.1
<version>2.1
<version>1.1

结论:REST 的每一类写操作(内容、标签、对象、附件)都会生成新版本,做批量脚本前要预估版本膨胀;版本清理策略见 《XWiki 18.8 配置参数详解》第 6.3 节的存储域参数。

6.6 POST 不能建页(405 实测)

XWiki REST 没有提供 POST 建页方法,对集合资源 POST 会得到 405:

curl -s -u admin:q1w2e3r4 -X POST -H 'Content-Type: application/xml' \
  -d '<page xmlns="http://www.xwiki.org"><name>APITestPage</name></page>' \
  http://localhost:8148/rest/wikis/xwiki/spaces/Sandbox/pages \
  -o /dev/null -w '%{http_code}\n'
curl -s -u admin:q1w2e3r4 -X POST http://localhost:8148/rest/wikis/xwiki/pages -o /dev/null -w '%{http_code}\n'

实测回显:

405
405

💡 说明:这与 MediaWiki 的 action=edit API 习惯不同——XWiki 一律用 PUT 在目标 URL 上「创建或更新」,天然幂等,重复执行不会产生重复页面,脚本重试更安全。

6.7 删除页面

curl -s -u admin:q1w2e3r4 -X DELETE -o /dev/null -w '%{http_code}\n' \
  http://localhost:8148/rest/wikis/xwiki/spaces/Sandbox/pages/APITestPage
curl -s -u admin:q1w2e3r4 -o /dev/null -w '%{http_code}\n' \
  http://localhost:8148/rest/wikis/xwiki/spaces/Sandbox/pages/APITestPage

实测回显:

204
404

DELETE 返回 204,再次 GET 即 404,删除即时生效。被删页面进入站内回收站(18.8 默认开启),恢复只能从 Web 界面回收站操作,REST 未提供恢复端点——批量删除前建议先导出页面 XML 备份。

七、附件域

7.1 multipart 上传:403 踩坑与 form_token 解法

先用最直觉的方式上传(multipart 表单 + Basic 认证):

curl -s -u admin:q1w2e3r4 -X POST -F 'file=@/opt/data/quzuo/tmp/xwiki/api-test-note.txt' \
  -w '%{http_code}\n' \
  http://localhost:8148/rest/wikis/xwiki/spaces/Sandbox/pages/APITestPage/attachments

实测回显:

403

排查应用日志 /usr/local/xwiki/data/logs/xwiki.log,找到对应的 WARN(逐字照抄):

2026-10-22 02:08:01,838 [http-nio-8080-exec-2 - http://localhost:8148/rest/wikis/xwiki/spaces/Sandbox/pages/APITestPage/attachments] WARN  o.x.c.i.DefaultCSRFToken       - Secret CSRF token verification failed (token: [null], stored token: [0Whzdzxgi7QenAAicoebIw])

根因:multipart/form-data 形态的请求即使走 Basic 认证,也会被 CSRF 过滤器拦截(过滤器对表单类请求强制校验 form_token,而该请求未携带)。把 4.4 节响应头里的 XWiki-Form-Token 值原样放进请求头重试:

TOKEN=$(curl -sI -u admin:q1w2e3r4 http://localhost:8148/rest/wikis | grep -i 'XWiki-Form-Token' | tr -d '\r' | awk '{print $2}')
curl -s -u admin:q1w2e3r4 -X POST -F 'file=@/opt/data/quzuo/tmp/xwiki/api-test-note.txt' \
  -H "XWiki-Form-Token: $TOKEN" \
  -w '%{http_code}\n' \
  http://localhost:8148/rest/wikis/xwiki/spaces/Sandbox/pages/APITestPage/attachments

实测回显:

201

⚠️ 注意:实测把 form_token 放在 URL 查询串或表单字段里都仍然 403(日志同样报 token: [null],REST 服务侧不解析这两种形态),只有请求头 XWiki-Form-Token 有效。token 是会话级值,脚本每次运行时先发一个认证请求取头即可,无需手工维护。

7.2 原始体上传(PUT,推荐脚本使用)

REST 同时提供非 multipart 的附件写法:对附件 URL 直接 PUT 原始字节流,免 token、免 multipart 组包:

curl -s -u admin:q1w2e3r4 -X PUT -H 'Content-Type: text/plain' \
  --data-binary @/opt/data/quzuo/tmp/xwiki/api-test-note.txt \
  http://localhost:8148/rest/wikis/xwiki/spaces/Sandbox/pages/APITestPage/attachments/api-test-note.txt

实测回显(节选):

<id>xwiki:Sandbox.APITestPage@api-test-note.txt
<name>api-test-note.txt
<size>51
<version>1.2
<mimeType>text/plain
<author>XWiki.admin

(输出为节选;此时 7.1 的 POST 已建出版本 1.1,PUT 同名附件即覆盖,版本推进到 1.2,返回 202。)对同一附件重复 PUT 生成新版本,与页面版本化规则一致。

7.3 附件列表、内容回读与删除

# 列表
curl -s -u admin:q1w2e3r4 http://localhost:8148/rest/wikis/xwiki/spaces/Sandbox/pages/APITestPage/attachments | grep -o '<\(filename\|mimeType\|size\|version\)>[^<]*'
# 内容回读(字节级一致)
curl -s -u admin:q1w2e3r4 http://localhost:8148/rest/wikis/xwiki/spaces/Sandbox/pages/APITestPage/attachments/api-test-note.txt
# 删除
curl -s -u admin:q1w2e3r4 -X DELETE -o /dev/null -w '%{http_code}\n' \
  http://localhost:8148/rest/wikis/xwiki/spaces/Sandbox/pages/APITestPage/attachments/api-test-note.txt

实测回显(三段依序):

<size>51
<version>1.2
<mimeType>text/plain
XWiki REST API 附件上传实测内容 2026-10-22
204

删除后再次请求附件列表返回空集合,内容回读 404,闭环成立。

八、搜索与查询域

8.1 站内搜索 /search

curl -s -u admin:q1w2e3r4 'http://localhost:8148/rest/wikis/xwiki/search?query=REST%20API' | grep -o '<\(searchResult\|id\|pageFullName\)>[^<]*'

实测回显(节选):

<searchResult>
<id>xwiki:Sandbox.APITestPage
<pageFullName>Sandbox.APITestPage
<searchResult>
<id>xwiki:Main.ConfigParamTest
<pageFullName>Main.ConfigParamTest

(grep 按 < 截断元素,行尾为原文缺失部分;命中两条结果:REST 创建的测试页与此前配置实验页。)query 参数做全文检索,命中标题与正文;number/start 同样可用于结果分页。

8.2 旧查询入口 /query(本环境返回空集合实录)

/rest/wikis/{wiki}/query?q= 是旧查询插件(Query Plugin/Query Manager)的 REST 入口。响应自带调用模板,但实测三种常用语法均返回空结果集合:

curl -s -u admin:q1w2e3r4 'http://localhost:8148/rest/wikis/xwiki/query?q=title%3AHome'

实测回显(逐字,全文即为一个空集合 + 模板声明):

<?xml version="1.0" encoding="UTF-8" standalone="yes"?><searchResults xmlns="http://www.xwiki.org" template="http://localhost:8148/rest/wikis/xwiki/query?q={query}(&amp;type={type})(&amp;number={number})(&amp;start={start})(&amp;orderField={fieldname}(&amp;order={asc|desc}))(&amp;distinct=1)(&amp;prettyNames={false|true})(&amp;wikis={wikis})(&amp;className={classname})"/>

q=name:APITestPage、q=object:XWiki.XWikiUsers 两种语法实测同样返回该空集合。结论:该端点依赖的旧查询插件在标准 flavor 的 18.8.0 中无可用查询实现,生产脚本一律使用 8.1 节的 /search;如需结构化查询(按对象属性、按类过滤),用页面遍历 + 对象端点组合,或在服务器端用 XWQL 页面(/bin/view/... 中嵌 services.query 脚本)实现。模板中展示的 number/start/orderField/order/distinct/prettyNames/wikis/className 参数仅在查询实现可用时生效。

九、对象与类域

9.1 页面对象列表

XWiki 的结构化数据挂在页面的「对象」(XObject)上。先看用户页自带的对象:

curl -s -u admin:q1w2e3r4 http://localhost:8148/rest/wikis/xwiki/spaces/XWiki/pages/admin/objects | grep -o '<\(className\|number\)>[^<]*'

实测回显(节选):

<className>XWiki.XWikiRights
<className>XWiki.XWikiUsers

(输出为节选;<number> 为同类对象的序号,从 0 起。)

9.2 新建对象(以评论为例)

curl -s -u admin:q1w2e3r4 -X POST -H 'Content-Type: application/xml' \
  -d '<?xml version="1.0" encoding="UTF-8"?><object xmlns="http://www.xwiki.org"><className>XWiki.XWikiComments</className><property name="author">admin</property><property name="comment">来自 REST API 的评论实测</property></object>' \
  http://localhost:8148/rest/wikis/xwiki/spaces/Sandbox/pages/APITestPage/objects \
  -o /dev/null -w '%{http_code}\n'

实测回显:

201

建后核验对象列表(实测回显):

<className>XWiki.TagClass
<number>0
<className>XWiki.XWikiComments
<number>0

XWikiComments 对象编号 0 已挂到页面,属性值随对象保存。对象 POST 同时会推动页面版本号(见 6.5 节版本清单)。

9.3 类定义列表与单个类

curl -s -u admin:q1w2e3r4 'http://localhost:8148/rest/wikis/xwiki/classes?number=4' | grep -o '<id>[^<]*'

实测回显:

<id>AnnotationCode.AnnotationClass
<id>AnnotationCode.AnnotationConfig
<id>AppWithinMinutes.Boolean
<id>AppWithinMinutes.Content
curl -s -u admin:q1w2e3r4 http://localhost:8148/rest/wikis/xwiki/classes/XWiki.XWikiUsers

实测回显(节选):

<?xml version="1.0" encoding="UTF-8" standalone="yes"?><class xmlns="http://www.xwiki.org"><link href="http://localhost:8148/rest/wikis/xwiki/classes/XWiki.XWikiUsers" rel="self"/><link href="http://localhost:8148/rest/wikis/xwiki/classes/XWiki.XWikiUsers/properties" rel="http://www.xwiki.org/rel/properties"/><link href="http://localhost:8148/rest/wikis/xwiki/classes/XWiki.XWikiUsers/objects" rel="http://www.xwiki.org/rel/objects"/><id>XWiki.XWikiUsers</id><name>XWiki.XWikiUsers</name><property name="first_name" type="String"><link href="http://localhost:8148/rest/wikis/xwiki/classes/XWiki.XWikiUsers/properties/first_name" rel="self"/><attribute name="name" value="first_name"/>

(输出为节选;property 元素带 name/type 属性,属性细节在 attribute 子元素中。)基于类清单 + 对象端点可做「按类批量导出页面」的盘点脚本。

十、标签域

10.1 正确写法:标签名是属性不是文本

curl -s -u admin:q1w2e3r4 -X PUT -H 'Content-Type: application/xml' \
  -d '<?xml version="1.0" encoding="UTF-8"?><tags xmlns="http://www.xwiki.org"><tag name="apitest"/><tag name="restapi"/></tags>' \
  http://localhost:8148/rest/wikis/xwiki/spaces/Sandbox/pages/APITestPage/tags \
  -o /dev/null -w '%{http_code}\n'
curl -s -u admin:q1w2e3r4 http://localhost:8148/rest/wikis/xwiki/spaces/Sandbox/pages/APITestPage/tags | grep -o '<tag name="[^"]*"'

实测回显:

202
<tag name="apitest"
<tag name="restapi"

(grep 按 < 截断," 后的原文尖括号未显示。)PUT 是整体覆盖语义:新请求里没列出的旧标签会被移除。

10.2 踩坑实录:文本子元素形态得到空标签

第一次尝试把标签名写成文本子元素 <tag>apitest</tag>,PUT 返回 202 但 GET 显示空标签(实测回显逐字):

<?xml version="1.0" encoding="UTF-8" standalone="yes"?><tags xmlns="http://www.xwiki.org"><tag name=""><link href="http://localhost:8148/rest/wikis/xwiki/tags/" rel="http://www.xwiki.org/rel/tag"/></tag><tag name=""><link href="http://localhost:8148/rest/wikis/xwiki/tags/" rel="http://www.xwiki.org/rel/tag"/></tag></tags>

服务端解析的是 name 属性,文本内容被忽略,结果给页面挂了两个空名标签(对应两个 XWiki.TagClass 对象)。解法即 10.1 的属性写法,PUT 覆盖后空标签随之清除。全局标签清单用 GET /rest/wikis/xwiki/tags 查看。

十一、分页、过滤与排序通用约定

  1. 分页:全部集合类端点统一用 number(页大小)+ start(起始下标),实测有效(6.1 节恰好 2 条);limit/offset 无效。
  2. 排序:集合端点本身不提供排序参数;排序诉求走 8.2 节模板中的 orderField/order(仅在查询实现可用时生效),或脚本取回后本地排序。
  3. 过滤:列表端点按资源天然过滤(空间级列表只含本空间);按类/按属性过滤无 REST 参数,用遍历 + 对象端点组合实现。
  4. 表示格式:Accept: application/json 与 XML 任选,字段语义一致(6.2 节实测两种形态)。
  5. 超媒体链接:每个资源响应的 <link rel=.../> 列出合法的后续操作地址,按链接走即可避免拼错路径(如 /pages/{space}/{page} 的 404 坑)。

十二、错误码对照与排障实录

以下状态码全部实测复现,响应体与日志逐字保留:

状态码触发场景(实测)响应/日志特征处理
401口令错误或匿名执行写操作(带合法请求体)响应体含 com.xpn.xwiki.XWikiException: Error number 9001 in 9: Access denied in edit mode on document xwiki:Sandbox.CreateByPut;日志 WARN Authentication failure with login [admin]核对凭据;匿名写被权限拒绝属预期
403multipart 附件上传未带 CSRF 令牌日志 WARN Secret CSRF token verification failed (token: [null], stored token: [...])请求头携带 XWiki-Form-Token(7.1 节)
404页面不存在;或页面路径写成了 /pages/{space}/{page} 旧形态Tomcat HTML 错误页(非 XML)核对资源路径形态
405对页面集合 POST 建页空XWiki 无 POST 建页,改用 PUT(6.6 节)
500PUT/POST 带空请求体Tomcat 异常报告,根因 NullPointerException: Cannot invoke "org.xwiki.rest.model.jaxb.Page.getContent()" because "rest..."请求体必须携带合法的 XML/JSON 表示(哪怕只写一个字段)

⚠️ 注意:500 与 401 容易混淆——空请求体(无认证头或有认证头)都会 500,只有「带合法表示体 + 凭据错误/无权限」才是 401。写脚本时先确保请求体非空,再排查认证。

排障取证统一看应用日志(固定落盘路径,-f 跟踪):

tail -f /usr/local/xwiki/data/logs/xwiki.log

实测该日志按行包含时间戳、线程、请求 URL、级别与消息(7.1、十二章引用的 WARN 均出自此文件)。

十三、服务进程与 JVM 内存核验

XWiki 是 Java 进程(Tomcat 内核),API 排障前先确认进程与堆参数真实生效。进程查看与生效值实测:

ps -ef | grep [j]ava

实测回显(节选,保留关键 JVM 参数,整行较长):

root    1  0  Oct21 ?  00:02:39 /opt/java/openjdk/bin/java -Djava.util.logging.config.file=/usr/local/tomcat/conf/logging.properties -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager -Xms512m -Xmx1024m -Dorg.apache.tomcat.util.buf.UDecoder.ALLOW_ENCODED_SLASH=true -Dorg.apache.catalina.connector.CoyoteAdapter.ALLOW_BACKSLASH=true -Djava.security.egd=file:/dev/./urandom -Djdk.tls.ephemeralDHKeySize=2048 -Djava.protocol.handler.pkgs=org.apache.catalina.webresources -Dsun.io.useCanonCaches=false -Dorg.apache.catalina.security.SecurityListener.UMASK=0027 --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.lang.reflect=ALL-UNNAMED --add-opens=java.base/java.io=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.base/java.util.concurrent=ALL-UNNAMED --add-opens=java.rmi/sun.rmi.transport=ALL-UNNAMED -classpath /usr/local/tomcat/bin/bootstrap.jar:/usr/local/tomcat/bin/tomcat-juli.jar -Dcatalina.base=/usr/local/tomcat -Dcatalina.home=/usr/local/tomcat -Djava.io.tmpdir=/usr/local/xwiki/data/tmp org.apache.catalina.startup.Bootstrap start

逐项解读:实测堆参数为 -Xms512m -Xmx1024m(初始堆 512MB、最大堆 1GB);-Djava.io.tmpdir=/usr/local/xwiki/data/tmp 指向永久目录下的临时目录;--add-opens 系列为 OpenJDK 21 反射开放参数,属官方启动脚本固定值,勿动。

堆内存的修改位置:JAVA_OPTS 在部署时的服务环境变量中设置——本文环境按 《XWiki 18.8 Docker Compose 部署指南》部署,改 compose 编排中 web 服务的 JAVA_OPTS 环境变量后重启服务生效;以 systemd 托管的裸机部署则写在服务单元的 Environment="JAVA_OPTS=-Xms512m -Xmx1024m"(每行仅一个 Environment 项)。修改后用上面的 ps -ef | grep [j]ava 复核 -Xms/-Xmx 实际生效值(本环境 jps 命令不可用,以 ps -ef 为准)。

生产建议(结合官方镜像默认值与中小规模实测):API 批量场景并发不高,但渲染与索引吃堆,-Xmx 不建议低于 1GB;扩容到 4GB 物理内存以上时可按 -Xms1g -Xmx3g 档位设置(配置模板见 《XWiki 18.8 配置参数详解》第十章)。

十四、常见问题

Q1:REST 前缀到底带不带 /xwiki? 取决于部署上下文。ROOT 上下文部署(本文环境)前缀是 /rest,带 /xwiki/rest 会 404;war 部署到 /xwiki 上下文则前缀为 /xwiki/rest。判定方法:curl -s -o /dev/null -w '%{http_code}\n' http://localhost:8148/xwiki/rest/wikis,404 即说明当前是 ROOT 形态(2.1 节实录)。

Q2:上传附件 403,但同一账号改页面是 202? multipart 附件上传被 CSRF 过滤器拦截,Basic 认证不豁免该类请求。解法:从任意认证后响应头取 XWiki-Form-Token 放入请求头重试;或改用 7.2 节的 PUT 原始体上传,完全绕开 multipart(推荐脚本使用)。

Q3:写请求返回 500,日志却有认证失败 WARN,是不是认证问题? 不是。实测「空请求体」无论认证与否都返回 500(根因是服务端解析空表示时代码空指针);「带合法 XML 体 + 错误口令」才是 401。先给请求体补上 <page xmlns="http://www.xwiki.org">... 最外层元素再排查。

Q4:页面列表翻页参数不生效? 参数名是 number/start,不是 limit/offset(6.1 节实测对照)。

Q5:设置了标签但名字是空的? 标签名必须写 name 属性(<tag name="apitest"/>),写成文本子元素会被忽略并产生空名标签(10.2 节实录);用属性写法重新 PUT 覆盖即可清除。

Q6:/query 端点为什么什么都查不到? 该端点依赖旧查询插件,在标准 flavor 的 18.8.0 中实测三种语法均返回空集合(8.2 节实录)。内容检索用 /search;结构化盘点用类 + 对象端点组合。

Q7:DELETE 删掉的页面能从 REST 恢复吗? 不能。REST 只有删除端点,恢复需到 Web 界面的回收站操作(18.8 默认开启回收站,行为受 《XWiki 18.8 配置参数详解》第 6.3 节存储域参数控制)。批量删除前建议逐页 GET 导出 XML 备份。

Q8:如何做每日巡检? 三步探活(实测命令):curl -s -o /dev/null -w '%{http_code}' http://localhost:8148/ 应为 302;curl -s -o /dev/null -w '%{http_code}' -u admin:q1w2e3r4 http://localhost:8148/rest/wikis 应为 200;ps -ef | grep [j]ava 确认进程与 -Xmx 未漂移。异常时看 /usr/local/xwiki/data/logs/xwiki.log 末段。

十五、参考链接