- 新增 ADR 架构决策记录 (Design DNA 集成与深化) - 新增 CMS 系统设计文档 - 新增实施计划文档 (Phase1-3) - 新增 Bain 品牌升级设计规格 - 新增 E2E 分层测试套件 (P1-P4) - 新增视觉回归测试配置 - 新增光效分析、视觉验证等辅助脚本 - 更新验收测试报告
22 KiB
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
内容: 定义以下核心类型
// 字段类型枚举
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<string, unknown>; // 动态字段数据,结构由 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<string, unknown>; // 区域级配置
}>;
}
// 主题配置
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<string, unknown>; // 其他自定义设置
}
// 分页查询参数
export interface ContentQueryParams {
modelCode: string;
status?: ContentStatus;
page?: number;
pageSize?: number;
sortBy?: string;
sortOrder?: 'asc' | 'desc';
filters?: Record<string, string | number | boolean>;
search?: string;
}
// 分页结果
export interface PaginatedResult<T> {
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 模式
// 配置
interface CmsClientConfig {
baseUrl: string;
apiKey?: string;
timeout?: number;
mockMode?: boolean; // 开发阶段使用 mock 数据
}
// 客户端类
class CmsClient {
constructor(config: CmsClientConfig) {}
// 内容模型相关
async getModels(): Promise<ContentModel[]> {}
async getModel(code: string): Promise<ContentModel> {}
// 内容项相关
async getItems(params: ContentQueryParams): Promise<PaginatedResult<ContentItem>> {}
async getItem(id: string): Promise<ContentItem> {}
async getItemBySlug(modelCode: string, slug: string): Promise<ContentItem> {}
// 内容区域相关
async getZone(code: string): Promise<ContentZone> {}
async getZonesByPage(pageCode: string): Promise<ContentZone[]> {}
// 主题配置相关
async getThemeConfig(): Promise<ThemeConfig> {}
// 工具方法
async withType<T extends Record<string, unknown>>(
item: ContentItem
): Promise<T> {}
}
任务 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 选择对应的渲染组件
// 组件注册表
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)
- 安全扫描通过
- 内容迁移脚本验证通过