跳到内容

CMS

受众:开发者 摘要:spring-open-module-cms 内容管理:页面 / 公告 / 轮播图 / 导航 / 内容块 / 专题 / 广告位。

CMS 模块用于承载官网、用户端首页和运营内容。第一阶段采用轻量内容项模型,先提供公共只读能力和统一后台内容管理入口,后续再按业务复杂度拆分更细的栏目、页面、公告、轮播图、导航、专题和广告位模型。

模块可选依赖,应用不需要 CMS 时可以移除 spring-open-module-cms 引用,不影响核心模块、Open Platform 或其他模块。

配置

yaml
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: 100

spring.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所有客户端通用
appApp 移动端
pcPC 电脑端
adminAdmin 后台端

该分端能力只用于轮播图和导航。页面、公告、内容块、专题等需要渲染正文或富文本的内容保持通用,不按客户端拆分。

显式 client 参数优先级最高。未传 client 时,后端会按 X-ClientX-PlatformSec-CH-UA-MobileUser-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 约定:

properties
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搜索结果标题
seoKeywordsSEO 关键词,多个关键词可用英文逗号分隔
seoDescription搜索结果摘要
seoCanonicalUrl搜索引擎规范 URL
seoRobots索引策略,例如 index,follownoindex,nofollow
seoOgTitleOpenGraph 社交分享标题
seoOgDescriptionOpenGraph 社交分享摘要
seoOgImageUrlOpenGraph 社交分享图片 URL
seoStructuredDataJsonJSON-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>

http
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 首页首屏减少多次请求:

http
GET /api/v1/cms/home?client=app&bannerSize=5&navigationSize=10&announcementSize=5&blockSize=8&topicSize=5&adSlotSize=5

返回结构:

json
{
  "banners": [],
  "navigations": [],
  "announcements": [],
  "blocks": [],
  "topics": [],
  "adSlots": []
}

列表接口支持:

http
page=1
size=10
keyword=home
parentId=1

/banners/navigations 额外支持 client=app|pc|admin,返回当前客户端内容和 all 通用内容;按 code 查询详情时优先返回当前客户端内容,找不到时回退 all 通用内容。未传 client 时会自动按请求头推断客户端。其他列表和详情接口不需要传客户端。

公共接口只返回已启用、已到可见时间且未过期的内容。 如果内容启用了审核,公共接口只返回审核通过或历史未设置审核状态的内容。

GET /api/v1/cms/sitemap 返回 CMS 已发布内容的站点地图条目:

http
GET /api/v1/cms/sitemap?size=200

返回字段:

字段说明
type / code内容类型和稳定业务编码
path相对路径;内容配置了本地 url 时优先使用,否则回退为 /cms/{type}/{code}
title / displayTitle原始标题和后端按当前语言解析后的标题
seoTitle / seoDescription / seoCanonicalUrl / seoRobotsSEO 元信息
visibleAt / updatedAt展示时间和更新时间

该接口不绑定域名,前端 SSR、站点层或 Nginx 部署可以按当前域名拼出最终 sitemap URL。

GET /api/v1/cms/contents/path 用于按站点地图返回的 path 反查内容详情,适合前端站点层统一处理动态内容页:

http
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/newsGET /api/v1/cms/pages/code/aboutGET /api/v1/cms/blocks/code/home.heroid 详情保留给后台跳转、列表项临时跳转或 App / PC 已拿到列表数据后的详情兜底,公开页面不要把数据库 ID 作为稳定 URL。

后台管理接口

第一阶段后台使用统一内容管理入口:

http
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

导出接口复用后台内容列表筛选条件:

http
GET /api/v1/admin/cms/contents/export?type=page&keyword=about&reviewStatus=approved

响应为 text/csv;charset=UTF-8 附件,字段包含内容 ID、类型、客户端、编码、标题、摘要、链接、图片、排序、状态、审核状态、展示时间、SEO 和创建 / 更新时间等运营元数据。导出不会输出正文 content、扩展 JSON、审核备注或操作日志内部字段,避免绕过内容权限或泄露后台审计备注。

内容操作日志导出接口复用后台日志列表的筛选条件:

http
GET /api/v1/admin/cms/content-operation-logs/export?contentType=page&action=update

响应为 text/csv;charset=UTF-8 附件,字段包含日志 ID、内容关联、操作人、动作、状态变更、标题和创建 / 更新时间。导出不会输出 remarkextraJson,避免绕过后台审计权限泄露内部备注和工作流扩展快照。

内容版本接口用于查看内容创建和正文 / SEO / 发布设置修改时留下的快照:

http
GET /api/v1/admin/cms/contents/{id}/versions?page=1&size=20

版本记录写入 cms_content_version,包含版本号、变更类型、内容类型、客户端、编码、标题、摘要、正文、链接、图片、排序、发布状态、审核状态、完整 SEO 字段、扩展 JSON、操作人和发生时间。该接口只读,复用 cms:content:view 权限;回滚和跨模块统一待办聚合只做只读展示,不在当前接口里隐式执行,避免绕过 CMS 审核 / 发布状态机。

预览接口用于后台编辑表单在保存前生成和前台一致的展示结果,不写入数据库,也不占用内容编码:

http
POST /api/v1/admin/cms/contents/preview

该接口返回 CmsContentVO,包含 displayTitledisplaySummarydisplayContent 和结构化 SEO 元信息等后端按当前语言解析后的展示字段。前端可以用它实现内容预览、SEO 预览和多语言文案预览。

状态处理接口用于发布、下线或调整展示时间窗口:

json
{
  "status": 1,
  "visibleAt": "2026-05-30T10:00:00",
  "expiredAt": "2026-06-30T23:59:59"
}

status=1 表示启用,status=0 表示停用。公共接口只展示启用、到达 visibleAt 且未超过 expiredAt 的内容。

定时发布不需要额外任务调度:后台把内容状态设为启用,并填写未来的 visibleAt,公共接口会在该时间前自动隐藏,到达时间后自动展示。expiredAt 可用于活动结束、公告过期或广告位下线。

审核接口用于需要人工确认的内容:

http
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

请求体可选:

json
{
  "remark": "审核意见"
}

驳回动作必须填写 remark,用于说明驳回原因;提交审核和审核通过可以不填。

审核状态:

状态说明
approved已通过;公共接口可展示
pending待审核;公共接口不展示
rejected已驳回;公共接口不展示

默认新内容为 approved,需要严格审核时再把 spring.open.cms.review.default-status 配置成 pending

CMS 内容审核已接入 spring-open-starter-approval-workflow,流程编码为 cms.content.review

动作来源状态目标状态权限
submitapproved / rejected / pendingpendingcms:content:update
approvependingapprovedcms:content:review
rejectpendingrejectedcms:content:review
auto-approvependingapproved调度任务
auto-rejectpendingrejected调度任务

工作流负责校验动作、来源状态、权限快照、备注要求和超时计划;CMS 负责把结果写回 cms_content.review_statusreviewed_byreviewed_atreview_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 自动落为 approvedrejected。自动处理同样会写内容操作日志,便于后台追踪。

人工驳回或自动驳回成功后会发布 cms.content.review-rejected 通知规则事件源。CMS 模块只发布领域事件,application 装配层按 Notification 通知规则发送站内信 / 邮件;payload 会携带内容 ID、类型、编码、标题、创建人、审核人、驳回原因、操作动作和发生时间。未配置运行时规则或规则没有接收人时静默跳过,不影响审核状态落库。

提交审核产生的操作日志会带上工作流审计摘要,extraJson 至少包含 workflowactionreviewStatusBeforereviewStatusAfter;如果工作流生成了超时计划,还会包含 timeoutActiontimeoutDueAt。后台仍通过内容操作日志接口查看,不需要额外调用 starter。

Admin 内容管理页已接入预览、保存、发布 / 下线、提交审核、审核通过、审核驳回和内容操作日志抽屉;CMS 运维工作台可以按类型、状态、审核状态和关键词筛选内容,并展示投放窗口,直接处理投放状态、审核动作和审计查看。后台内容分页支持 reviewStatus=approved|pending|rejected,可直接拉取待审核或驳回队列。

权限标识:

txt
cms:content:view
cms:content:create
cms:content:update
cms:content:review
cms:content:delete

提交审核复用 cms:content:update;通过 / 驳回使用 cms:content:review

操作日志

CMS 会在创建、修改、发布 / 下线和删除内容时记录操作日志,用于后台审计和排查运营变更。

http
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操作动作:createupdatestatus-updatesubmit-reviewapprove-reviewreject-reviewauto-approve-reviewauto-reject-reviewdelete

日志查看复用 cms:content:view 权限;日志删除复用 cms:content:delete 权限。普通用户侧不会暴露这些内部审计字段。

与前端视图联动

PC / App 视图可以先接入:

http
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.contenttargetPath 使用产品内路由 /cms/{type}/{code}url 会保留内容配置的直达 URL 或回退到同一路由;CMS 仍不依赖 IAM 实现,只通过中性 contract 贡献来源。

后续方向

  • 跨 CMS / Blog / Forum 的只读统一待办聚合已通过 Approval Workflow ApprovalInbox contract 落地;后续如需要可继续补统一后台工作台、分派和处理 SLA。
  • 根据真实业务复杂度拆分独立表或附属扩展表。

Released under the Apache License 2.0.