Apache Lenya 内容管理实战详解
以真实操作记录讲解 Apache Lenya 2.0.4 内容管理全流程:登录发布后台、导入示例内容、创建与编辑页面、校验出校验入、工作流提交与发布上线,并覆盖角色模型与常见故障处理。
Apache Lenya 内容管理实战详解
退役提示:Apache Lenya 2.0.4 已随项目于 2015 年迁入 Apache Attic,本文面向学习研究与遗产系统维护。文中全部操作与回显均在 Ubuntu 22.04 服务器上完成,Lenya 以内置 Jetty(8888 端口)运行,Java 进程堆为 -Xms256M -Xmx512M(经 ps -ef | grep [j]ava 确认)。安装与构建步骤见本专栏安装部署篇。
一、实战环境与入口地址
1.1 环境约定
| 项目 | 值 |
|---|---|
| 操作系统 | Ubuntu 22.04 |
| Java | OpenJDK 1.8.0_504 |
| Lenya | 2.0.4(源码构建) |
| Servlet 环境 | Jetty 5.1.4(构建包自带) |
| 基础地址 | http://localhost:8888/ |
| 出版站点 | default |
| 编辑区 URL | http://localhost:8888/default/authoring/ |
| 发布区 URL | http://localhost:8888/default/live/ |
1.2 未登录状态的行为
直接访问编辑区会被重定向到登录页:
curl -s -c /tmp/lj -L http://localhost:8888/default/authoring/ -o /tmp/login-page.html
grep -oE '<form[^>]*>' /tmp/login-page.html
实测回显:
<form method="post" action=";jsessionid=32ibvfkr82vk2?lenya.continuation=65b1da5a6dcb324fa1e3ce028ff6d2e96b78bf45&lenya.usecase=ac.login" class="lenya-form">
登录表单由 usecase 框架的 continuation 机制驱动:action 地址带有会话标识与 continuation 令牌,表单字段为 username 与 password。
重定向行为的边界也值得摸清,实测三组对照:访问编辑区根路径返回 302 并带 Location 指向登录表单;访问 live 区根路径同样是 302;而访问 live 区不存在的页面(如 nonexistent.html)在未登录态下竟也返回 302 而非 404——未登录用户的任何受控资源访问都会先撞上登录墙,"资源是否存在"这一信息被登录门统一挡住。这个行为对安全巡检有两点提示:一是不能靠 404 探测来给 live 区做资源枚举测试,未登录态下 302 一视同仁;二是登录后的 404 才反映真实资源存在性,做巡检脚本时要区分两种会话状态。另外注意 action 地址中的分号会话标识(;jsessionid)是 URL 重写式会话跟踪,老式运行环境的兼容做法,若前台挂了新式反向代理需确保不剥离分号路径参数,否则登录会静默失败。
1.3 运行期日志布局与观测点
实战排障的前提是知道去哪里找证据。Lenya 运行期日志集中在构建产物内的 WEB-INF/logs/ 目录,实测包含五个文件,各司其职:
2026_10_14.request.log # 按天滚动的访问日志,文件名含日期
jetty-log4j.log # Jetty 服务层日志
log4j.log # Cocoon 与 Lenya 应用主日志,排障首选
repository.log # 仓库操作日志,checkout/checkin 留痕
sessions.log # 会话建立与销毁
观测建议按问题类型直达:页面渲染异常看 log4j.log;保存与锁问题先看 repository.log,再到 log4j.log 检索 lockInvolvedObjects;登录与会话异常对照 sessions.log 的会话存活记录。access-control.xml 与用例策略解析产生的事件也会进入 log4j.log,权限类问题同样以它为主证据源。
二、登录发布后台
2.1 设置账号口令
默认账号 lenya 属于 admin、editor、sitemanager 组。为便于实测,先把口令重置为已知值(生产环境请使用强口令):Lenya 口令以 MD5 散列存储,生成方式就是对口令原文做 md5:
echo -n 'Lenya@2026' | md5sum
实测回显:
9a4823b283cbbcef8d0b26d3020f3192
编辑 config/access-control/passwd/lenya.iml,将 password 字段替换为新散列,重启 Lenya 后生效。文件结构如下(节选):
<identity id="lenya" class="org.apache.lenya.ac.file.FileUser">
<name>Levi Vanya</name>
<email>levi.vanya@lenya.apache.org</email>
<password type="md5">9a4823b283cbbcef8d0b26d3020f3192</password>
<groups>
<group>editor</group>
<group>admin</group>
<group>sitemanager</group>
<group>reviewer</group>
</groups>
</identity>
2.2 执行登录
用 curl 模拟浏览器:先取登录页与 continuation,再提交表单:
curl -s -b /tmp/lj -c /tmp/lj -L -o /tmp/login-result.html \
--data 'username=lenya&password=Lenya@2026&submit=Login' \
'http://localhost:8888/default/authoring/;jsessionid=32ibvfkr82vk2?lenya.continuation=65b1da5a6dcb324fa1e3ce028ff6d2e96b78bf45&lenya.usecase=ac.login'
登录成功后再访问编辑区,页面不再是登录表单,而是首次运行向导:
Getting Started
Welcome to the Lenya default publication.
Your publication is currently empty. The easiest way to start is to import example pages.
页面提供两个动作:Import example pages(导入示例内容,lenya.usecase=export.importExampleContent)与 Start with own page(从空白页面开始)。
三、导入示例内容
3.1 执行导入
提交导入用例后,编辑区立即出现完整站点骨架。实测导入后的 authoring 首页标题与菜单:
<title>Apache Lenya | default | authoring | /index.html | Welcome to the Lenya default publication</title>
页面中可提取出的管理用例包括:
ac.logout # 注销
admin.changePassword # 修改口令
admin.users # 用户管理
admin.aboutLenya # 关于
bxe.edit / kupu.edit # 所见即所得编辑器
tab.meta / tab.overview / tab.revisions # 元数据、概览、修订标签页
sitemanagement.create / sitemanagement.rename / sitemanagement.delete
sitemanagement.changeLabel / sitemanagement.deleteLanguage
workflow.submit / workflow.publish / workflow.multiWorkflow
monitoring.sessionLogs
3.2 导入后的文件形态
示例内容落在 content/authoring 下,每个文档一个 UUID 目录:
content/authoring/
├── 03cf36c0-c80d-11f1-a41d-e0673eff98e1/
│ ├── en
│ ├── en.meta
│ └── en.rcml
├── 1f6d47b0-c80d-11f1-a41d-e0673eff98e1/
└── sitetree.xml
其中 en 是 XHTML 正文,en.meta 存 Dublin Core 元数据,en.rcml 是修订控制记录(版本链)。示例站点自身的组织也值得一看:sitetree.xml 中 doctypes 节点下挂着 xhtml-document、links、opendocument、cforms 四个子节点,news 节点下挂着 installed 子节点——这份目录骨架实际是 Lenya 文档类型的活教材,接入新站点时把业务页面挂到对应分支,就能继承示例内容里已经配好的区域、工作流与角色策略,这也解释了为什么 Lenya 官方建议从示例内容起步而不是空白起步。
导入动作的幂等性也值得验证:再次请求同样的导入用例,会得到内容已存在的提示而不会重复复制,判断依据就是 content/authoring/sitetree.xml 中各节点的 uuid——uuid 一旦落盘即作为身份锚点,导入逻辑按 uuid 去重。
3.3 全新构建下的空站点状态对照
将构建产物完全重建后重跑登录,可以观察到"零内容"状态:此时 content/authoring 目录尚未生成,登录后编辑区直接呈现 Getting Started 向导页,页面列出两个入口——Import example pages(导入示例内容)与 Start with own page(从零开始)。这正是 2.1 中提到的首次运行向导。实测在全新构建上执行导入,产生的 UUID 目录与此前部署完全不同(如 181aedd0-c821-11f1 与之前的 12a85db0-c80d),说明 uuid 由系统实时生成,同一份示例内容在不同实例上会得到不同的 uuid;随之而来一个运维要点:跨实例迁移内容时不能只拷贝正文文件,sitetree.xml 与各 .meta 文件必须整体同步,否则 uuid 锚定关系断裂,链接与导航将失效。
四、创建新页面
4.1 打开创建表单
创建用例挂在一个已存在的文档 URL 上,用 path 参数指定新节点路径,用 doctype 指定资源类型:
curl -s -b /tmp/lj 'http://localhost:8888/default/authoring/index.html?lenya.usecase=sitemanagement.create&path=/myfirst&doctype=xhtml' -o /tmp/create-form.html
grep -oE 'name="[a-zA-Z.]+"' /tmp/create-form.html | sort -u
实测表单字段:
name="lenya.continuation"
name="lenya.usecase"
name="path"
name="nodeName"
name="relation"
name="dublincore.title"
name="dublincore.creator"
name="dublincore.subject"
name="dublincore.publisher"
name="dublincore.rights"
name="visibleInNav"
name="submit"
4.2 两个必带参数的教训(真实排障实录)
第一次提交时省略了 doctype 参数,服务端返回 500,日志记录:
ERROR lenya.site - UsecaseException: ResourceTypeSelector/null
java.lang.IllegalArgumentException: Attempted to retrieve component with null hint (Key='ResourceTypeSelector/null')
at org.apache.excalibur.source.impl.SourceResolverImpl.resolveURI(SourceResolverImpl.java:612)
at org.apache.lenya.cms.repository.SourceRepository.getNode(SourceRepository.java:85)
at org.apache.lenya.cms.site.usecases.Create.getResourceType(Create.java:413)
原因:资源类型组件按提示名查找,缺参时提示为空。第二次补上 doctype=xhtml 后又遇到新的 500:
ERROR lenya.site -
java.lang.NullPointerException
at org.apache.lenya.cms.site.usecases.Create.getVisibleInNav(Create.java:180)
原因:表单缺少 visibleInNav 复选框参数时 getParameterAsString 返回 null,代码直接对 null 调 equals。这两次排障说明 Lenya 2.0.4 的创建用例必须同时携带 doctype 与 visibleInNav(浏览器菜单里由模板自动补齐,命令行复现时必须手工带上)。
4.3 提交创建
带齐参数提交(continuation 取自表单页):
curl -s -b /tmp/lj -o /dev/null -w 'POST=%{http_code}\n' \
--data 'lenya.continuation=408c37353e2e8f1b5a811c666b8d223227127076&lenya.usecase=sitemanagement.create&path=/myfirst&doctype=xhtml&visibleInNav=true&nodeName=myfirst&dublincore.title=My First Lenya Page&relation=child&dublincore.creator=lenya&dublincore.subject=Lenya demo&dublincore.publisher=AgentB&dublincore.rights=2026 AgentB&submit=Create' \
'http://localhost:8888/default/authoring/index.html'
说明:continuation 令牌是服务端为本次表单流生成的 40 位十六进制值,每次打开表单都会变化,脚本化时应先 GET 表单页再用正则提取(见 4.1 的取表单步骤),上述值仅为一次真实提交的示例。实测回显:
POST=302
验证创建结果:
curl -s -b /tmp/lj -o /tmp/myfirst.html -w 'PAGE=%{http_code}\n' 'http://localhost:8888/default/authoring/myfirst.html'
grep -oiE '<title>[^<]*' /tmp/myfirst.html | head -1
grep -oE '<node id="myfirst"[^>]*>' content/authoring/sitetree.xml
实测回显:
PAGE=200
Apache Lenya | default | authoring | /myfirst.html | New XHTML document
<node id="myfirst" uuid="12a85db0-c80d-11f1-b628-e0673eff98e1">
新页面已进入编辑区导航树,标签取自 Dublin Core 标题。
创建成功还有一个落盘侧的可视信号:content/authoring/sitetree.xml 的树尾会追加新节点,形如 <node id="fresh2026" uuid="2214a3d0-c821-11f1-9e1c-95c84426f22a">,其 label 仅含勾选了显示在导航的那一种语言;若创建时未勾选 visibleInNav,节点会携带 visibleinnav="false" 属性,页面存在但不进导航菜单,与示例内容中"搜索页"的处理方式一致。排查"页面创建了但导航没有"或"导航有了但页面 404"这类不一致时,先看 sitetree.xml 的节点属性,再看 content 目录下的语言文件是否齐全,两侧对照基本能把问题定死在元数据或文件层。
五、编辑页面内容
5.1 取出内容(checkout 语义)
编辑器打开页面前会执行校验出(checkout)。命令行下可通过 neutron 风格的 introspection 获取编辑入口,或直接以 WebDAV 方式读写。这套通道的设计意图是双轨的:浏览器侧的 BXE/Kupu 编辑器本质上也是通过同一组 WebDAV 风格的读写接口拉取与提交正文,命令行只是把同一协议显式化;因此理解命令行流程,就等于理解了编辑器背后的全部网络行为,反过来界面编辑遇到的问题(保存失败、内容回退)都可以用命令行复现来定位是前端还是服务端的责任。实测 GET 内容源:
curl -s -b /tmp/lj 'http://localhost:8888/default/authoring/myfirst.xml?lenya.module=webdav&lenya.event=edit' -o /tmp/doc.xml
返回的 XML 含 DAV 属性描述与文档正文。直接读仓库文件亦可看到当前内容:
grep -oE '<h1>[^<]*</h1>' content/authoring/12a85db0-c80d-11f1-b628-e0673eff98e1/en
实测回显:
<h1>New XHTML document</h1>
5.2 提交修改(PUT 保存)
构造新的 XHTML 并 PUT 回同一地址:
curl -s -b /tmp/lj -X PUT -H 'Content-Type: text/xml' \
--data-binary @/tmp/newcontent.xml \
'http://localhost:8888/default/authoring/myfirst.xml?lenya.module=webdav&lenya.event=edit'
实测回显:
HTTP/1.1 204 No Content
再次读仓库文件,正文已更新:
<h1>My First Lenya Page</h1>
<p>This page was created and edited through the Lenya authoring usecase API during a real deployment test on Ubuntu 22.04.</p>
编辑区页面同步生效,浏览器打开 /default/authoring/myfirst.html 即见新内容。
5.3 锁与校验入(真实排障实录)
PUT 偶发 415 Unsupported Media Type 时不要急着改请求头,先看服务端日志。实测日志显示:
DEBUG lenya.publication - AbstractUsecase::lockInvolvedObjects() can not execute, object [node lenya://lenya/pubs/default/content/authoring/12a85db0-c80d-11f1-b628-e0673eff98e1/en] is already checked out
即文档被先前会话 checkout 后未正常 checkin(例如调试过程中会话重建),悬挂锁在 en.rcml 中体现为多余的 CheckOut 记录:
<XPSRevisionControl xmlns="">
<CheckIn backup="true" identity="lenya" session="12a7c170" time="1792009389851" version="1"/>
<CheckOut identity="lenya" session="213900f0" time="1792009414272"/>
</XPSRevisionControl>
处理:作为管理员恢复操作,从 en.rcml 中移除悬挂的 CheckOut 元素(保留 CheckIn 历史),并重启 Lenya(节点状态有内存缓存)。之后再 PUT 即返回 204。生产环境应避免手工改 rcml,优先让原会话正常退出或使用管理界面解锁。
版本回溯的文件形态值得再深一层。实测同一页面经过创建与一次 PUT 后,目录中出现成对的 .bak 文件:
en # 当前正文(第 2 版)
en.1792018005651.bak # 第 1 版正文备份
en.meta # 当前元数据
en.meta.1792018005651.bak # 第 1 版元数据备份
en.rcml # 修订控制记录
三个观察:其一,时间戳式的备份文件按毫秒命名,正文与元数据成对备份,说明元数据与正文走同一条版本化通道,回滚元数据(如标题、工作流历史)与回滚正文是原子动作;其二,.rcml 的 CheckOut 记录携带会话标识,两次不同浏览器会话的保存会产生两条不同的 CheckOut 行,据此可以还原"谁在哪个会话里改的";其三,.bak 数量随保存次数线性增长且不会自动清理,长期运行的站点需要把清理策略(如按月归档删除)写进运维规程,否则内容目录会持续膨胀。这套"文件即版本库"的设计没有中央数据库,备份内容目录就等于备份了全部修订历史,代价则是没有跨页面的事务性——理解这一点对设计备份窗口很重要。
六、工作流:提交与发布
6.1 提交审核(authoring 至 review)
打开提交用例表单:
curl -s -b /tmp/lj 'http://localhost:8888/default/authoring/myfirst.html?lenya.usecase=workflow.submit' -o /tmp/wf1.html
grep -oE '<input[^>]*name="(submit|sendNotification)"[^>]*>' /tmp/wf1.html
实测表单控件:
<input name="sendNotification" type="checkbox" />
<input type="submit" name="submit" value="Submit" />
提交后查看页面状态栏:
curl -s -b /tmp/lj 'http://localhost:8888/default/authoring/myfirst.html' | grep -oE 'id="workflow-state">[a-z]+|id="info-live">[a-z ]+'
实测回显:
id="workflow-state">review
id="info-live">not live
6.2 角色不足时发布被拒(真实排障实录)
直接打开发布用例,页面出现告警而非发布按钮:
Publish
Document: "My First Lenya Page"
Messages: The event publish is not executable on document "My First Lenya Page"
原因在工作流定义 config/workflow/workflow.xml:review 至 live 的 publish 转换带有角色条件
<transition source="review" destination="live">
<event id="publish"/>
<condition class="org.apache.lenya.cms.workflow.RoleCondition">review</condition>
<assign variable="is_live" value="true"/>
</transition>
而 authoring 子树的策略文件只给 reviewer 组授 review 角色、给 editor 组授 edit 角色,默认 lenya 账号不在 reviewer 组中。实测将默认工作流定义完整枚举一遍,六条转换与其角色条件如下表,"发布被拒"的原因在表上一目了然:
authoring -> review 事件 submit 条件 edit(提交审核)
authoring -> live 事件 publish 条件 review + edit(直发,双角色)
review -> live 事件 publish 条件 review(审核后发布)
review -> authoring 事件 reject 条件 review(驳回)
live -> authoring 事件 edit 条件 edit(下线回编)
authoring -> authoring 事件 edit 条件 edit(续改)
另有 trash 与 archive 两个状态,其转换由删除、归档用例驱动,不在 publish 主链路上。
这张表解释了两个容易疑惑的现象:其一,为什么从 authoring 区直接点 publish 有时可行——authoring→live 转换存在,但要求同时持 review 与 edit 两角色;其二,职责分离为什么天然成立——从 review 区发布只查 review 角色,编辑角色在这里无用武之地。is_live 变量在转换中被赋值,页面状态栏的 not live/live 显示即读取该变量,工作流变量与页面渲染由此打通。
处理:在 lenya.iml 的 groups 节补充一行,然后重启:
<group>reviewer</group>
重启后重新打开发布表单,Publish 按钮出现。
补充一个与该排障互为印证的实测对照:在全新构建的产物上(lenya.iml 保持出厂默认,仅 editor、admin、sitemanager 三个组),对刚创建的页面重复 submit 与 publish 两步,submit 正常把状态推进到 review,publish 同样得到 "The event publish is not executable" 告警——说明角色约束在每个实例上稳定生效,与内容状态无关,判断依据始终是"当前账号在目标区域子树上的角色集合"减去"工作流转换要求的角色集合"是否为空。这给排障提供了一个可复用的判定式:发布失败先查两件事,文档当前工作流状态(决定走哪条转换)与账号在 authoring/review 子树上的 ACML 角色(决定转换条件是否满足),两者任何一个不匹配都会表现为同一个 "not executable" 告警。
6.3 执行发布
curl -s -b /tmp/lj -o /dev/null -w 'PUB=%{http_code}\n' \
--data 'lenya.continuation=36184319691d381619563c8c52171465368f4e26&lenya.usecase=workflow.publish&submit=Publish' \
'http://localhost:8888/default/authoring/myfirst.html'
说明:continuation 取自 6.2 中重新打开的发布表单页,同样每次打开都会变化。实测回显:
PUB=302
验证发布区与编辑区状态:
curl -s -o /tmp/live.html -w 'LIVE=%{http_code}\n' 'http://localhost:8888/default/live/myfirst.html'
grep -oE '<h1>[^<]*' /tmp/live.html
curl -s -b /tmp/lj 'http://localhost:8888/default/authoring/myfirst.html' | grep -oE 'Workflow State: <span[^>]*>[a-z]+|id="info-live">[a-z ]+'
实测回显:
LIVE=200
<h1>My First Lenya Page
Workflow State: live
id="info-live">live
发布前 live 区该 URL 为 404,发布后 200 且内容与编辑区一致;编辑区状态从 review 进入 live。父页面(站点首页 index.html)需先发布,否则子页面发布会因祖先未上线而无法完成,这也是 Lenya 站点级一致性的体现。
6.4 版本回溯
编辑区右侧的 Versions 标签由 tab.revisions 用例驱动,列出该文档的全部修订版本与创建时间,选中历史版本即可恢复;其文件层实现就是把对应时间戳的 .bak 写回当前文件并补一条 CheckIn 记录,与 Q10 描述的手工路径完全一致。
每次保存都会产生带时间戳的历史文件(形如 en.1792009389851.bak),编辑区的 tab.revisions 标签页可浏览修订列表并恢复到指定版本;修订链的权威记录在 en.rcml。
七、多语言与站点管理
7.1 多语言页面
在创建用例与页面标签页中带 language 参数即可维护多语言版本;不同语言的内容文件并列存放于同一 UUID 目录(en、de 等),sitetree 中 label 按 xml:lang 区分。sitemanagement.deleteLanguage 用例可删除指定语言版本。
7.2 重命名、移动与标签
sitemanagement 用例族提供 rename(改 URL 路径)、move、changeLabel(改导航标签)等操作。由于内部引用均以 UUID 为锚(sitetree 的 uuid 属性、内链由 UuidToUrlTransformer 解析),重命名与移动不会造成内容断链,这是 Lenya 站点管理设计上最值得借鉴的一点。
7.3 用户与组管理
实测 access-control/passwd 目录中的文件按后缀分为五类,除用户文件(.iml)外还有组文件(admin.gml、editor.gml、reviewer.gml、sitemanager.gml)、角色登记文件(admin.rml、edit.rml、review.rml、session.rml、sitemanager.rml、visit.rml)、机器身份文件(localhost.ipml,把特定来源地址当作访问主体用于本机免登录等场景)与装配入口 access-control.xml。目录中另有两个样例配置 cifs.properties.sample 与 ldap.properties.sample,说明 Lenya 设计上支持把 CIFS 域或 LDAP 目录接入为身份源,账号不必仅存于本地文件。对遗产系统接手者而言,这里有两个实操要点:接手时先确认线上实际启用哪种身份源(看 access-control.xml 装配的 realm 实现类),别只盯着 passwd 目录排查登录问题;新增账号时若沿用本地文件路线,.iml 的 groups 节与口令散列必须成对修改,漏改 groups 是"能登录但看不到菜单"类问题的常见根因。
admin.users 用例提供后台用户管理界面,支持创建用户、设置口令(界面内自动生成 MD5)、分配组。等价的文件操作是直接编辑 config/access-control/passwd 下的 .iml 与 .gml 文件后重启服务,本文 6.2 的角色调整即采用文件方式。
八、实战检查清单
实战收尾或接手老站点时,按下列清单逐项核对,能覆盖绝大多数运行风险:
- 账号与角色:passwd 目录中每个在用 .iml 的 groups 节是否与其实际职责匹配,有无账号被临时授予 reviewer 后忘了回收;
- 工作流健康:抽取几篇文档的 en.meta,检查 workflow 历史是否有长期停在 review 的"僵尸"稿件与异常的事件序列(如无 submit 直接 publish);
- 版本堆积:统计 .bak 文件总量与最老时间戳,超过备份保留策略的做归档清理;
- 锁卫生:.rcml 中 CheckOut 无对应 CheckIn 的悬挂记录有多少处,只允许通过恢复操作消除;
- 日志水位:log4j.log 与 repository.log 中 ERROR 级条目的最近出现时间,长时间无 ERROR 才算健康;
- live 一致性:随机抽页对比 authoring 与 live 区的标题,确认发布链路正常工作;
- 备份有效性:content/ 与 config/ 的备份是否在最近 24 小时内生成,且可在独立环境恢复演练;
- 端口暴露:8888/8088 是否仍只对预期网段开放,管理面是否可被非运维来源访问。
接手老站点的第一天,另有七件小事值得按顺序做完,作为上文检查单的落地抓手:
- 部署完成后先登录,确认编辑区出现 Getting Started 向导或站点菜单;
- 首次使用选择导入示例内容,观察 content/authoring 目录的 UUID 结构;
- 任何页面操作失败先看 build/lenya/webapp/WEB-INF/logs/log4j.log,Lenya 的错误信息准确但返回页简陋;
- 创建页面务必携带 doctype 与 visibleInNav;浏览器操作则无需关心;
- 发布前确认父页面已上线、账号具备 editor 与 reviewer 组;
- 保存返回 415 时优先排查悬挂的 checkout 锁;
- 定期备份 content/ 目录与 config/ 目录,二者合起来就是站点的全部状态。
九、常见问题解答(FAQ)
Q1:登录页反复跳回登录表单? 确认提交地址带了完整的 jsessionid 与 lenya.continuation 参数,且用户状态文件(passwd 目录)可写。若手工改过 .iml,需重启 Lenya 使账号缓存失效。
Q2:创建页面返回 500,日志提示 ResourceTypeSelector/null? 缺少 doctype 参数。创建用例 URL 必须形如 index.html?lenya.usecase=sitemanagement.create&path=/myfirst&doctype=xhtml,同时 POST 表单也要带上 doctype 字段。
Q3:创建页面 500,日志提示 NullPointerException(Create.getVisibleInNav)? 缺少 visibleInNav 参数。该值对应表单中"在导航中显示"复选框,命令行提交时显式加 visibleInNav=true。
Q4:保存内容返回 415? 大概率是文档存在悬挂 checkout 锁。查看日志中 lockInvolvedObjects 提示,从对应 en.rcml 移除多余 CheckOut 元素并重启服务,之后重试 PUT。
Q5:发布按钮不出现或提示 The event publish is not executable? 按顺序检查三点:文档当前工作流状态(review 或 authoring 才能直接 publish 至 live);账号是否同时具备 editor 与 reviewer 组(在 .iml 的 groups 中确认);父页面是否已发布上线。
Q6:发布成功后 live 区仍是 404? 确认访问的是正确端口与路径(Jetty 默认 8888),并确认发布的是当前 URL 对应文档;父页面未上线时子页面发布会被拒绝,先发布站点首页。
Q7:编辑器(BXE/Kupu)页面空白? 这两个编辑器依赖 2010 年代浏览器特性,现代浏览器可能无法完整渲染。命令行与脚本维护可直接使用 WebDAV 方式读写 myfirst.xml,本文第五章即采用该方式,功能等价。
Q8:如何彻底重置一个出版站点的内容? 停止服务后删除 content/ 目录(保留目录骨架),重启后在编辑区会重新出现 Getting Started 向导,可重新导入示例内容。config/ 目录承载账号与策略,重置时不应删除。
Q9:导航树标签与页面标题不一致? 导航标签来自 sitetree.xml 的 label,页面标题来自内容文件 head/title。changeLabel 用例只改前者;两者可在站点管理中分别维护,属正常现象。
Q10:内容目录里的 en.1792009389851.bak 是什么? 修订控制的备份版本文件,由 CheckIn backup=true 产生,命名规则为语言代码加时间戳。恢复历史版本时把目标 bak 文件内容写回 en 并补一条 CheckIn 记录,或直接使用界面上的 tab.revisions 恢复功能。
Q11:能否跳过审核直接发布? 可以。工作流定义中存在 authoring 直接至 live 的 publish 转换(需要 edit 与 review 角色),对状态为 authoring 的文档直接执行 workflow.publish 即可;本文为演示完整流程先走了 submit 至 review 再 publish。
Q12:如何查看当前登录用户的会话与状态? 编辑区右上角状态栏显示 Workflow State、live 状态与 User 信息(实测 id 分别为 workflow-state、info-live、logged-user);monitoring.sessionLogs 用例可查看会话日志。
Q13:全新构建产物上第一次登录,看不到任何页面菜单,正常吗? 正常。全新构建的出版站点没有任何内容,content/authoring 目录尚未生成,编辑区直接呈现 Getting Started 向导,需要先执行 Import example pages 导入示例内容(或选择 Start with own page 从零建页)。导入会生成全新的 UUID 目录,与任何历史部署的 UUID 都不同,这是 uuid 实时生成机制的正常表现而非异常。
Q14:为什么正文备份文件与元数据备份文件总是成对出现、时间戳一致? 保存动作走同一条版本化通道,正文 en 与元数据 en.meta 在同一次 checkin 中被一并备份,时间戳即毫秒级保存时刻。因此回滚正文时应同步回滚对应的 en.meta,否则元数据中的工作流历史与正文版本错位,状态栏显示可能与实际内容不符。
十、参考链接
- Apache Attic 项目页:https://attic.apache.org/projects/lenya.html
- Lenya 归档下载:https://archive.apache.org/dist/lenya/
- Lenya 网站归档(用户与工作流文档):https://lenya.apache.org/
- Lenya 源码仓库(只读):https://svn.apache.org/repos/asf/lenya/
- Dublin Core 元数据词汇:https://www.dublincore.org/specifications/dublin-core/dces/
- Apache Cocoon 2.1 管线文档:https://cocoon.apache.org/2.1/
- WebDAV 协议(RFC 4918):https://datatracker.ietf.org/doc/html/rfc4918
- Lenya Wiki 归档:https://cwiki.apache.org/confluence/spaces/LENYA/