CMS
受众:开发者 摘要:spring-open-module-cms 内容管理:页面 / 公告 / 轮播图 / 导航 / 内容块 / 专题 / 广告位。
CMS 模块用于承载官网、用户端首页和运营内容。第一阶段采用轻量内容项模型,先提供公共只读能力和统一后台内容管理入口,后续再按业务复杂度拆分更细的栏目、页面、公告、轮播图、导航、专题和广告位模型。
模块可选依赖,应用不需要 CMS 时可以移除 spring-open-module-cms 引用,不影响核心模块、Open Platform 或其他模块。
配置
spring:
open:
cms:
enabled: true
review:
# 新内容默认审核状态:approved / pending / rejected
# 默认 approved,避免运营内容因人工审核不及时无法展示。
default-status: approved
auto:
# 待审核内容超时自动处理。default-status 改成 pending 后,仍可按默认动作兜底。
enabled: true
auto-startup: true
timeout: 24h
fixed-delay: 10m
# 可选 approved / rejected。
action: approved
task-name: cms.content.review.auto-handle
instance-id: default
limit: 100spring.open.cms.enabled=false 时,CMS Controller、Service 和 Mapper 扫描都会关闭。
spring.open.cms.review.default-status 控制新内容的默认审核状态。框架默认 approved,适合快速上线和运营内容低风险场景;如果项目需要先审后发,可以改为 pending,然后通过后台审核接口通过后才会进入公共展示。
spring.open.cms.review.auto.* 控制待审核内容的超时自动处理。默认开启,超过 timeout 仍未人工处理时按 action 自动通过或驳回,并写入内容操作日志。第一阶段使用待审核内容的更新时间作为超时判断点;如果项目需要严格审核,可以把 action 配成 rejected,或关闭 auto-startup 后只保留人工审核。
上述 CMS 配置已通过模块 ServiceProvider 声明为配置中心元数据。启用配置中心后,后台可以发现并维护这些运行配置;未通过配置中心覆盖时继续使用 application.yml / 环境变量中的默认装配值。
内容类型
当前统一使用 cms_content 表,content_type 区分内容用途:
| 类型 | 说明 |
|---|---|
channel | 栏目 |
page | 页面 |
announcement | 公告 |
banner | 轮播图 |
navigation | 导航 |
block | 内容块 |
topic | 专题 |
ad-slot | 广告位 |
统一模型可以减少早期重复代码,也方便前端先完成真实接口联调。后续如果某类内容字段明显复杂,再单独拆表或扩展附属表。
轮播图和导航这类入口链接支持按客户端区分,后台字段为 client:
| 值 | 说明 |
|---|---|
all | 所有客户端通用 |
app | App 移动端 |
pc | PC 电脑端 |
admin | Admin 后台端 |
该分端能力只用于轮播图和导航。页面、公告、内容块、专题等需要渲染正文或富文本的内容保持通用,不按客户端拆分。
显式 client 参数优先级最高。未传 client 时,后端会按 X-Client、X-Platform、Sec-CH-UA-Mobile、User-Agent 等请求头推断 App / PC / Admin;无法推断时只返回 all 通用内容。自动识别只作用于轮播图和导航,不扩散到富文本内容。
跳转只使用一个 url 字段,不维护多套 targetUrl。前端按 URL Scheme 自行解析:
| Scheme | 说明 |
|---|---|
http:// / https:// | 外部网址 |
path:// | 站内页面路径,例如 path:///pages/home/index |
event:// | 触发前端事件 |
method:// | 调用当前页面方法 |
scan:// | 调起扫码后继续解析扫码结果 |
App 端解析外部网页时会按运行环境处理:H5 直接使用浏览器打开,App 使用系统浏览器容器,小程序使用内置 web-view 容器页,避免在非 H5 环境中直接跳转失败。
CMS 内容会同时返回原始文案和展示文案:
| 字段 | 说明 |
|---|---|
title / summary / content | 后台维护的原始文案,适合后台编辑和内容管理 |
displayTitle / displaySummary / displayContent | 后端按当前语言解析后的展示文案,前端展示优先使用 |
多语言 key 约定:
messages.cms.content.<code>.title=标题
messages.cms.content.<code>.summary=摘要
messages.cms.content.<code>.content=正文未配置翻译时会回退到原始文案。Admin / App / PC 不需要为 CMS 运营内容维护独立硬编码翻译表。
SEO 元信息
CMS 内容项自带结构化 SEO 元信息,服务官网、落地页、公告、专题和广告位等 CMS 自有内容。Blog / Forum 仍维护各自文章、主题和举报治理所需的 SEO / 审核字段,不迁入 CMS。
当前后台内容编辑、保存前预览、内容版本快照和内容 CSV 导出都支持以下字段:
| 字段 | 说明 |
|---|---|
seoTitle | 搜索结果标题 |
seoKeywords | SEO 关键词,多个关键词可用英文逗号分隔 |
seoDescription | 搜索结果摘要 |
seoCanonicalUrl | 搜索引擎规范 URL |
seoRobots | 索引策略,例如 index,follow、noindex,nofollow |
seoOgTitle | OpenGraph 社交分享标题 |
seoOgDescription | OpenGraph 社交分享摘要 |
seoOgImageUrl | OpenGraph 社交分享图片 URL |
seoStructuredDataJson | JSON-LD 结构化数据,适合站点层渲染 <script type="application/ld+json"> |
seoStructuredDataJson 会随内容详情和版本快照返回,便于前端或 SSR 层渲染,但默认不进入 CSV 导出,避免在运营导出中混入大段 JSON 配置。
内置演示数据
新环境会随 CMS 模块插入少量演示内容,包含 1 条轮播图、1 条导航、1 条公告和 1 条内容块。它们只用于避免安装后首页聚合接口完全空白,管理员可以在后台内容管理中直接编辑、下线或删除。
这些数据使用稳定编码 quickstart.*,并已接入 Install 统一的演示数据清理入口。管理员可在“安装运维 / 演示数据清理”中预览 CMS 可清理目标,确认时提交模块编码 cms、数据类型 quickstart-data 和确认短语 清理 CMS 快速体验数据。清理只会处理这批稳定编码的数据;如果这些内容下已有子内容,后端会阻止自动清理,避免影响管理员后续新增的真实内容。
公共只读接口
所有接口的 JSON 响应体使用 Result<T>。
GET /api/v1/cms/home
GET /api/v1/cms/sitemap
GET /api/v1/cms/channels
GET /api/v1/cms/channels/{id}
GET /api/v1/cms/channels/code/{code}
GET /api/v1/cms/pages
GET /api/v1/cms/pages/{id}
GET /api/v1/cms/pages/code/{code}
GET /api/v1/cms/announcements
GET /api/v1/cms/announcements/{id}
GET /api/v1/cms/announcements/code/{code}
GET /api/v1/cms/banners
GET /api/v1/cms/banners/{id}
GET /api/v1/cms/banners/code/{code}
GET /api/v1/cms/navigations
GET /api/v1/cms/navigations/{id}
GET /api/v1/cms/navigations/code/{code}
GET /api/v1/cms/blocks
GET /api/v1/cms/blocks/{id}
GET /api/v1/cms/blocks/code/{code}
GET /api/v1/cms/topics
GET /api/v1/cms/topics/{id}
GET /api/v1/cms/topics/code/{code}
GET /api/v1/cms/promo-slots
GET /api/v1/cms/promo-slots/{id}
GET /api/v1/cms/promo-slots/code/{code}GET /api/v1/cms/home 是首页聚合接口,一次返回轮播图、导航、公告、内容块、专题和广告位,适合 App / PC 首页首屏减少多次请求:
GET /api/v1/cms/home?client=app&bannerSize=5&navigationSize=10&announcementSize=5&blockSize=8&topicSize=5&adSlotSize=5返回结构:
{
"banners": [],
"navigations": [],
"announcements": [],
"blocks": [],
"topics": [],
"adSlots": []
}列表接口支持:
page=1
size=10
keyword=home
parentId=1/banners 和 /navigations 额外支持 client=app|pc|admin,返回当前客户端内容和 all 通用内容;按 code 查询详情时优先返回当前客户端内容,找不到时回退 all 通用内容。未传 client 时会自动按请求头推断客户端。其他列表和详情接口不需要传客户端。
公共接口只返回已启用、已到可见时间且未过期的内容。 如果内容启用了审核,公共接口只返回审核通过或历史未设置审核状态的内容。
GET /api/v1/cms/sitemap 返回 CMS 已发布内容的站点地图条目:
GET /api/v1/cms/sitemap?size=200返回字段:
| 字段 | 说明 |
|---|---|
type / code | 内容类型和稳定业务编码 |
path | 相对路径;内容配置了本地 url 时优先使用,否则回退为 /cms/{type}/{code} |
title / displayTitle | 原始标题和后端按当前语言解析后的标题 |
seoTitle / seoDescription / seoCanonicalUrl / seoRobots | SEO 元信息 |
visibleAt / updatedAt | 展示时间和更新时间 |
该接口不绑定域名,前端 SSR、站点层或 Nginx 部署可以按当前域名拼出最终 sitemap URL。
GET /api/v1/cms/contents/path 用于按站点地图返回的 path 反查内容详情,适合前端站点层统一处理动态内容页:
GET /api/v1/cms/contents/path?path=/cms/page/about
GET /api/v1/cms/contents/path?path=/terms规则:
/cms/{type}/{code}会解析为对应内容类型和稳定业务编码,等价于调用对应/code/{code}详情接口。- 内容配置了本地相对
url时,也可以直接通过该相对路径查询,例如/terms。 - 只接受以
/开头的站内相对路径,不接受外部 URL。 banner/navigation可继续通过client=app|pc|admin或请求头自动识别客户端,并按当前客户端优先、all兜底。
前端详情页优先使用 code 路由,例如 GET /api/v1/cms/channels/code/news、 GET /api/v1/cms/pages/code/about 或 GET /api/v1/cms/blocks/code/home.hero。id 详情保留给后台跳转、列表项临时跳转或 App / PC 已拿到列表数据后的详情兜底,公开页面不要把数据库 ID 作为稳定 URL。
后台管理接口
第一阶段后台使用统一内容管理入口:
GET /api/v1/admin/cms/contents
GET /api/v1/admin/cms/contents/export
GET /api/v1/admin/cms/contents/{id}
GET /api/v1/admin/cms/contents/{id}/versions
POST /api/v1/admin/cms/contents/preview
POST /api/v1/admin/cms/contents
PUT /api/v1/admin/cms/contents/{id}
PUT /api/v1/admin/cms/contents/{id}/status
POST /api/v1/admin/cms/contents/{id}/review/submit
POST /api/v1/admin/cms/contents/{id}/review/approve
POST /api/v1/admin/cms/contents/{id}/review/reject
DELETE /api/v1/admin/cms/contents/{id}
DELETE /api/v1/admin/cms/contents导出接口复用后台内容列表筛选条件:
GET /api/v1/admin/cms/contents/export?type=page&keyword=about&reviewStatus=approved响应为 text/csv;charset=UTF-8 附件,字段包含内容 ID、类型、客户端、编码、标题、摘要、链接、图片、排序、状态、审核状态、展示时间、SEO 和创建 / 更新时间等运营元数据。导出不会输出正文 content、扩展 JSON、审核备注或操作日志内部字段,避免绕过内容权限或泄露后台审计备注。
内容操作日志导出接口复用后台日志列表的筛选条件:
GET /api/v1/admin/cms/content-operation-logs/export?contentType=page&action=update响应为 text/csv;charset=UTF-8 附件,字段包含日志 ID、内容关联、操作人、动作、状态变更、标题和创建 / 更新时间。导出不会输出 remark 或 extraJson,避免绕过后台审计权限泄露内部备注和工作流扩展快照。
内容版本接口用于查看内容创建和正文 / SEO / 发布设置修改时留下的快照:
GET /api/v1/admin/cms/contents/{id}/versions?page=1&size=20版本记录写入 cms_content_version,包含版本号、变更类型、内容类型、客户端、编码、标题、摘要、正文、链接、图片、排序、发布状态、审核状态、完整 SEO 字段、扩展 JSON、操作人和发生时间。该接口只读,复用 cms:content:view 权限;回滚和跨模块统一待办聚合只做只读展示,不在当前接口里隐式执行,避免绕过 CMS 审核 / 发布状态机。
预览接口用于后台编辑表单在保存前生成和前台一致的展示结果,不写入数据库,也不占用内容编码:
POST /api/v1/admin/cms/contents/preview该接口返回 CmsContentVO,包含 displayTitle、displaySummary、displayContent 和结构化 SEO 元信息等后端按当前语言解析后的展示字段。前端可以用它实现内容预览、SEO 预览和多语言文案预览。
状态处理接口用于发布、下线或调整展示时间窗口:
{
"status": 1,
"visibleAt": "2026-05-30T10:00:00",
"expiredAt": "2026-06-30T23:59:59"
}status=1 表示启用,status=0 表示停用。公共接口只展示启用、到达 visibleAt 且未超过 expiredAt 的内容。
定时发布不需要额外任务调度:后台把内容状态设为启用,并填写未来的 visibleAt,公共接口会在该时间前自动隐藏,到达时间后自动展示。expiredAt 可用于活动结束、公告过期或广告位下线。
审核接口用于需要人工确认的内容:
POST /api/v1/admin/cms/contents/{id}/review/submit
POST /api/v1/admin/cms/contents/{id}/review/approve
POST /api/v1/admin/cms/contents/{id}/review/reject请求体可选:
{
"remark": "审核意见"
}驳回动作必须填写 remark,用于说明驳回原因;提交审核和审核通过可以不填。
审核状态:
| 状态 | 说明 |
|---|---|
approved | 已通过;公共接口可展示 |
pending | 待审核;公共接口不展示 |
rejected | 已驳回;公共接口不展示 |
默认新内容为 approved,需要严格审核时再把 spring.open.cms.review.default-status 配置成 pending。
CMS 内容审核已接入 spring-open-starter-approval-workflow,流程编码为 cms.content.review:
| 动作 | 来源状态 | 目标状态 | 权限 |
|---|---|---|---|
submit | approved / rejected / pending | pending | cms:content:update |
approve | pending | approved | cms:content:review |
reject | pending | rejected | cms:content:review |
auto-approve | pending | approved | 调度任务 |
auto-reject | pending | rejected | 调度任务 |
工作流负责校验动作、来源状态、权限快照、备注要求和超时计划;CMS 负责把结果写回 cms_content.review_status、reviewed_by、reviewed_at、review_remark,并写入内容操作日志。
审核执行已接入 Hook / Pipeline / Chain 扩展点。提交审核会触发 Hook cms.content.review.submit、Pipeline cms.content.review.submit.precheck 和 Chain cms.content.review.submit.rules;审核完成(人工通过 / 驳回、自动通过 / 驳回)会触发 Hook cms.content.review.finish、Pipeline cms.content.review.finish.precheck 和 Chain cms.content.review.finish.rules。CMS 内置规则会校验内容 ID、类型、编码、标题、发布状态、审核状态和审核动作,业务项目可以继续追加自定义 Pipeline Step 或 Chain Rule 在审批工作流执行前阻断。
待审核内容如果长时间没有人工处理,会由 cms.content.review.auto-handle 调度任务按 spring.open.cms.review.auto.action 自动落为 approved 或 rejected。自动处理同样会写内容操作日志,便于后台追踪。
人工驳回或自动驳回成功后会发布 cms.content.review-rejected 通知规则事件源。CMS 模块只发布领域事件,application 装配层按 Notification 通知规则发送站内信 / 邮件;payload 会携带内容 ID、类型、编码、标题、创建人、审核人、驳回原因、操作动作和发生时间。未配置运行时规则或规则没有接收人时静默跳过,不影响审核状态落库。
提交审核产生的操作日志会带上工作流审计摘要,extraJson 至少包含 workflow、action、reviewStatusBefore、reviewStatusAfter;如果工作流生成了超时计划,还会包含 timeoutAction 和 timeoutDueAt。后台仍通过内容操作日志接口查看,不需要额外调用 starter。
Admin 内容管理页已接入预览、保存、发布 / 下线、提交审核、审核通过、审核驳回和内容操作日志抽屉;CMS 运维工作台可以按类型、状态、审核状态和关键词筛选内容,并展示投放窗口,直接处理投放状态、审核动作和审计查看。后台内容分页支持 reviewStatus=approved|pending|rejected,可直接拉取待审核或驳回队列。
权限标识:
cms:content:view
cms:content:create
cms:content:update
cms:content:review
cms:content:delete提交审核复用 cms:content:update;通过 / 驳回使用 cms:content:review。
操作日志
CMS 会在创建、修改、发布 / 下线和删除内容时记录操作日志,用于后台审计和排查运营变更。
GET /api/v1/admin/cms/content-operation-logs
GET /api/v1/admin/cms/content-operation-logs/export
GET /api/v1/admin/cms/content-operation-logs/{id}
DELETE /api/v1/admin/cms/content-operation-logs/{id}
DELETE /api/v1/admin/cms/content-operation-logs查询参数:
| 参数 | 说明 |
|---|---|
contentId | 内容 ID |
contentType | 内容类型 |
contentCode | 内容编码 |
operatorId | 操作人 ID |
action | 操作动作:create、update、status-update、submit-review、approve-review、reject-review、auto-approve-review、auto-reject-review、delete |
日志查看复用 cms:content:view 权限;日志删除复用 cms:content:delete 权限。普通用户侧不会暴露这些内部审计字段。
与前端视图联动
PC / App 视图可以先接入:
GET /api/v1/cms/home
GET /api/v1/cms/blocks
GET /api/v1/cms/banners
GET /api/v1/cms/navigations
GET /api/v1/cms/announcements首页推荐位、轮播图、公告栏和导航入口优先使用 home 聚合接口读取;如果页面需要独立分页、搜索或局部刷新,再使用对应类型列表接口。后台管理界面优先使用统一 admin/contents,内容模型稳定后再按业务复杂度拆成更精细的栏目、页面、公告、轮播图、导航、专题和广告位管理页。
公开用户动态
CMS 模块通过 core UserActivitySource 向 IAM 用户动态聚合出口贡献当前用户创建的已发布内容。动态类型为 cms.content,targetPath 使用产品内路由 /cms/{type}/{code},url 会保留内容配置的直达 URL 或回退到同一路由;CMS 仍不依赖 IAM 实现,只通过中性 contract 贡献来源。
后续方向
- 跨 CMS / Blog / Forum 的只读统一待办聚合已通过 Approval Workflow
ApprovalInboxcontract 落地;后续如需要可继续补统一后台工作台、分派和处理 SLA。 - 根据真实业务复杂度拆分独立表或附属扩展表。