10404dbb36
同步工作区剩余变更,主要包括: - 营销页面组件与布局持续优化(about/news/services/solutions/team 等) - 详情页四层叙事组件、布局组件、UI 组件调整 - CMS 数据模型、API 路由、权限、工作流、站内通知、媒体管理扩展 - 新增/补充单元测试与 E2E 测试(cms-workflow.spec.ts 等) - ESLint 9 迁移、jest/tsconfig 配置更新、依赖调整 - 新增 ADR、CMS 评估文档、Release Review / Acceptance 报告 - 移除水墨装饰组件与大体积未使用字体文件
12 KiB
12 KiB
PRD:Novalon 轻量 Headless CMS 建设与内容全量迁移
本 PRD 基于 grilling 会话(docs/cms-evaluation.md)与 ADR-0007 整理而成。
Problem Statement
Novalon 官网目前存在两层脱节:
- CMS 数据层已建,但页面未切换:
src/lib/cms/已定义ContentModel、ContentItem、ContentZone、MediaAsset、AuditLog等类型,/api/cms/*与/api/admin/*路由也已存在,但所有营销页面仍从src/lib/constants/*.ts读取内容。内容更新必须改代码、走构建、再部署,市场/运营人员无法自主维护。 - 部署模式口径不一致:
README.md仍称项目为“纯静态网站”,但next.config.mjs已移除output: 'export'并存在/api/cms/revalidate、/api/cms/draft/*等假设服务端运行的路由。团队对是否采用混合渲染(SSR/ISR)缺乏统一决策,导致 CMS 的实时预览、一键发布、版本回滚等能力无法落地。 - 权限与工作流未实现:类型中虽有
ContentStatus和AuditLog,但缺少 RBAC 中间件、角色模型和“提交 → 审核 → 发布”的状态流转,review状态实际是死状态。 - 媒体管理停留在类型层面:
MediaAsset预留了local | oss | s3枚举,但无上传接口、无缩略图/格式转换、无 OSS 对接,媒体文件仍散落在public/。
结果是:官网内容运营效率低、协作流程不规范、CMS 投资无法产生实际价值。
Solution
在现有 src/lib/cms/ + Prisma + Next.js API 基础上,建设一个字段可配置、RBAC 模型+操作级、多语言预留、支持本地+OSS 双写、具备最简工作流的轻量 Headless CMS。配合已确认的**混合渲染(SSR/ISR)**部署模式,实现内容实时发布、草稿预览、版本回滚。
内容按页面类型全量迁移到 CMS:legal → news → team → cases → services → solutions → products → standalone-products → homepage zones,逐步废弃对应 src/lib/constants/*.ts 数据。
User Stories
系统管理员
- 作为系统管理员,我希望为不同用户分配角色(super_admin / content_admin / content_editor / reviewer / readonly),以便控制谁能操作哪些内容。
- 作为系统管理员,我希望按“角色 × 内容模型 × 操作(create/read/update/delete/publish)”配置权限矩阵,以便实现“编辑能写新闻但不能发布产品”这类需求。
- 作为系统管理员,我希望在管理后台查看操作日志(AuditLog),以便追踪内容变更与状态流转。
- 作为系统管理员,我希望为内容模型启用/禁用版本控制,以便灵活控制哪些模型需要保留历史版本。
内容编辑
- 作为内容编辑,我希望在管理后台创建和编辑新闻、案例、团队、产品、方案、服务、法律页等内容,而不需要修改代码。
- 作为内容编辑,我希望将内容保存为草稿,以便在正式发布前反复修改。
- 作为内容编辑,我希望将完成的内容提交审核,以便进入发布流程。
- 作为内容编辑,我希望收到审核结果通知(站内消息),以便知晓内容是否通过或需要修改。
- 作为内容编辑,我希望在内容被驳回后能看到驳回原因,并重新编辑提交。
- 作为内容编辑,我希望在媒体库上传图片并自动获得缩略图/WebP/AVIF 派生格式链接,以便在内容中引用优化后的图片。
- 作为内容编辑,我希望为同一个内容条目预留多语言字段(当前默认 zh-CN),以便未来扩展其他语言时无需重构。
内容审核员
- 作为审核员,我希望在管理后台看到所有待审核内容的列表和 badge,以便快速定位需要处理的内容。
- 作为审核员,我希望查看内容的完整版本差异(before/after),以便做出准确的审核判断。
- 作为审核员,我希望通过或驳回待审核内容,并填写审核意见,以便内容进入发布或回退到编辑状态。
- 作为审核员,我希望只有具备 publish 权限的人才能将内容发布到线上,以便保证内容质量。
网站访客
- 作为网站访客,我希望在内容发布后即刻看到最新内容(通过 ISR),而无需等待全站构建。
- 作为网站访客,我希望访问的法律条款、隐私政策等页面始终是最新版本,以便获取准确信息。
- 作为网站访客,我希望在移动端和桌面端都能正常浏览 CMS 驱动的内容,以便获得一致的响应式体验。
开发者/运维
- 作为开发者,我希望 CMS API 返回类型安全的数据结构,以便前端组件可靠渲染。
- 作为开发者,我希望内容发布后通过
/api/cms/revalidate刷新 ISR 缓存,以便前端页面自动更新。 - 作为开发者,我希望草稿预览通过 Draft Mode 实现,以便编辑和审核人员在正式发布前验证页面效果。
- 作为运维,我希望媒体文件在开发/测试环境存储在本地,在生产环境写入 OSS/S3,以便降低本地开发成本并保证生产性能。
- 作为运维,我希望 Nginx 配置正确代理 SSR/ISR/API 请求到 Next.js 服务,以便混合渲染架构正常运行。
- 作为开发者,我希望为权限中间件、数据访问层、API 路由编写单元和集成测试,以便在持续迭代中防止回归。
Implementation Decisions
1. 部署模式:混合渲染(SSR/ISR)
- 营销展示页面默认使用 SSG + ISR,CMS 内容发布后调用
/api/cms/revalidate刷新缓存。 - 管理后台、认证、CMS API、联系表单等使用 SSR/API Routes。
- 草稿预览通过 Next.js Draft Mode 实现,由
/api/cms/draft/enable与/api/cms/draft/disable控制。 - 该决策已记录于 ADR-0007。
2. CMS 路线:坚持自研轻量 Headless CMS
- 基于现有
src/lib/cms/+ Prisma + Next.js API 继续建设。 - 不引入 Strapi、Payload、Directus 等开源方案,也不采购 SaaS CMS。
- 核心范围限定为:数据模型扩展、RBAC、媒体管理、最简工作流、内容迁移、管理后台界面。
3. 数据模型扩展
3.1 ContentItem 增加 locale 字段
- 默认值为
'zh-CN'。 - 为存在
slug的模型增加唯一索引(slug, modelCode, locale),保证同一模型同语言下 slug 唯一。 - 多语言内容通过新增
locale记录实现,不改动现有字段结构。
3.2 RBAC 数据模型
Role:super_admin、content_admin、content_editor、reviewer、readonly。Permission:角色 × 内容模型 × 操作(create / read / update / delete / publish)。- 用户与角色多对多关联。
- 在
/api/admin/*路由增加权限中间件,拒绝未授权请求。
权限矩阵示例(来自 grilling 决策):
| 角色 | news | product | legal |
|---|---|---|---|
| content_editor | create/read/update | read | read |
| reviewer | read/publish | read/publish | read/publish |
| readonly | read | read | read |
3.3 review 状态语义
review= 已提交待审核。- 仅拥有对应模型
publish权限的角色可以审核通过(进入published)或驳回(回到draft)。 - 状态流转:draft → review → published → archived;draft → review → draft(驳回)。
4. 媒体管理模块
- 完成
/api/admin/media上传接口。 - 本地环境/测试落盘到
public/uploads/;生产环境写入 OSS/S3。 - 上传后生成缩略图、WebP、AVIF 派生格式;
MediaAsset记录原图与所有派生格式 URL。 - 存储类型枚举沿用现有
local | oss | s3。
5. 工作流引擎(最简版)
- 状态机仅支持
draft → review → published / archived,驳回回draft。 - 站内消息通知:审核人收到待审核 badge,提交人收到审核通过/驳回通知。
AuditLog记录每次状态流转的 before/after、操作人、时间戳。- 不实现邮件、企业微信、钉钉等外部通知。
6. 内容迁移策略
- 按页面类型全量迁移,顺序为:legal → news → team → cases → services → solutions → products → standalone-products → homepage zones。
- 每个页面类型迁移时,同步废弃
src/lib/constants/*.ts中对应数据,改为从 CMS API 获取。 - 页面侧使用 ISR,发布/回滚后调用
/api/cms/revalidate。 - 独立产品(standalone-products)需要单独设计
standalone-product内容模型,支持技术参数、合规认证等字段分组。
7. 管理后台界面
- 模型管理:CRUD 内容模型与字段定义。
- 内容编辑:按模型展示列表与表单,支持草稿、提交审核、发布。
- 媒体库:上传、预览、复制链接、删除。
- 角色权限:角色列表、权限矩阵配置。
- 工作流审核:待审核列表、版本对比、通过/驳回。
- 通知中心:站内消息列表与 badge。
8. API 契约
- 保持现有
/api/admin/*与/api/cms/*路由结构。 - 新增
/api/admin/media用于媒体上传与管理。 - 新增
/api/admin/roles与/api/admin/permissions用于 RBAC 配置。 - 新增
/api/admin/notifications用于站内消息。 /api/cms/revalidate接收path或tag参数,刷新 ISR 缓存。/api/cms/draft/*继续用于 Draft Mode 启停。
Testing Decisions
测试原则
- 只测外部行为,不测实现细节。例如:测试“未授权用户不能发布新闻”,而不是测试权限中间件的内部判断逻辑。
- 优先复用现有测试基础设施:Jest 用于单元/集成测试,Playwright 用于 E2E。
测试接缝
- 数据访问层:
src/lib/cms/data-server.ts中的增删改查函数。 - 权限中间件:在
/api/admin/*路由上的授权行为。 - API 路由:
/api/admin/content/*、/api/admin/media、/api/cms/revalidate的响应状态与数据。 - 管理后台界面:Playwright 覆盖登录 → 创建草稿 → 提交审核 → 审核通过 → 页面可见的完整流程。
具体测试覆盖
- 单元测试:RBAC 权限计算、状态机流转、媒体元数据生成、locale 唯一索引校验。
- 集成测试:带权限中间件的 API 路由、Prisma 事务与回滚、ISR revalidate 调用。
- E2E 测试:
- 管理员创建角色并分配权限;
- 编辑登录后创建新闻草稿并提交审核;
- 审核人登录后通过新闻;
- 前台新闻页面在 revalidate 后显示新内容;
- 未授权用户尝试发布内容被 403。
现有参考
src/lib/cms/data-server.test.ts已存在 CMS 数据层测试,可扩展覆盖 locale 与 RBAC 相关场景。e2e/目录已有 Playwright 配置与测试,可新增 CMS 管理后台流程测试。
Out of Scope
- 字段级权限:本期只实现模型级 + 操作级 RBAC,字段级权限作为未来扩展预留。
- 外部通知:不实现邮件、企业微信、钉钉等工作流通知,仅保留站内消息。
- 内容日历与任务分配:不实现排期、指派、协作看板等高级 CMS 功能。
- 表单/线索统一管理:联系表单继续由独立
/api/contact/*处理,本期不纳入 CMS。 - 多语言前端切换:虽然预留
locale字段,但本期不实现语言切换 UI 与多语言内容录入,仅保证数据模型可扩展。 - 替换开源/SaaS CMS:本期坚持自研,不引入 Strapi、Payload、Sanity 等第三方 CMS。
- 字段级版本历史:版本控制到内容条目级别,不细化到单个字段。
Further Notes
- 风险:全量迁移工作量较大,可能挤压 7 月其他任务。缓解措施是按 P0 → P1 → P2 分阶段交付,内容迁移可逐页面进行。
- 风险:混合渲染对 Nginx 配置提出新要求。缓解措施是更新
nginx-static-production.conf,确保/api/*、/admin/*及动态路由正确代理到 Next.js 服务。 - 风险:SQLite 高并发写入可能成为瓶颈。当前后台管理并发低,可接受;未来可平滑迁移至 PostgreSQL,数据模型无需改动。
- 参考文档: