Files
novalon-website/docs/cms/api-contract.md
T
zhangxiang a0328a623f chore(qa): 验收台账与证据入库 + 构建/部署配置同步
- docs/acceptance/qa-tracker.md:跨周期缺陷单一真源台账(§7=第五轮)。
- 周期 1/2 + iPhone SE/axe 验收证据目录、ACCEPTANCE_REVIEW 快照入库。
- 同步 README/CONTEXT/CLAUDE/DESIGN/testing/deployment/lessons-learned 口径;
  next.config/Dockerfile/nginx/Jenkinsfile/docker-compose/sentry/prisma 对齐
  standalone 产物装配与部署形态。
2026-09-28 10:48:09 +08:00

32 KiB
Raw Blame History

Novalon CMS 后端 API 设计规范

版本:v1.1
日期:2026-07-02
技术栈:Java 21 + Spring Boot WebFlux + PostgreSQL + Flyway
前端对接:Next.js 16(App Router,output: 'standalone' 混合渲染)+ 读取层 src/lib/cms/data-server.ts

⚠ 路径时效注(2026-08-31 一致性审计):src/lib/cms/client.ts / mock-data.ts 在当前代码库中不存在(src/lib/cms/ 现含 data-server.ts / workflow.ts / notifications.ts 等)。本文档 API 契约本身仍有效,前端对接实现以 src/lib/cms/ 实际代码为准。


一、总体设计原则

1.1 API 风格

  • 风格:RESTful API
  • 数据格式:JSON
  • 字符编码:UTF-8
  • 时间格式:ISO 8601(2024-01-01T00:00:00Z)
  • 统一响应结构:
{
  "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

响应结构:

{
  "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(内容模型表)

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(内容项表)

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(内容版本表)

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(内容区域表)

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(主题配置表)

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(媒体资源表)

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(审计日志表)

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 结构:

{
  "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

请求体:

{
  "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 结构:

{
  "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

请求体:

{
  "modelCode": "case-study",
  "title": "某制造企业 ERP 升级",
  "slug": "manufacturing-erp-upgrade",
  "data": {
    "client": "某上市制造企业",
    "industry": "制造业"
  }
}

说明:

  • 创建时状态默认为 draft
  • 版本号默认为 1
  • 自动创建第一个版本记录
  • 本仓库当前实现(POST /api/admin/items):status 只接受缺省或 draft,其余取值一律 400 并指向 workflow 接口;落库固定 status: 'draft' 且 publishedAt: null(src/app/api/admin/items/route.ts:138-142、:163-176)。创建接口不是发布通道。

响应 data:创建后的 ContentItem 对象

3.2.5 更新内容项

PUT /api/cms/items/{id}

请求体:

{
  "title": "新标题",
  "data": {
    "client": "新客户名称"
  },
  "changeLog": "更新客户名称"
}

说明:

  • 更新时版本号 +1
  • 自动保存到版本历史表
  • 状态不随更新改变:任何「把 status 改成别的值」的载荷都被拒绝(400,指向 workflow 接口);原样回传当前状态是被接受的(admin 编辑页每次保存都会带上现有 status)。见 src/app/api/admin/items/route.ts:222-226

响应 data:更新后的 ContentItem 对象

3.2.6 发布内容

POST /api/cms/items/{id}/publish

请求体(可选):

{
  "changeLog": "发布正式版本"
}

说明:

  • 不存在「从 draft 一步到 published」的通道:draft 只能先 submit 进入 review,再由 review approve 到 published(src/lib/cms/workflow.ts:20-32)
  • 审批通过时设置 publishedAt 时间(src/lib/cms/workflow.ts:77-79)
  • 每次流转 version +1(与内容更新同为 { increment: 1 },见 src/lib/cms/workflow.ts:68-75)
  • 本仓库当前实现:状态变更的唯一入口是 workflow 接口 POST /api/admin/items/[id]/workflow(src/app/api/admin/items/[id]/workflow/route.ts),请求体 { action: 'submit' | 'approve' | 'reject' | 'archive', reason? };approve / reject / archive 需要 publish 权限,submit 只需 update 权限(:31-36)。非法流转与并发状态冲突返回 400,不会静默忽略

响应 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}

请求体:

{
  "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 结构:

{
  "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

请求体:

{
  "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. 状态流转(本仓库实现见 src/lib/cms/workflow.ts:20-32,动作即唯一通道):
    • draft --submit--> review --approve--> published --archive--> archived
    • review --reject--> draft(驳回回草稿,不是 published 直接回退)
    • archived 为终态,无任何后继动作;published 只能归档,不能再改回 draft
    • 每个状态只允许上述动作,其余组合一律抛错并由路由转为 400(src/app/api/admin/items/[id]/workflow/route.ts:86-88)

九、前端内容字段定义(对接参考)

目的:以下字段定义与前端数据层保持一致,后端实现时需按此结构创建 fields_json。 源码位置:src/lib/cms/content-types.ts(全部模型的 CONTENT_TYPE_CONFIGS,:1981 起;§九 列出的 7 个模型对应其中 7 个键,case-study 配置在 :1837)。同文件 :1978 注明「数据模型定义已全部移至 CONTENT_TYPE_CONFIGS」,mock-data.ts 已不存在(见文首路径时效注)。 字段类型:以 src/lib/cms/types.ts:24-40 的 FieldType 为准 —— text | textarea | richtext | number | boolean | date | datetime | image | media | reference | references | json | array | object | select | dropdown。其中 admin 编辑页 renderField 只为 text / textarea / number / boolean / select / dropdown / json / image / array / object 提供专门控件,其余类型(含 richtext、date、datetime、media、reference、references)落入 default: 的单行 <input type="text">(src/app/admin/content/[modelCode]/[itemId]/page.tsx:447-628,见 §10.4) 漂移告知(2026-09-23 核对):§9.0–§9.7 是早期对接快照,与 content-types.ts 已系统性偏离,不得当作现行字段清单引用——已知三类差异:① 类型口径:文档写作 select 的字段在代码里是带 options 的 text(news.category、product.categoryId、product.status、case-study.color、stat-item.trendDirection、hero-banner.theme 等,二者语义同义,见 src/lib/cms/validate-content-data.ts:29-30);② 文档写作 array / object 的列表字段多已改为 json(如 service.features、solution.painPoints);③ 字段名与集合有变更(hero-banner 的 primaryCtaText/primaryCtaLink 现为 ctaLabel/ctaHref,solution.valueProps 现为 challenges/solutions/valueProposition,case-study 另有 projectDuration/departments/dataScale/businessProblem/verified 未列出)。逐字段口径请以 CONTENT_TYPE_CONFIGS 为准。

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 text(带 options,语义同 select) 是 分类 代码侧选项为 公司新闻 / 研发动态(src/lib/cms/content-types.ts:58-66)
image image 是 封面图 -
content richtext 是 正文内容 富文本(HTML);admin 侧无专用编辑器,落 default: 单行输入框由作者直接书写 HTML(见 §10.4),渲染端经 src/lib/sanitize.ts 净化
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


十、前端数据层架构说明

10.1 数据流转(现状)

src/lib/constants/*.ts、prisma/seeds/*.ts(原始业务数据)
        ↓ prisma/seed.ts 写入(contentItem.upsert,`prisma/seed.ts:42`)
Prisma ContentItem(data 列存 JSON 字符串)
        ↓ getPublishedItems / getPublishedItemBySlug / getZone / getResolvedHomeZones(React cache)
src/lib/cms/data-server.ts(server 端读取层)
        ↓ 以 props 传入
src/app/(marketing)/**/page.tsx(server 组件)→ client.tsx / home-content-v15.tsx(client 渲染)

历史口径:本节曾描述 lib/cms/mock-data.ts → lib/cms/client.ts(cmsClient)的纯 Mock 流转,两个文件均已不存在(文首路径时效注)。「CMS 优先 + 常量兜底」的语义仍成立,但实现分散在读取层与页面里:首页在 zone 未解析到内容时回落到逐模型 getPublishedItems()(src/app/(marketing)/page.tsx:8-33),结构性文案在 CMS 未配置时由组件回退 src/lib/constants/*(见 CONTEXT.md「结构性文案 CMS 化」)。

10.2 切换到真实 API 的步骤

  1. 读取层已落在 src/lib/cms/data-server.ts(Prisma + React cache),不存在 CMS_MODE 开关与 src/lib/cms/client.ts。
  2. 接真实后端时改的是 data-server.ts 的取数函数(getPublishedItems / getPublishedItemBySlug / getZone / getResolvedHomeZones),业务页与 client 组件按 props 契约保持不变。
  3. ContentItem 的结构定义在 src/lib/cms/types.ts,字段声明在 src/lib/cms/content-types.ts 的 CONTENT_TYPE_CONFIGS。

10.3 渲染模式与预渲染兼容性

  • 构建产物为 output: 'standalone'(next.config.mjs:44),output: 'export' 早已移除:营销页在构建时预渲染并走 ISR(页面级 export const revalidate = 3600,如 src/app/(marketing)/page.tsx:5、src/app/privacy/page.tsx:12),/api/* 与 /admin/* 需要 Node 运行时
  • 读取层(data-server.ts)不标记 'use client';generateStaticParams 与 generateMetadata 在 server 端调用它
  • CMS 通知型路由只导出 POST(src/app/api/cms/draft/enable/route.ts:13、src/app/api/cms/draft/disable/route.ts:6、src/app/api/cms/revalidate/route.ts:33),且密钥为 fail-closed:CMS_PREVIEW_SECRET(draft)/ CMS_REVALIDATE_SECRET(revalidate)未配置时直接拒绝,不放行

10.4 富文本字段(richtext)

  • admin 编辑页的 renderField(src/app/admin/content/[modelCode]/[itemId]/page.tsx:447 的 switch)没有 richtext 分支,该类型落入 default:(:613-627)⇒ 渲染成单行 <input type="text">,作者需直接书写 HTML
  • 历史组件 src/components/cms/RichTextEditor.tsx(TipTap)与配套 .rich-text-editor-content 样式在生产代码中无任何引用,已作为死代码删除(src/components/cms/ 目录整体不存在);恢复富文本编辑需重新接线到 renderField。package.json 仍留着 @tiptap/* 依赖,但不参与任何运行时路径
  • richtext 字段产出的 HTML 存于 ContentItem.data 对应字段(如 news.content),公开页经 src/lib/sanitize.ts 净化后才进入 dangerouslySetInnerHTML(白名单见 src/lib/sanitize.ts:7-46,用例见 src/app/privacy/page.tsx:268、src/app/terms/page.tsx:213)

10.5 状态字段:只有 workflow 能改(admin 编辑页只读)

  • ContentStatus 只有四个取值:draft / review / published / archived(src/lib/cms/types.ts:81),变更只能经 POST /api/admin/items/[id]/workflow(动作 submit / approve / reject / archive,权限口径见 §3.2.6)
  • POST /api/admin/items:status 传缺省或 draft 以外的值即 400;新建条目固定 status: 'draft'、publishedAt: null(src/app/api/admin/items/route.ts:138-142、:163-176)
  • PUT /api/admin/items(?id=):载荷改变 status 即 400;原样回传当前 status 放行 —— 编辑页每次保存(含自动保存)都会带上现有 status,一律拒绝会让所有编辑保存变红(src/app/api/admin/items/route.ts:222-226)
  • admin 编辑页的「状态」是只读展示(<p> + 文案「状态通过「发布」提交审核变更,不可在此直接修改」,src/app/admin/content/[modelCode]/[itemId]/page.tsx:821-828),不再是早前的 <select>;「发布」按钮走 handlePublishConfirm → 先落库再依次 submit(draft→review)、approve(review→published),账号无 publish 权限时 approve 返回 403、条目停在「待审核」,这是审批制的预期结果(同文件 :407-441)
  • 列表页只做状态展示、不提供改 status 的入口(src/app/admin/content/[modelCode]/page.tsx:197-204);客户端唯一的写状态通道是 adminApi.runWorkflow → POST /api/admin/items/[id]/workflow(src/lib/admin-api.ts:146-153),不存在「绕过状态机直接写 status」的路径