docs(test): 添加设计文档、测试规范与 E2E 测试套件
- 新增 ADR 架构决策记录 (Design DNA 集成与深化) - 新增 CMS 系统设计文档 - 新增实施计划文档 (Phase1-3) - 新增 Bain 品牌升级设计规格 - 新增 E2E 分层测试套件 (P1-P4) - 新增视觉回归测试配置 - 新增光效分析、视觉验证等辅助脚本 - 更新验收测试报告
This commit is contained in:
@@ -0,0 +1,950 @@
|
||||
# Novalon CMS 后端 API 设计规范
|
||||
|
||||
> **版本**:v1.1
|
||||
> **日期**:2026-07-02
|
||||
> **技术栈**:Java 21 + Spring Boot WebFlux + PostgreSQL + Flyway
|
||||
> **前端对接**:Next.js 14 + CMS SDK(`src/lib/cms/client.ts`)
|
||||
|
||||
---
|
||||
|
||||
## 一、总体设计原则
|
||||
|
||||
### 1.1 API 风格
|
||||
|
||||
- **风格**:RESTful API
|
||||
- **数据格式**:JSON
|
||||
- **字符编码**:UTF-8
|
||||
- **时间格式**:ISO 8601(`2024-01-01T00:00:00Z`)
|
||||
- **统一响应结构**:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"message": "success",
|
||||
"data": {},
|
||||
"timestamp": "2024-01-01T00:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| code | int | 业务状态码,0 表示成功 |
|
||||
| message | string | 提示信息 |
|
||||
| data | object/array/null | 响应数据 |
|
||||
| timestamp | string | 服务器时间 |
|
||||
|
||||
### 1.2 分页统一格式
|
||||
|
||||
**请求参数**:
|
||||
|
||||
| 参数 | 类型 | 默认值 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| page | int | 1 | 页码,从 1 开始 |
|
||||
| pageSize | int | 20 | 每页数量,最大 100 |
|
||||
| sortBy | string | created_at | 排序字段 |
|
||||
| sortOrder | string | desc | 排序方向:asc/desc |
|
||||
|
||||
**响应结构**:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"items": [],
|
||||
"total": 100,
|
||||
"page": 1,
|
||||
"pageSize": 20,
|
||||
"totalPages": 5
|
||||
},
|
||||
"timestamp": "2024-01-01T00:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
### 1.3 错误码规范
|
||||
|
||||
| 错误码 | 说明 | HTTP 状态码 |
|
||||
|--------|------|------------|
|
||||
| 0 | 成功 | 200 |
|
||||
| 40000 | 请求参数错误 | 400 |
|
||||
| 40100 | 未授权 | 401 |
|
||||
| 40300 | 无权限 | 403 |
|
||||
| 40400 | 资源不存在 | 404 |
|
||||
| 40900 | 资源冲突(如 code 重复) | 409 |
|
||||
| 50000 | 服务器内部错误 | 500 |
|
||||
|
||||
---
|
||||
|
||||
## 二、数据库表结构
|
||||
|
||||
### 2.1 cms_content_model(内容模型表)
|
||||
|
||||
```sql
|
||||
CREATE TABLE cms_content_model (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
code VARCHAR(100) NOT NULL UNIQUE,
|
||||
name VARCHAR(200) NOT NULL,
|
||||
description VARCHAR(500),
|
||||
fields_json JSONB NOT NULL DEFAULT '[]'::jsonb,
|
||||
is_page_type BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
url_pattern VARCHAR(200),
|
||||
has_versions BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
has_draft BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
icon VARCHAR(50),
|
||||
created_by VARCHAR(100) NOT NULL,
|
||||
updated_by VARCHAR(100) NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted BOOLEAN NOT NULL DEFAULT FALSE
|
||||
);
|
||||
|
||||
CREATE INDEX idx_cms_content_model_code ON cms_content_model(code) WHERE deleted = FALSE;
|
||||
```
|
||||
|
||||
### 2.2 cms_content_item(内容项表)
|
||||
|
||||
```sql
|
||||
CREATE TABLE cms_content_item (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
model_id BIGINT NOT NULL REFERENCES cms_content_model(id),
|
||||
model_code VARCHAR(100) NOT NULL,
|
||||
title VARCHAR(500) NOT NULL,
|
||||
slug VARCHAR(200),
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'draft',
|
||||
data_json JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
version INT NOT NULL DEFAULT 1,
|
||||
published_at TIMESTAMP,
|
||||
created_by VARCHAR(100) NOT NULL,
|
||||
updated_by VARCHAR(100) NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted BOOLEAN NOT NULL DEFAULT FALSE
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX idx_cms_content_item_model_slug
|
||||
ON cms_content_item(model_code, slug) WHERE deleted = FALSE;
|
||||
CREATE INDEX idx_cms_content_item_model_status
|
||||
ON cms_content_item(model_code, status, created_at DESC) WHERE deleted = FALSE;
|
||||
CREATE INDEX idx_cms_content_item_data ON cms_content_item USING GIN (data_json) WHERE deleted = FALSE;
|
||||
```
|
||||
|
||||
### 2.3 cms_content_version(内容版本表)
|
||||
|
||||
```sql
|
||||
CREATE TABLE cms_content_version (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
item_id BIGINT NOT NULL REFERENCES cms_content_item(id),
|
||||
version INT NOT NULL,
|
||||
data_json JSONB NOT NULL,
|
||||
status VARCHAR(20) NOT NULL,
|
||||
change_log VARCHAR(500),
|
||||
created_by VARCHAR(100) NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX idx_cms_content_version_item
|
||||
ON cms_content_version(item_id, version DESC);
|
||||
```
|
||||
|
||||
### 2.4 cms_content_zone(内容区域表)
|
||||
|
||||
```sql
|
||||
CREATE TABLE cms_content_zone (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
code VARCHAR(100) NOT NULL UNIQUE,
|
||||
name VARCHAR(200) NOT NULL,
|
||||
description VARCHAR(500),
|
||||
page_code VARCHAR(100),
|
||||
allowed_models VARCHAR(500),
|
||||
items_json JSONB NOT NULL DEFAULT '[]'::jsonb,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX idx_cms_content_zone_page ON cms_content_zone(page_code);
|
||||
```
|
||||
|
||||
### 2.5 cms_theme_config(主题配置表)
|
||||
|
||||
```sql
|
||||
CREATE TABLE cms_theme_config (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
config_json JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
updated_by VARCHAR(100) NOT NULL,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- 初始插入一条默认记录(id=1)
|
||||
INSERT INTO cms_theme_config (id, config_json, updated_by)
|
||||
VALUES (1, '{}', 'system');
|
||||
```
|
||||
|
||||
### 2.6 cms_media_asset(媒体资源表)
|
||||
|
||||
```sql
|
||||
CREATE TABLE cms_media_asset (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
name VARCHAR(200) NOT NULL,
|
||||
path VARCHAR(500) NOT NULL,
|
||||
url VARCHAR(500) NOT NULL,
|
||||
mime_type VARCHAR(100) NOT NULL,
|
||||
size BIGINT NOT NULL,
|
||||
width INT,
|
||||
height INT,
|
||||
alt VARCHAR(200),
|
||||
storage_type VARCHAR(20) NOT NULL DEFAULT 'local',
|
||||
created_by VARCHAR(100) NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted BOOLEAN NOT NULL DEFAULT FALSE
|
||||
);
|
||||
|
||||
CREATE INDEX idx_cms_media_mime ON cms_media_asset(mime_type) WHERE deleted = FALSE;
|
||||
```
|
||||
|
||||
### 2.7 cms_audit_log(审计日志表)
|
||||
|
||||
```sql
|
||||
CREATE TABLE cms_audit_log (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
module VARCHAR(50) NOT NULL,
|
||||
target_id BIGINT NOT NULL,
|
||||
action VARCHAR(50) NOT NULL,
|
||||
operator VARCHAR(100) NOT NULL,
|
||||
before_json JSONB,
|
||||
after_json JSONB,
|
||||
ip VARCHAR(50),
|
||||
user_agent VARCHAR(500),
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX idx_cms_audit_module ON cms_audit_log(module, target_id, created_at DESC);
|
||||
CREATE INDEX idx_cms_audit_operator ON cms_audit_log(operator, created_at DESC);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 三、API 接口详细设计
|
||||
|
||||
### 3.1 内容模型管理
|
||||
|
||||
#### 3.1.1 获取内容模型列表
|
||||
|
||||
```
|
||||
GET /api/cms/models
|
||||
```
|
||||
|
||||
**查询参数**:
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| page | int | 否 | 页码,默认 1 |
|
||||
| pageSize | int | 否 | 每页数量,默认 20 |
|
||||
| keyword | string | 否 | 搜索关键词(按 name/code 搜索) |
|
||||
| isPageType | boolean | 否 | 是否页面类型 |
|
||||
|
||||
**响应 data**:分页 + ContentModel 数组
|
||||
|
||||
**ContentModel 结构**:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "1",
|
||||
"code": "case-study",
|
||||
"name": "案例研究",
|
||||
"description": "客户成功案例",
|
||||
"fields": [...],
|
||||
"isPageType": true,
|
||||
"urlPattern": "/cases/{slug}",
|
||||
"hasVersions": true,
|
||||
"hasDraft": true,
|
||||
"icon": "briefcase",
|
||||
"createdAt": "2024-01-01T00:00:00Z",
|
||||
"updatedAt": "2024-01-01T00:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.1.2 获取单个内容模型
|
||||
|
||||
```
|
||||
GET /api/cms/models/{code}
|
||||
```
|
||||
|
||||
**路径参数**:`code` - 模型编码
|
||||
|
||||
**响应 data**:ContentModel 对象
|
||||
|
||||
#### 3.1.3 创建内容模型
|
||||
|
||||
```
|
||||
POST /api/cms/models
|
||||
```
|
||||
|
||||
**请求体**:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": "case-study",
|
||||
"name": "案例研究",
|
||||
"description": "客户成功案例",
|
||||
"fields": [...],
|
||||
"isPageType": true,
|
||||
"urlPattern": "/cases/{slug}",
|
||||
"hasVersions": true,
|
||||
"hasDraft": true,
|
||||
"icon": "briefcase"
|
||||
}
|
||||
```
|
||||
|
||||
**响应 data**:创建后的 ContentModel 对象
|
||||
|
||||
#### 3.1.4 更新内容模型
|
||||
|
||||
```
|
||||
PUT /api/cms/models/{code}
|
||||
```
|
||||
|
||||
**请求体**:同创建接口
|
||||
|
||||
**响应 data**:更新后的 ContentModel 对象
|
||||
|
||||
#### 3.1.5 删除内容模型
|
||||
|
||||
```
|
||||
DELETE /api/cms/models/{code}
|
||||
```
|
||||
|
||||
**说明**:逻辑删除,同时删除该模型下的所有内容项(逻辑删除)
|
||||
|
||||
---
|
||||
|
||||
### 3.2 内容项管理
|
||||
|
||||
#### 3.2.1 分页查询内容项
|
||||
|
||||
```
|
||||
GET /api/cms/items
|
||||
```
|
||||
|
||||
**查询参数**:
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| modelCode | string | 是 | 内容模型编码 |
|
||||
| status | string | 否 | 状态过滤:draft/review/published/archived |
|
||||
| page | int | 否 | 页码 |
|
||||
| pageSize | int | 否 | 每页数量 |
|
||||
| sortBy | string | 否 | 排序字段,默认 created_at |
|
||||
| sortOrder | string | 否 | 排序方向,默认 desc |
|
||||
| search | string | 否 | 搜索关键词(按 title) |
|
||||
| filters | object | 否 | 动态字段过滤,JSON 格式 |
|
||||
|
||||
**响应 data**:分页 + ContentItem 数组
|
||||
|
||||
**ContentItem 结构**:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "1",
|
||||
"modelId": "1",
|
||||
"modelCode": "case-study",
|
||||
"title": "某制造企业 ERP 升级",
|
||||
"slug": "manufacturing-erp-upgrade",
|
||||
"status": "published",
|
||||
"data": {
|
||||
"client": "某上市制造企业",
|
||||
"industry": "制造业",
|
||||
"metrics": [...]
|
||||
},
|
||||
"version": 3,
|
||||
"publishedAt": "2024-01-15T10:00:00Z",
|
||||
"createdBy": "admin",
|
||||
"updatedBy": "admin",
|
||||
"createdAt": "2024-01-01T00:00:00Z",
|
||||
"updatedAt": "2024-01-15T10:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.2.2 获取内容项详情
|
||||
|
||||
```
|
||||
GET /api/cms/items/{id}
|
||||
```
|
||||
|
||||
**路径参数**:`id` - 内容项 ID
|
||||
|
||||
**响应 data**:ContentItem 对象
|
||||
|
||||
#### 3.2.3 按 slug 获取内容项
|
||||
|
||||
```
|
||||
GET /api/cms/items/slug/{modelCode}/{slug}
|
||||
```
|
||||
|
||||
**路径参数**:
|
||||
- `modelCode` - 模型编码
|
||||
- `slug` - 内容 slug
|
||||
|
||||
**响应 data**:ContentItem 对象
|
||||
|
||||
#### 3.2.4 创建内容项
|
||||
|
||||
```
|
||||
POST /api/cms/items
|
||||
```
|
||||
|
||||
**请求体**:
|
||||
|
||||
```json
|
||||
{
|
||||
"modelCode": "case-study",
|
||||
"title": "某制造企业 ERP 升级",
|
||||
"slug": "manufacturing-erp-upgrade",
|
||||
"data": {
|
||||
"client": "某上市制造企业",
|
||||
"industry": "制造业"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**说明**:
|
||||
- 创建时状态默认为 `draft`
|
||||
- 版本号默认为 1
|
||||
- 自动创建第一个版本记录
|
||||
|
||||
**响应 data**:创建后的 ContentItem 对象
|
||||
|
||||
#### 3.2.5 更新内容项
|
||||
|
||||
```
|
||||
PUT /api/cms/items/{id}
|
||||
```
|
||||
|
||||
**请求体**:
|
||||
|
||||
```json
|
||||
{
|
||||
"title": "新标题",
|
||||
"data": {
|
||||
"client": "新客户名称"
|
||||
},
|
||||
"changeLog": "更新客户名称"
|
||||
}
|
||||
```
|
||||
|
||||
**说明**:
|
||||
- 更新时版本号 +1
|
||||
- 自动保存到版本历史表
|
||||
- 如果当前状态是 published,更新后变为 draft(可选配置)
|
||||
|
||||
**响应 data**:更新后的 ContentItem 对象
|
||||
|
||||
#### 3.2.6 发布内容
|
||||
|
||||
```
|
||||
POST /api/cms/items/{id}/publish
|
||||
```
|
||||
|
||||
**请求体**(可选):
|
||||
|
||||
```json
|
||||
{
|
||||
"changeLog": "发布正式版本"
|
||||
}
|
||||
```
|
||||
|
||||
**说明**:
|
||||
- 将状态从 `draft`/`review` 改为 `published`
|
||||
- 设置 `publishedAt` 时间
|
||||
- 版本号不变(发布当前版本)
|
||||
|
||||
**响应 data**:发布后的 ContentItem 对象
|
||||
|
||||
#### 3.2.7 下架内容
|
||||
|
||||
```
|
||||
POST /api/cms/items/{id}/unpublish
|
||||
```
|
||||
|
||||
**说明**:将状态从 `published` 改为 `archived`
|
||||
|
||||
**响应 data**:下架后的 ContentItem 对象
|
||||
|
||||
#### 3.2.8 获取版本历史
|
||||
|
||||
```
|
||||
GET /api/cms/items/{id}/versions
|
||||
```
|
||||
|
||||
**响应 data**:ContentVersion 数组
|
||||
|
||||
#### 3.2.9 回滚到指定版本
|
||||
|
||||
```
|
||||
POST /api/cms/items/{id}/versions/{version}/rollback
|
||||
```
|
||||
|
||||
**说明**:创建新版本,内容为指定版本的快照,版本号 +1
|
||||
|
||||
**响应 data**:回滚后的 ContentItem 对象
|
||||
|
||||
#### 3.2.10 删除内容项
|
||||
|
||||
```
|
||||
DELETE /api/cms/items/{id}
|
||||
```
|
||||
|
||||
**说明**:逻辑删除
|
||||
|
||||
---
|
||||
|
||||
### 3.3 内容区域管理
|
||||
|
||||
#### 3.3.1 获取单个内容区域
|
||||
|
||||
```
|
||||
GET /api/cms/zones/{code}
|
||||
```
|
||||
|
||||
**路径参数**:`code` - 区域编码
|
||||
|
||||
**响应 data**:ContentZone 对象(含 items 详情)
|
||||
|
||||
#### 3.3.2 获取页面的所有内容区域
|
||||
|
||||
```
|
||||
GET /api/cms/zones/page/{pageCode}
|
||||
```
|
||||
|
||||
**路径参数**:`pageCode` - 页面编码
|
||||
|
||||
**响应 data**:ContentZone 数组
|
||||
|
||||
#### 3.3.3 更新内容区域配置
|
||||
|
||||
```
|
||||
PUT /api/cms/zones/{code}
|
||||
```
|
||||
|
||||
**请求体**:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "首页 Hero 区",
|
||||
"description": "首页顶部大 Banner 区域",
|
||||
"allowedModels": ["hero-banner", "video-banner"],
|
||||
"items": [
|
||||
{
|
||||
"itemId": "101",
|
||||
"sortOrder": 1,
|
||||
"config": {
|
||||
"fullWidth": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"itemId": "102",
|
||||
"sortOrder": 2,
|
||||
"config": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**响应 data**:更新后的 ContentZone 对象
|
||||
|
||||
---
|
||||
|
||||
### 3.4 主题配置管理
|
||||
|
||||
#### 3.4.1 获取主题配置
|
||||
|
||||
```
|
||||
GET /api/cms/theme
|
||||
```
|
||||
|
||||
**响应 data**:ThemeConfig 对象
|
||||
|
||||
**ThemeConfig 结构**:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "1",
|
||||
"siteName": "Novalon",
|
||||
"siteDescription": "企业数字化转型服务商",
|
||||
"logo": "/images/logo.svg",
|
||||
"favicon": "/favicon.ico",
|
||||
"colors": {
|
||||
"primary": "#00D084",
|
||||
"secondary": "#6C8CFF",
|
||||
"accent": "#FFB800",
|
||||
"background": "#FFFFFF",
|
||||
"text": "#1A1A1A"
|
||||
},
|
||||
"seo": {
|
||||
"defaultTitle": "Novalon - 企业数字化转型",
|
||||
"defaultDescription": "Novalon 致力于帮助企业实现数字化转型",
|
||||
"defaultKeywords": "数字化转型,ERP,CRM",
|
||||
"ogImage": "/images/og-default.png"
|
||||
},
|
||||
"social": {
|
||||
"wechat": "novalon",
|
||||
"weibo": "@novalon",
|
||||
"linkedin": "novalon",
|
||||
"github": "novalon"
|
||||
},
|
||||
"settings": {
|
||||
"enableDarkMode": true,
|
||||
"defaultTheme": "light"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.4.2 更新主题配置
|
||||
|
||||
```
|
||||
PUT /api/cms/theme
|
||||
```
|
||||
|
||||
**请求体**:完整的 ThemeConfig 对象(或部分字段)
|
||||
|
||||
**响应 data**:更新后的 ThemeConfig 对象
|
||||
|
||||
---
|
||||
|
||||
### 3.5 媒体资源管理
|
||||
|
||||
#### 3.5.1 获取媒体列表
|
||||
|
||||
```
|
||||
GET /api/cms/media
|
||||
```
|
||||
|
||||
**查询参数**:
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| page | int | 否 | 页码 |
|
||||
| pageSize | int | 否 | 每页数量 |
|
||||
| mimeType | string | 否 | MIME 类型过滤 |
|
||||
| search | string | 否 | 按名称搜索 |
|
||||
|
||||
**响应 data**:分页 + MediaAsset 数组
|
||||
|
||||
#### 3.5.2 上传媒体文件
|
||||
|
||||
```
|
||||
POST /api/cms/media/upload
|
||||
```
|
||||
|
||||
**Content-Type**:`multipart/form-data`
|
||||
|
||||
**表单字段**:
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| file | file | 是 | 文件内容 |
|
||||
| alt | string | 否 | 替代文本 |
|
||||
| folder | string | 否 | 存储目录 |
|
||||
|
||||
**响应 data**:MediaAsset 对象
|
||||
|
||||
#### 3.5.3 获取媒体详情
|
||||
|
||||
```
|
||||
GET /api/cms/media/{id}
|
||||
```
|
||||
|
||||
#### 3.5.4 删除媒体资源
|
||||
|
||||
```
|
||||
DELETE /api/cms/media/{id}
|
||||
```
|
||||
|
||||
**说明**:逻辑删除,实际文件不删除
|
||||
|
||||
---
|
||||
|
||||
### 3.6 审计日志
|
||||
|
||||
#### 3.6.1 获取审计日志列表
|
||||
|
||||
```
|
||||
GET /api/cms/audit-logs
|
||||
```
|
||||
|
||||
**查询参数**:
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| page | int | 否 | 页码 |
|
||||
| pageSize | int | 否 | 每页数量 |
|
||||
| module | string | 否 | 模块过滤 |
|
||||
| targetId | string | 否 | 目标 ID |
|
||||
| operator | string | 否 | 操作人 |
|
||||
| action | string | 否 | 操作类型 |
|
||||
| startTime | string | 否 | 开始时间 |
|
||||
| endTime | string | 否 | 结束时间 |
|
||||
|
||||
**响应 data**:分页 + AuditLog 数组
|
||||
|
||||
---
|
||||
|
||||
## 四、前端公开 API(无需鉴权)
|
||||
|
||||
> 以下 API 用于前端网站展示,仅返回 `published` 状态的内容,无需登录。
|
||||
> 建议使用 API Key 进行简单的访问频率限制。
|
||||
|
||||
### 4.1 获取内容项列表
|
||||
|
||||
```
|
||||
GET /api/cms/public/items
|
||||
```
|
||||
|
||||
**查询参数**:同 3.2.1,但 `status` 固定为 `published`
|
||||
|
||||
### 4.2 按 slug 获取内容项
|
||||
|
||||
```
|
||||
GET /api/cms/public/items/slug/{modelCode}/{slug}
|
||||
```
|
||||
|
||||
### 4.3 获取内容区域
|
||||
|
||||
```
|
||||
GET /api/cms/public/zones/{code}
|
||||
```
|
||||
|
||||
### 4.4 获取主题配置
|
||||
|
||||
```
|
||||
GET /api/cms/public/theme
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 五、Webhook 通知(可选)
|
||||
|
||||
内容发布/更新时,可配置 Webhook 通知前端触发 ISR 重新生成:
|
||||
|
||||
```
|
||||
POST https://website.example.com/api/revalidate
|
||||
```
|
||||
|
||||
**请求体**:
|
||||
|
||||
```json
|
||||
{
|
||||
"event": "content.published",
|
||||
"modelCode": "case-study",
|
||||
"itemId": "100",
|
||||
"slug": "manufacturing-erp-upgrade",
|
||||
"timestamp": "2024-01-15T10:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 六、性能优化建议
|
||||
|
||||
1. **热点内容缓存**:使用 Redis 缓存 `published` 状态的内容,TTL 5-10 分钟
|
||||
2. **数据库索引**:
|
||||
- `data_json` 字段使用 GIN 索引支持 JSON 查询
|
||||
- 常用查询组合建立联合索引
|
||||
3. **读写分离**:读操作走从库,写操作走主库
|
||||
4. **分页优化**:深分页使用游标分页(seek pagination)
|
||||
5. **CDN 加速**:媒体资源上传到 OSS/S3,走 CDN
|
||||
|
||||
---
|
||||
|
||||
## 七、安全要求
|
||||
|
||||
1. **管理端 API**:必须通过 SSO 鉴权,并有角色权限控制
|
||||
2. **公开 API**:
|
||||
- 只能访问 `published` 状态的内容
|
||||
- 使用 API Key 进行限流
|
||||
- 禁止查询敏感字段(如内部备注)
|
||||
3. **审计日志**:所有写操作必须记录审计日志
|
||||
4. **输入校验**:
|
||||
- `code` 字段必须匹配正则:`^[a-z][a-z0-9-]*$`
|
||||
- JSON 字段大小限制(如 1MB)
|
||||
- XSS 防护:富文本内容存储前进行净化
|
||||
5. **SQL 注入防护**:使用参数化查询,禁止拼接 SQL
|
||||
|
||||
---
|
||||
|
||||
## 八、数据一致性约束
|
||||
|
||||
1. **model_code 冗余**:`cms_content_item.model_code` 与 `cms_content_model.code` 保持一致(冗余设计,提升查询性能)
|
||||
2. **版本号递增**:每次更新内容项,`version` 必须 +1
|
||||
3. **状态流转**:
|
||||
- `draft` → `review` → `published` → `archived`
|
||||
- `published` → `draft`(修改时)
|
||||
- 任意状态 → `archived`
|
||||
|
||||
---
|
||||
|
||||
## 九、前端 Mock 数据字段定义(对接参考)
|
||||
|
||||
> **目的**:以下字段定义与前端 Mock 数据保持一致,后端实现时需按此结构创建 `fields_json`。
|
||||
> **源码位置**:`src/lib/cms/mock-data.ts`(case-study)、`src/lib/cms/content-types.ts`(其余 6 个模型)。
|
||||
> **字段类型**:`text` | `textarea` | `richtext` | `number` | `boolean` | `date` | `select` | `image` | `array` | `object`
|
||||
|
||||
### 9.0 模型总览
|
||||
|
||||
| model code | 名称 | 页面类型 | URL 模式 | Mock 数据条数 | 数据源 |
|
||||
|------------|------|---------|----------|--------------|--------|
|
||||
| `case-study` | 案例研究 | 是 | `/cases/{slug}` | 6 | `lib/constants/cases.ts` |
|
||||
| `news` | 新闻资讯 | 是 | `/news/{slug}` | 2 | `lib/constants/news.ts` |
|
||||
| `service` | 服务 | 是 | `/services/{slug}` | 4 | `lib/constants/services.ts` |
|
||||
| `product` | 产品 | 是 | `/products/{slug}` | 7 | `lib/constants/products.ts` |
|
||||
| `solution` | 解决方案 | 是 | `/solutions/{slug}` | 4 | `lib/constants/solutions.ts` |
|
||||
| `hero-banner` | Hero Banner | 否 | - | 0(首页 CMS 注入) | `lib/cms/mock-home.ts` |
|
||||
| `stat-item` | 数据指标 | 否 | - | 0(首页 CMS 注入) | `lib/cms/mock-home.ts` |
|
||||
|
||||
> **说明**:`hero-banner` 和 `stat-item` 不是页面类型,作为首页内容区域(zone)的组件,通过 `ContentZoneRenderer` 渲染。
|
||||
|
||||
### 9.1 case-study(案例研究)
|
||||
|
||||
| 字段名 | 类型 | 必填 | 标签 | 说明 |
|
||||
|--------|------|------|------|------|
|
||||
| `client` | text | 是 | 客户名称 | 客户公司或机构名称 |
|
||||
| `industry` | text | 是 | 所属行业 | 客户所属行业领域 |
|
||||
| `companySize` | text | 是 | 公司规模 | 客户公司人员规模 |
|
||||
| `subtitle` | text | 是 | 副标题 | 案例副标题,简要概括项目价值 |
|
||||
| `challenge` | textarea | 是 | 挑战 | 客户面临的业务挑战与痛点 |
|
||||
| `solution` | textarea | 是 | 解决方案 | 我们提供的解决方案与实施路径 |
|
||||
| `result` | textarea | 是 | 实施成果 | 项目交付后的业务成果与价值 |
|
||||
| `metrics` | array | 否 | 关键指标 | 项目关键量化指标,子字段:`value`(text)、`label`(text)、`highlight`(boolean) |
|
||||
| `timeline` | array | 否 | 项目时间线 | 子字段:`phase`(text)、`duration`(text)、`description`(textarea) |
|
||||
| `services` | array | 否 | 相关服务 | 子字段:`id`(text)、`title`(text)、`description`(textarea) |
|
||||
| `testimonial` | object | 否 | 客户评价 | 子字段:`quote`(textarea)、`author`(text)、`role`(text)、`company`(text) |
|
||||
| `color` | select | 是 | 主题色 | 选项:brand / blue / teal / amber / purple,默认 brand |
|
||||
| `featured` | boolean | 否 | 精选案例 | 是否在首页/推荐位展示,默认 false |
|
||||
|
||||
### 9.2 news(新闻资讯)
|
||||
|
||||
| 字段名 | 类型 | 必填 | 标签 | 说明 |
|
||||
|--------|------|------|------|------|
|
||||
| `excerpt` | textarea | 是 | 摘要 | 新闻摘要,用于列表页展示 |
|
||||
| `date` | date | 是 | 发布日期 | - |
|
||||
| `category` | select | 是 | 分类 | 选项:company(公司动态) / industry(行业资讯) / tech(技术分享) |
|
||||
| `image` | image | 是 | 封面图 | - |
|
||||
| `content` | richtext | 是 | 正文内容 | 富文本(HTML),通过 TipTap 编辑器产出 |
|
||||
| `featured` | boolean | 否 | 精选推荐 | 是否在首页精选区展示,默认 false |
|
||||
|
||||
### 9.3 service(服务)
|
||||
|
||||
| 字段名 | 类型 | 必填 | 标签 | 说明 |
|
||||
|--------|------|------|------|------|
|
||||
| `description` | textarea | 是 | 简短描述 | - |
|
||||
| `icon` | text | 是 | 图标标识 | 图标名称,对应图标库 |
|
||||
| `overview` | textarea | 是 | 服务概述 | - |
|
||||
| `features` | array | 否 | 核心能力 | 子字段:`name`(text) |
|
||||
| `benefits` | array | 否 | 服务价值 | 子字段:`name`(text) |
|
||||
| `process` | array | 否 | 服务流程 | 子字段:`step`(text) |
|
||||
| `heroThemeId` | text | 否 | Hero 主题 ID | 关联的 Hero 区域主题配置 |
|
||||
|
||||
### 9.4 product(产品)
|
||||
|
||||
| 字段名 | 类型 | 必填 | 标签 | 说明 |
|
||||
|--------|------|------|------|------|
|
||||
| `description` | textarea | 是 | 产品简介 | - |
|
||||
| `image` | image | 是 | 产品图片 | - |
|
||||
| `category` | text | 是 | 分类名称 | - |
|
||||
| `categoryId` | select | 是 | 分类 ID | 选项:enterprise(企业套装) / specialized(专业产品) |
|
||||
| `status` | select | 是 | 产品状态 | 选项:研发中 / 内测中 / 已发布 |
|
||||
| `overview` | textarea | 是 | 产品概述 | - |
|
||||
| `features` | array | 否 | 核心功能 | 子字段:`name`(text) |
|
||||
| `benefits` | array | 否 | 产品价值 | 子字段:`name`(text) |
|
||||
| `tags` | array | 否 | 标签 | 子字段:`name`(text) |
|
||||
| `featured` | boolean | 否 | 精选产品 | 默认 false |
|
||||
|
||||
### 9.5 solution(解决方案)
|
||||
|
||||
| 字段名 | 类型 | 必填 | 标签 | 说明 |
|
||||
|--------|------|------|------|------|
|
||||
| `description` | textarea | 是 | 方案简介 | - |
|
||||
| `icon` | text | 是 | 图标标识 | - |
|
||||
| `industry` | text | 否 | 适用行业 | - |
|
||||
| `overview` | textarea | 是 | 方案概述 | - |
|
||||
| `painPoints` | array | 否 | 行业痛点 | 子字段:`text`(text) |
|
||||
| `valueProps` | array | 否 | 方案价值 | 子字段:`text`(text) |
|
||||
| `featured` | boolean | 否 | 精选方案 | 默认 false |
|
||||
|
||||
### 9.6 hero-banner(Hero Banner)
|
||||
|
||||
> 非页面类型,作为首页内容区域组件使用。
|
||||
|
||||
| 字段名 | 类型 | 必填 | 标签 | 默认值 |
|
||||
|--------|------|------|------|--------|
|
||||
| `heading` | text | 是 | 主标题 | - |
|
||||
| `subheading` | textarea | 是 | 副标题 | - |
|
||||
| `description` | textarea | 否 | 描述文本 | - |
|
||||
| `primaryCtaText` | text | 否 | 主按钮文案 | 了解更多 |
|
||||
| `primaryCtaLink` | text | 否 | 主按钮链接 | /solutions |
|
||||
| `secondaryCtaText` | text | 否 | 副按钮文案 | 联系我们 |
|
||||
| `secondaryCtaLink` | text | 否 | 副按钮链接 | /contact |
|
||||
| `theme` | select | 否 | 主题风格 | brand |
|
||||
| `sortOrder` | number | 否 | 排序 | 0 |
|
||||
|
||||
**theme 选项**:brand / blue / teal / amber / purple
|
||||
|
||||
### 9.7 stat-item(数据指标)
|
||||
|
||||
> 非页面类型,作为首页数据展示区组件使用。
|
||||
|
||||
| 字段名 | 类型 | 必填 | 标签 | 默认值 |
|
||||
|--------|------|------|------|--------|
|
||||
| `icon` | text | 是 | 图标 | - |
|
||||
| `label` | text | 是 | 指标名称 | - |
|
||||
| `value` | number | 是 | 数值 | - |
|
||||
| `suffix` | text | 否 | 后缀 | - |
|
||||
| `prefix` | text | 否 | 前缀 | - |
|
||||
| `decimals` | number | 否 | 小数位数 | 0 |
|
||||
| `description` | textarea | 否 | 描述说明 | - |
|
||||
| `trendValue` | text | 否 | 趋势值 | - |
|
||||
| `trendDirection` | select | 否 | 趋势方向 | up |
|
||||
| `accentColor` | select | 否 | 强调色 | brand |
|
||||
| `sortOrder` | number | 否 | 排序 | 0 |
|
||||
|
||||
**trendDirection 选项**:up(上升) / down(下降) / neutral(持平)
|
||||
**accentColor 选项**:brand / blue / teal / amber / purple
|
||||
|
||||
---
|
||||
|
||||
## 十、前端 Mock 模式架构说明
|
||||
|
||||
### 10.1 Mock 数据流转
|
||||
|
||||
```
|
||||
lib/constants/*.ts(原始业务数据)
|
||||
↓ toContentItem<T>()
|
||||
lib/cms/mock-data.ts(allMockItems,server-safe 同步数据源)
|
||||
↓ getMockItems(modelCode) / getMockItemBySlug / getMockItemById
|
||||
lib/cms/client.ts(cmsClient,运行时数据访问层)
|
||||
↓ getContentItems / getItem / getZone
|
||||
src/app/(marketing)/*/page.tsx(server 端同步预取 ContentItem)
|
||||
↓ 传 ContentItem prop 给 client 组件
|
||||
src/app/(marketing)/*/client.tsx(client 端接收 prop 渲染)
|
||||
```
|
||||
|
||||
### 10.2 切换到真实 API 的步骤
|
||||
|
||||
1. 在 `src/lib/cms/client.ts` 中将 `CMS_MODE` 从 `'mock'` 改为 `'api'`
|
||||
2. 配置 `CMS_API_BASE_URL` 环境变量指向 Java 后端
|
||||
3. `cmsClient` 的 `getItems/getItem/getItemBySlug/getZone` 会自动切换为 HTTP 请求
|
||||
4. 业务层代码(page.tsx、client.tsx)无需任何改动,ContentItem 数据结构保持一致
|
||||
|
||||
### 10.3 静态导出兼容性
|
||||
|
||||
- Mock 数据源(`mock-data.ts`)不标记 `'use client'`,提供同步函数
|
||||
- `generateStaticParams` 和 `generateMetadata` 在 server 端同步调用 `getMockItems` / `getMockItemBySlug`
|
||||
- 静态导出(`output: 'export'`)模式下,构建时预渲染所有页面路径
|
||||
- CMS API 路由(`/api/cms/draft/*`、`/api/cms/revalidate`)已改为 POST 方法,避免静态导出预渲染时报错(GET + searchParams 在 `output: 'export'` 下不支持)
|
||||
|
||||
### 10.4 富文本编辑器(TipTap)
|
||||
|
||||
- CMS Studio 的 `richtext` 字段使用 TipTap 富文本编辑器(`src/components/cms/RichTextEditor.tsx`)
|
||||
- 支持:加粗、斜体、H2/H3、有序/无序列表、引用、链接、撤销/重做
|
||||
- `immediatelyRender: false` 避免 SSR hydration mismatch
|
||||
- 编辑器产出的 HTML 存储在 ContentItem.data 的对应字段中(如 news.content)
|
||||
- 样式定义在 `src/app/globals.css` 的 `.rich-text-editor-content` 选择器下
|
||||
Reference in New Issue
Block a user