# Novalon CMS 自研系统实现计划 > **面向 AI 代理的工作者:** 必需子技能:使用 superpowers:subagent-driven-development(推荐)或 superpowers:executing-plans 逐任务实现此计划。步骤使用复选框(`- [ ]`)语法来跟踪进度。 **目标:** 构建与 Novalon 技术栈深度融合的自研 Headless CMS 系统,实现内容模型定义、内容管理、动态页面渲染、主题配置等核心能力。 **架构:** 采用 Broadleaf CMS 核心设计思想(ContentModel + ContentItem + ContentZone + ThemeField),基于 Java 21 + Spring Boot + PostgreSQL 构建后端服务,React + ProLayout 构建管理后台,Next.js 前端通过统一 SDK 接入。前端优先实现 SDK 与动态渲染层,后端 API 按接口契约并行开发。 **技术栈:** - 后端:Java 21 + Spring Boot WebFlux + PostgreSQL + Flyway + Spring Security - 管理端:React 18 + ProLayout + Zustand + Ant Design 5(对齐 novalon-manage-system) - 前端网站:Next.js 14 + TypeScript + ISR/SSG - 数据校验:Zod(前端)+ Jakarta Validation(后端) --- ## 一、系统架构总览 ### 1.1 整体架构图 ``` ┌─────────────────────────────────────────────────────────────┐ │ 前端展示层(Website) │ │ Next.js 14 + CMS SDK + 动态渲染器 │ └──────────────────────────────┬──────────────────────────────┘ │ RESTful API ┌──────────────────────────────▼──────────────────────────────┐ │ CMS 服务层(Backend) │ │ Java 21 + Spring Boot WebFlux + PostgreSQL + Flyway │ │ │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌─────────────┐ │ │ │ 内容模型 │ │ 内容项 │ │ 内容区域 │ │ 主题配置 │ │ │ │ 管理 │ │ 管理 │ │ 管理 │ │ 管理 │ │ │ └──────────┘ └──────────┘ └──────────┘ └─────────────┘ │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌─────────────┐ │ │ │ 媒体资源 │ │ 版本管理 │ │ 审计日志 │ │ 权限/SSO │ │ │ │ 管理 │ │ 与草稿 │ │ │ │ 集成 │ │ │ └──────────┘ └──────────┘ └──────────┘ └─────────────┘ │ └──────────────────────────────┬──────────────────────────────┘ │ ┌──────────────────────────────▼──────────────────────────────┐ │ 管理后台(Admin) │ │ React 18 + ProLayout + Zustand + Ant Design 5 │ │ (对齐 novalon-manage-system 架构规范) │ └─────────────────────────────────────────────────────────────┘ ``` ### 1.2 核心领域模型(源自 Broadleaf CMS 设计) | 模型 | 表名 | 职责 | 对应 Broadleaf 概念 | |------|------|------|-------------------| | ContentModel | `cms_content_model` | 定义内容类型的 schema(字段、类型、校验) | ContentModel | | ContentItem | `cms_content_item` | 实际的内容数据,基于 model 动态字段 | ContentItem | | ContentZone | `cms_content_zone` | 页面中的可编辑区域定义 | ContentZone | | ThemeConfig | `cms_theme_config` | 站点主题、设置、SEO 等配置 | ThemeField | | MediaAsset | `cms_media_asset` | 媒体资源管理 | - | | ContentVersion | `cms_content_version` | 内容版本与草稿 | - | | AuditLog | `cms_audit_log` | 操作审计日志 | - | --- ## 二、文件结构设计 ### 2.1 当前项目(novalon-website)新增文件 ``` src/ ├── lib/ │ └── cms/ # CMS 前端 SDK │ ├── types.ts # 类型定义(ContentModel/ContentItem 等) │ ├── client.ts # API 客户端封装 │ ├── renderer.ts # 内容渲染工具函数 │ └── index.ts # barrel 导出 ├── components/ │ └── cms/ # CMS 渲染组件 │ ├── ContentRenderer.tsx # 动态内容渲染器 │ ├── SectionRenderer.tsx # Section 级渲染器(ContentZone) │ ├── FieldRenderer.tsx # 字段级渲染器 │ └── index.ts └── app/ └── api/ └── cms/ # 前端 BFF 层(可选) └── [...path]/route.ts ``` ### 2.2 后端服务(novalon-cms,独立项目) ``` novalon-cms/ ├── cms-gateway/ # API 网关模块 ├── cms-common/ # 公共工具模块 ├── cms-db/ # 数据访问层(实体 + Repository) ├── cms-content/ # 内容管理模块(model/item/zone) ├── cms-media/ # 媒体资源模块 ├── cms-theme/ # 主题配置模块 ├── cms-admin/ # 管理端 API └── cms-sys/ # 系统管理(用户/权限/审计) ``` ### 2.3 管理后台(novalon-manage-system 扩展) ``` src/ ├── pages/ │ └── cms/ │ ├── content-model/ # 内容模型管理 │ ├── content-item/ # 内容项管理 │ ├── content-zone/ # 内容区域管理 │ ├── media/ # 媒体库 │ └── theme/ # 主题配置 ├── stores/ │ └── cms.ts # CMS 状态管理 └── services/ └── cms.ts # CMS API 服务 ``` --- ## 三、阶段划分与里程碑 ### 阶段一:前端 SDK + 动态渲染(当前项目,立即启动) **目标:** 在当前 Next.js 项目中建立 CMS 接入层,定义完整的类型系统和渲染架构,为后端 API 开发提供契约。 **交付物:** - CMS 类型定义系统 - API Client SDK(带 mock 数据支持) - 动态 Section 渲染器 - 案例模块迁移试点(从 constants → CMS 数据结构) ### 阶段二:后端核心服务(Java 团队并行) **目标:** 实现 CMS 核心数据模型和 CRUD API。 **交付物:** - 数据库表结构 + Flyway 迁移脚本 - ContentModel CRUD API - ContentItem CRUD API + 版本管理 - ContentZone API - 权限集成 + 审计日志 ### 阶段三:管理后台开发 **目标:** 在 novalon-manage-system 中扩展 CMS 管理模块。 **交付物:** - 内容模型可视化编辑器 - 内容项编辑器(动态表单) - 媒体资源管理 - 主题配置面板 ### 阶段四:全链路联调 + 内容迁移 **目标:** 前后端打通,完成所有内容模块迁移。 **交付物:** - 全模块 CMS 接入 - 内容迁移脚本 - ISR/SSG 缓存策略 - 上线 --- ## 四、详细任务清单 ### 任务 1:CMS 类型系统定义 **文件:** - 创建:`src/lib/cms/types.ts` - 创建:`src/lib/cms/index.ts` **内容:** 定义以下核心类型 ```typescript // 字段类型枚举 export type FieldType = | 'text' // 单行文本 | 'textarea' // 多行文本 | 'richtext' // 富文本 | 'number' // 数字 | 'boolean' // 布尔 | 'date' // 日期 | 'datetime' // 日期时间 | 'image' // 图片 | 'media' // 媒体资源 | 'reference' // 引用其他内容项 | 'references' // 引用多个内容项 | 'json' // JSON 结构 | 'array' // 数组 | 'object'; // 嵌套对象 // 字段定义 export interface FieldDefinition { name: string; type: FieldType; label: string; required?: boolean; description?: string; placeholder?: string; defaultValue?: unknown; validation?: { min?: number; max?: number; pattern?: string; custom?: string; }; options?: Array<{ label: string; value: string | number | boolean }>; referenceModel?: string; // reference 类型关联的 model code ui?: { component?: string; // 指定 UI 组件 width?: 'full' | 'half' | 'third'; helpText?: string; }; fields?: FieldDefinition[]; // object/array 类型的嵌套字段 } // 内容模型 export interface ContentModel { id: string; code: string; // 唯一标识,如 'case-study' name: string; description?: string; fields: FieldDefinition[]; isPageType: boolean; // 是否为页面类型(支持 URL 路由) urlPattern?: string; // URL 模式,如 '/cases/{slug}' hasVersions: boolean; // 是否启用版本管理 hasDraft: boolean; // 是否启用草稿 icon?: string; createdAt: string; updatedAt: string; } // 内容状态 export type ContentStatus = 'draft' | 'review' | 'published' | 'archived'; // 内容项 export interface ContentItem { id: string; modelId: string; modelCode: string; title: string; slug?: string; status: ContentStatus; data: Record; // 动态字段数据,结构由 model 定义 version: number; publishedAt?: string; createdBy: string; updatedBy: string; createdAt: string; updatedAt: string; } // 内容区域 export interface ContentZone { id: string; code: string; // 区域唯一标识,如 'home-hero' name: string; description?: string; pageCode?: string; // 所属页面 allowedModels: string[]; // 允许的内容模型 codes items: Array<{ itemId: string; item?: ContentItem; sortOrder: number; config?: Record; // 区域级配置 }>; } // 主题配置 export interface ThemeConfig { id: string; siteName: string; siteDescription: string; logo?: string; favicon?: string; colors: { primary: string; secondary: string; accent: string; background: string; text: string; }; seo: { defaultTitle: string; defaultDescription: string; defaultKeywords: string; ogImage?: string; }; social: { wechat?: string; weibo?: string; linkedin?: string; github?: string; }; settings: Record; // 其他自定义设置 } // 分页查询参数 export interface ContentQueryParams { modelCode: string; status?: ContentStatus; page?: number; pageSize?: number; sortBy?: string; sortOrder?: 'asc' | 'desc'; filters?: Record; search?: string; } // 分页结果 export interface PaginatedResult { items: T[]; total: number; page: number; pageSize: number; totalPages: number; } ``` --- ### 任务 2:CMS API Client SDK **文件:** - 创建:`src/lib/cms/client.ts` - 修改:`src/lib/cms/index.ts` **内容:** 封装 CMS API 客户端,支持真实 API 和 mock 模式 ```typescript // 配置 interface CmsClientConfig { baseUrl: string; apiKey?: string; timeout?: number; mockMode?: boolean; // 开发阶段使用 mock 数据 } // 客户端类 class CmsClient { constructor(config: CmsClientConfig) {} // 内容模型相关 async getModels(): Promise {} async getModel(code: string): Promise {} // 内容项相关 async getItems(params: ContentQueryParams): Promise> {} async getItem(id: string): Promise {} async getItemBySlug(modelCode: string, slug: string): Promise {} // 内容区域相关 async getZone(code: string): Promise {} async getZonesByPage(pageCode: string): Promise {} // 主题配置相关 async getThemeConfig(): Promise {} // 工具方法 async withType>( item: ContentItem ): Promise {} } ``` --- ### 任务 3:动态 Section 渲染器(ContentZone 实现) **文件:** - 创建:`src/components/cms/SectionRenderer.tsx` - 创建:`src/components/cms/FieldRenderer.tsx` - 创建:`src/components/cms/ContentRenderer.tsx` - 创建:`src/components/cms/index.ts` **核心设计思想:** - ContentZone = 页面上的一个可编辑区域 - 每个 Zone 包含多个 ContentItem - 每个 ContentItem 根据其 model type 选择对应的渲染组件 ```typescript // 组件注册表 interface SectionComponentRegistry { [modelCode: string]: React.ComponentType<{ data: any; config?: any }>; } // SectionRenderer: 渲染单个 ContentZone function SectionRenderer({ zoneCode, fallback }: { zoneCode: string; fallback?: React.ReactNode; }) {} // ContentRenderer: 渲染单个 ContentItem function ContentRenderer({ item }: { item: ContentItem }) {} // FieldRenderer: 渲染单个字段 function FieldRenderer({ field, value }: { field: FieldDefinition; value: unknown; }) {} ``` --- ### 任务 4:案例模块迁移试点 **目标:** 将现有的案例数据从 `constants/cases.ts` 迁移为 CMS 数据结构,验证类型系统和渲染器的正确性。 **文件:** - 修改:`src/lib/cms/mock-data.ts`(新建 mock 数据) - 修改:`src/app/(marketing)/cases/cases-content-v1.tsx` - 修改:`src/app/(marketing)/cases/[slug]/client.tsx` **迁移内容:** - 定义 `case-study` 内容模型(对应 CaseStudy 接口) - 将 6 个案例转为 ContentItem 格式 - 列表页和详情页改为从 CMS Client 获取数据 - 保留 mock 模式,后端 API 完成后无缝切换 --- ### 任务 5:后端 API 接口契约 **文件:** - 创建:`docs/cms/api-contract.md` **核心 API 列表:** | 模块 | Method | Path | 说明 | |------|--------|------|------| | 内容模型 | GET | `/api/cms/models` | 获取所有内容模型 | | 内容模型 | GET | `/api/cms/models/{code}` | 获取单个模型定义 | | 内容模型 | POST | `/api/cms/models` | 创建内容模型 | | 内容模型 | PUT | `/api/cms/models/{code}` | 更新内容模型 | | 内容项 | GET | `/api/cms/items` | 分页查询内容项 | | 内容项 | GET | `/api/cms/items/{id}` | 获取内容项详情 | | 内容项 | GET | `/api/cms/items/slug/{modelCode}/{slug}` | 按 slug 获取 | | 内容项 | POST | `/api/cms/items` | 创建内容项 | | 内容项 | PUT | `/api/cms/items/{id}` | 更新内容项 | | 内容项 | POST | `/api/cms/items/{id}/publish` | 发布内容 | | 内容项 | POST | `/api/cms/items/{id}/unpublish` | 下架内容 | | 内容区域 | GET | `/api/cms/zones/{code}` | 获取区域内容 | | 内容区域 | PUT | `/api/cms/zones/{code}` | 更新区域配置 | | 主题配置 | GET | `/api/cms/theme` | 获取主题配置 | | 主题配置 | PUT | `/api/cms/theme` | 更新主题配置 | | 媒体资源 | GET | `/api/cms/media` | 媒体列表 | | 媒体资源 | POST | `/api/cms/media/upload` | 上传媒体 | --- ## 五、数据模型详细设计 ### 5.1 cms_content_model(内容模型表) | 字段 | 类型 | 说明 | |------|------|------| | id | bigint PK | 主键 | | code | varchar(100) UNIQUE | 模型唯一编码 | | name | varchar(200) | 模型名称 | | description | varchar(500) | 描述 | | fields_json | jsonb | 字段定义 JSON | | is_page_type | boolean | 是否为页面类型 | | url_pattern | varchar(200) | URL 模式 | | has_versions | boolean | 是否启用版本 | | has_draft | boolean | 是否启用草稿 | | icon | varchar(50) | 图标 | | created_by | varchar(100) | 创建人 | | updated_by | varchar(100) | 更新人 | | created_at | timestamp | 创建时间 | | updated_at | timestamp | 更新时间 | | deleted | boolean | 逻辑删除 | ### 5.2 cms_content_item(内容项表) | 字段 | 类型 | 说明 | |------|------|------| | id | bigint PK | 主键 | | model_id | bigint FK | 关联模型 ID | | model_code | varchar(100) | 冗余模型编码 | | title | varchar(500) | 内容标题 | | slug | varchar(200) | URL slug | | status | varchar(20) | 状态:draft/review/published/archived | | data_json | jsonb | 内容数据 JSON | | version | int | 当前版本号 | | published_at | timestamp | 发布时间 | | created_by | varchar(100) | 创建人 | | updated_by | varchar(100) | 更新人 | | created_at | timestamp | 创建时间 | | updated_at | timestamp | 更新时间 | | deleted | boolean | 逻辑删除 | 索引:`(model_code, status, created_at)`、`(model_code, slug)` 唯一索引 ### 5.3 cms_content_version(内容版本表) | 字段 | 类型 | 说明 | |------|------|------| | id | bigint PK | 主键 | | item_id | bigint FK | 内容项 ID | | version | int | 版本号 | | data_json | jsonb | 版本数据快照 | | status | varchar(20) | 版本状态 | | change_log | varchar(500) | 变更说明 | | created_by | varchar(100) | 创建人 | | created_at | timestamp | 创建时间 | ### 5.4 cms_content_zone(内容区域表) | 字段 | 类型 | 说明 | |------|------|------| | id | bigint PK | 主键 | | code | varchar(100) UNIQUE | 区域编码 | | name | varchar(200) | 区域名称 | | description | varchar(500) | 描述 | | page_code | varchar(100) | 所属页面 | | allowed_models | varchar(500) | 允许的模型 codes(逗号分隔) | | items_json | jsonb | 区域内内容项配置(顺序、附加配置) | | created_at | timestamp | 创建时间 | | updated_at | timestamp | 更新时间 | ### 5.5 cms_theme_config(主题配置表) | 字段 | 类型 | 说明 | |------|------|------| | id | bigint PK | 主键(单表单记录,id=1) | | config_json | jsonb | 完整配置 JSON | | updated_by | varchar(100) | 更新人 | | updated_at | timestamp | 更新时间 | ### 5.6 cms_media_asset(媒体资源表) | 字段 | 类型 | 说明 | |------|------|------| | id | bigint PK | 主键 | | name | varchar(200) | 文件名称 | | path | varchar(500) | 存储路径 | | url | varchar(500) | 访问 URL | | mime_type | varchar(100) | MIME 类型 | | size | bigint | 文件大小(字节) | | width | int | 图片宽度 | | height | int | 图片高度 | | alt | varchar(200) | 替代文本 | | storage_type | varchar(20) | 存储类型:local/oss/s3 | | created_by | varchar(100) | 创建人 | | created_at | timestamp | 创建时间 | ### 5.7 cms_audit_log(审计日志表) | 字段 | 类型 | 说明 | |------|------|------| | id | bigint PK | 主键 | | module | varchar(50) | 模块:model/item/zone/theme/media | | target_id | bigint | 操作对象 ID | | action | varchar(50) | 操作:create/update/delete/publish/unpublish | | operator | varchar(100) | 操作人 | | before_json | jsonb | 操作前数据 | | after_json | jsonb | 操作后数据 | | ip | varchar(50) | IP 地址 | | user_agent | varchar(500) | User Agent | | created_at | timestamp | 操作时间 | --- ## 六、安全与权限设计 ### 6.1 权限模型 | 角色 | 权限说明 | |------|----------| | 超级管理员 | 所有权限 | | CMS 管理员 | 内容模型管理、内容管理、用户管理 | | 内容编辑 | 内容创建、编辑、提交审核 | | 内容审核 | 内容审核、发布、下架 | | 访客 | 仅查看(前端 API) | ### 6.2 API 安全 - 管理端 API:走现有 SSO + 权限体系 - 前端 API:仅读取 published 状态内容,无需鉴权(或 API Key 限流) - 敏感操作:写入操作必须有审计日志 - 数据隔离:按业务线/租户隔离(预留) --- ## 七、性能优化策略 ### 7.1 前端侧 - **ISR/SSG**:内容页使用 Next.js ISR,内容变更时触发 revalidate - **缓存策略**: - 内容列表:10 分钟缓存 + stale-while-revalidate - 内容详情:1 小时缓存,发布时主动失效 - 主题配置:构建时注入,运行时热更新 - **图片优化**:使用 Next.js Image + CDN 加速 ### 7.2 后端侧 - **数据库索引**:按查询模式建立合理索引 - **Redis 缓存**:热点内容数据缓存 - **读写分离**:读操作走从库 - **CDN 加速**:媒体资源走 CDN --- ## 八、与现有系统的集成 ### 8.1 用户/权限集成 - 复用 novalon-manage-system 的用户体系和权限框架 - SSO 单点登录 - 统一的角色/权限管理界面 ### 8.2 审计集成 - 接入现有审计日志系统 - CMS 操作日志同步到统一审计平台 ### 8.3 监控集成 - 接入现有监控体系(Prometheus + Grafana) - 关键指标:API 响应时间、错误率、内容发布量 --- ## 九、风险与应对 | 风险 | 概率 | 影响 | 应对策略 | |------|------|------|----------| | 动态字段查询性能 | 中 | 高 | 合理设计索引,必要时引入 ES 做全文检索 | | 富文本编辑器选型 | 低 | 中 | 预研 Tiptap/Lexical,留接口抽象层可替换 | | 内容迁移成本 | 中 | 中 | 开发迁移脚本,分批迁移,先迁移更新频繁的模块 | | 管理后台开发量 | 高 | 中 | 优先开发核心功能,可视化编辑器可后置,先用动态表单 | --- ## 十、里程碑验收标准 ### 阶段一验收(前端 SDK) - [ ] 完整的 TypeScript 类型定义 - [ ] CMS Client SDK(mock 模式可用) - [ ] 动态 Section 渲染器(支持 3 种以上内容类型) - [ ] 案例模块试点完成(列表 + 详情) - [ ] 单元测试覆盖率 ≥ 80% ### 阶段二验收(后端核心) - [ ] 7 张核心表 + Flyway 迁移脚本 - [ ] 30+ 个 RESTful API - [ ] 内容版本管理(草稿/发布/回滚) - [ ] 审计日志完整 - [ ] 权限集成完成 - [ ] API 单元测试覆盖率 ≥ 80% ### 阶段三验收(管理后台) - [ ] 内容模型可视化编辑器 - [ ] 动态表单内容编辑器 - [ ] 媒体资源管理(上传/列表/删除) - [ ] 主题配置面板 - [ ] 操作审计日志查看 ### 阶段四验收(全链路) - [ ] 所有内容模块迁移完成(案例/新闻/产品/解决方案/服务) - [ ] 前后端联调通过 - [ ] 性能达标(LCP < 2s) - [ ] 安全扫描通过 - [ ] 内容迁移脚本验证通过