Files
wagtailcms/documents/设计方案分析与完善版.md
T
Zhengen f37571f395 feat(rbac): 落地 Wagtail Group + GroupPagePermission/GroupCollectionPermission/Snippet 权限体系
新增 setup_rbac_groups 幂等管理命令,按设计方案 §2.8 矩阵创建 5 个 Group(站点管理员/编辑/审核员/作者/查看者),配置页面/图片文档/Snippet/用户管理权限;Author 仅编辑自己页面利用 Wagtail 内置 OwnershipPermissionPolicy 实现,无需自定义 hook;新增 7 个单元测试,pytest 33→40 全部通过。
2026-08-07 17:07:39 +08:00

405 lines
31 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 企业官网 Headless CMS 设计方案 —— 分析与完善版
> 本文档基于 `documents/` 目录下现有的 5 份 PDF 设计文档整理分析,并结合当前代码仓库的真实状态(`manage.py` + 裸 Django 6.0 项目骨架,尚未引入 Wagtail),产出一份可直接指导开发的**完善版设计方案**。
---
## 一、现有文档分析
### 1.1 文档清单
| 文档 | 定位 |
|---|---|
| 基于 Wagtail 的现代前后端分离 CMS 设计方案.pdf | 通用架构设计(30 章节,最全面) |
| Next.js + Wagtail 企业级 Headless CMS 完整项目代码架构.pdf | Monorepo + 代码架构细化 |
| Wagtail CMS Backend 完整源码(企业级 Headless CMS.pdf | 后端代码骨架示例 |
| Next.js 企业官网完整源码模板(基于 Wagtail Headless CMS.pdf | 前端代码骨架示例 |
| 软件公司企业官网设计方案(基于 Wagtail Headless CMS.pdf | 官网 IA / 页面 / 视觉设计 |
| 整体设计图.png | 首页高保真视觉稿(TechFlow 品牌示例) |
### 1.2 共性结论
5 份文档高度一致地收敛到同一技术选型:**Django 5 + Wagtail 7 + DRF + Strawberry GraphQL + Next.js 15 (React 19) + PostgreSQL + Redis + Celery + OpenSearch/Elasticsearch + MinIO/S3 + Docker/K8s**。这说明该技术栈选型是合理、成熟的,可以直接作为最终方案基线,**不需要重新论证**。
### 1.3 已发现的不足 / 待完善点
逐份文档都以"罗列技术名词 + 目录结构"为主,**普遍缺少可落地的细节**,具体归纳如下:
**A. 架构完整性缺口**
1. 只给出多租户"共享库 / Schema 隔离"两个方案名词,未给出本项目该如何取舍(本项目是单一企业官网,暂不需要多租户,过度设计)。
2. 没有数据库 ER 图 / 字段级设计,`Page` 模型都只写了 1-2 个示例字段。
3. 没有 Headless 预览(Preview)机制设计 —— 编辑在 Wagtail 后台点"预览"Next.js 如何展示草稿内容(draft mode / preview token)完全没有提及。
4. 没有"发布后前端缓存失效"的具体实现(只画了 `Content Published → Event Bus → Webhook → Frontend Revalidate` 的框图,没有落地到 Wagtail `page_published` signal + Next.js `revalidateTag` 的代码级方案)。
5. API 只给了 URL 列表,没有分页、排序、过滤、错误码、版本弃用策略等规范。
**B. 安全设计不落地**
6. 安全章节只列了名词(HTTPS/CSP/WAF/JWT Rotation…),没有给出 Django 层面应设置的具体 settings 项(`SECURE_HSTS_SECONDS``SESSION_COOKIE_SECURE``CSRF_TRUSTED_ORIGINS`、DRF 节流类等)。
7. `CORS_ALLOW_ALL_ORIGINS = True` 是示例代码里的真实隐患,生产环境必须收敛为白名单。
8. 没有考虑富文本 XSSWagtail RichTextField 需要配置白名单 `WAGTAILADMIN_RICH_TEXT_EDITORS` / `bleach`)。
**C. 本地化 / 合规缺失(重要)**
9. 所有文档默认使用 **AWS S3 + Cloudflare CDN + Google 字体生态**,但"整体设计图.png"高保真稿明确显示目标是**中国大陆企业官网**(简体中文导航、"沪ICP备xxxxxxxx号-1"、中国移动/招商银行/中国平安/上汽集团/美团 等客户 Logo)。这是一个关键但被忽视的落地风险:
- AWS S3/Cloudflare 在中国大陆访问速度和合规性都存在问题,需替换为 **阿里云 OSS/腾讯云 COS + 阿里云 CDN/腾讯云 CDN** 等国内方案。
- 网站备案:需要 **ICP 备案**(个人/企业备案流程、幕布拍照要求等),这会影响上线时间表,必须提前规划。
- 需要 **隐私政策 / Cookie 同意条**,遵循《个人信息保护法》(PIPL),而不是文档里默认的 GDPR 话术。
- Google Fonts / Google Maps 在中国大陆不可用,需要自托管字体(思源黑体/HarmonyOS Sans 子集化)、替换为高德/腾讯地图。
- 人机验证码建议用极验/腾讯云验证码而非 reCAPTCHA。
- 表单营销自动化提到 HubSpot/Salesforce,国内客户更常用企业微信 + 探迹/纷享销客等 CRM,需要补充可选国内方案。
**D. 工程化缺口**
10. 没有测试策略(单元测试、集成测试、E2E),文档提到 `pytest` 仅在 lint 章节一笔带过。
11. 没有环境变量与密钥管理方案(`.env.example`、多环境 settings 分层的实际代码)。
12. 没有健康检查 / 就绪探针(`/healthz``/readyz`),K8s 部署缺了这个会导致滚动更新时无法探活。
13. 日志没有约定格式(结构化 JSON 日志 + request-id 贯穿链路),排障困难。
**E. 内容建模细节不足**
14. StreamField Block 只给了 `HeroBlock``FeatureBlock` 两个例子,官网设计方案里列出的 14 个 BlockStats/CTA/Pricing/FAQ/CaseStudy/Timeline/Team/LogoCloud/TechStack/BlogList/Video/Form 等)都没有代码,容易导致前后端字段不对齐。
15. 没有说明图片如何做响应式(Wagtail `Renditions` + `srcset`)与前端 `next/image` 的对接方式。
### 1.4 与当前代码仓库现状的差距
当前仓库仅是 `django-admin startproject` 生成的裸项目(Django 6.0.5,未安装 Wagtail/DRF`INSTALLED_APPS` 只有 Django 内置应用,`urls.py` 只有 `admin/`)。也就是说,**目前处于"设计文档阶段",代码尚未开始落地**,因此完善设计的同时,应当同步启动 Phase 1 的工程骨架搭建(见本文档第二部分 §18 路线图,以及仓库中已完成的初始化改动)。
---
## 二、完善后的整体设计方案
### 2.1 项目目标
在原有目标基础上明确补充**中国大陆本地化与合规目标**:
- 面向中国大陆企业客户的现代化 B2B 官网 + Headless CMS 内容中台
- 前后端彻底分离:Wagtail(内容与 API+ Next.js(展示层)
- 满足 ICP 备案、PIPL 合规、国内 CDN/存储加速
- 可平滑演进到多语言(中/英/日)、多站点,**暂不做多租户**(YAGNI,待 SaaS 化需求明确后再引入 `django-tenants`
- 内容编辑体验优先(StreamField 组件化、预览、审核流)
### 2.2 总体架构
```mermaid
flowchart TB
subgraph Client
Browser["浏览器 / 移动端"]
end
subgraph Edge["Edge / CDN"]
CDN["国内 CDN(阿里云 CDN / 腾讯云 CDN"]
end
subgraph FE["Next.js 前端(App Router"]
NextApp["React 19 + SSR/ISR"]
end
subgraph Gateway
Nginx["Nginx / Traefik 网关"]
end
subgraph BE["Wagtail 后端"]
Wagtail["Django 5 + Wagtail 7"]
DRF["DRF REST v2"]
GQL["Strawberry GraphQL"]
end
subgraph Infra["基础设施"]
PG[(PostgreSQL)]
Redis[(Redis)]
Search[(OpenSearch)]
OSS[(阿里云 OSS / 腾讯云 COS)]
Celery["Celery Worker"]
end
Browser --> CDN --> NextApp --> Nginx --> Wagtail
Wagtail --> DRF
Wagtail --> GQL
Wagtail --> PG
Wagtail --> Redis
Wagtail --> Search
Wagtail --> OSS
Wagtail -. 异步任务 .-> Celery
Wagtail -- "publish webhook" --> NextApp
```
### 2.3 技术栈(含国内替代方案对照)
| 分层 | 通用方案(原文档) | 中国大陆推荐替代 / 补充 |
|---|---|---|
| 对象存储 | AWS S3 / MinIO | 阿里云 OSS / 腾讯云 COS`django-storages` 均支持 S3 兼容协议) |
| CDN | Cloudflare | 阿里云 CDN、腾讯云 CDN、又拍云 |
| 字体 | Google Fonts | 思源黑体 / HarmonyOS Sans,构建期子集化后自托管,避免运行时依赖 Google |
| 地图 | Google Maps | 高德地图 / 腾讯地图 JS SDK |
| 验证码 | reCAPTCHA | 极验 GeeTest / 腾讯云验证码 |
| 短信/通知 | 无 | 阿里云短信服务 + 企业微信机器人 Webhook(替代 Slack |
| 登录方式(可选) | OAuth2/Auth0/Keycloak | 保留通用 OAuth2;如需 C 端可加"企业微信扫码登录" |
| 搜索 | Elasticsearch/OpenSearch | 同左,国内可用阿里云 OpenSearch 托管版或自建 |
| 备案 | 无 | 需在页脚展示 ICP 备案号,域名/服务器需完成 ICP 备案后方可上线 |
其余后端(Django/Wagtail/DRF/GraphQL/PostgreSQL/Redis/Celery)与前端(Next.js/React/Tailwind/Zustand/React Query)技术栈维持原文档选型不变,属于合理选型。
### 2.4 Monorepo 目录结构(沿用并细化)
```
wagtailcms/ # 当前仓库根目录(后端仓库,独立 git 仓库)
├── manage.py
├── requirements/
│ ├── base.txt
│ ├── dev.txt
│ └── production.txt
├── wagtailcms/ # 设置包(实际未按早期草案重命名为 config/,沿用项目同名包)
│ ├── settings/
│ │ ├── base.py
│ │ ├── dev.py
│ │ └── production.py
│ ├── urls.py
│ ├── wsgi.py
│ └── asgi.py
├── apps/
│ ├── core/ # 基础抽象:SEOablePage、健康检查、公共 Block ✅
│ ├── home/ # 首页 ✅
│ ├── blog/ # 技术博客 ✅
│ ├── products/ # 产品 ✅
│ ├── solutions/ # 解决方案(行业)✅
│ ├── cases/ # 客户案例 ✅
│ ├── forms/ # 线索表单 ✅
│ └── api/ # DRF + Wagtail API v2 路由 ✅
├── documents/ # 设计文档(当前目录)
└── (frontend/ 已拆分为独立仓库,不在本仓库内,见下方说明)
```
> 说明:前后端已**拆分为两个独立 git 仓库**(后端本仓库 + `frontend/` 独立仓库),通过 REST APIWagtail API v2 + `/api/v1/custom/`)契约解耦。后端根 `.gitignore` 已排除 `frontend/`,避免嵌套仓库冲突。两个仓库目前均为本地仓库,尚未配置远程/推送。
### 2.5 内容模型与数据库设计
在原有 `Page + StreamField` 思路基础上,补齐字段级设计:
```python
# apps/core/models.py
class SEOablePage(Page):
"""所有业务页面的抽象基类,统一 SEO 字段"""
seo_title = models.CharField(max_length=70, blank=True)
seo_description = models.CharField(max_length=160, blank=True)
og_image = models.ForeignKey(
"wagtailimages.Image", null=True, blank=True,
on_delete=models.SET_NULL, related_name="+",
)
canonical_url = models.URLField(blank=True)
schema_json = models.JSONField(blank=True, default=dict)
promote_panels = Page.promote_panels + [
FieldPanel("seo_title"),
FieldPanel("seo_description"),
FieldPanel("og_image"),
FieldPanel("canonical_url"),
FieldPanel("schema_json"),
]
class Meta:
abstract = True
```
页面树结构(细化到本项目实际官网 IA):
```
HomePage
├── ProductIndexPage
│ └── ProductPage[]
├── SolutionIndexPage
│ └── SolutionPage[]
├── CaseStudyIndexPage
│ └── CaseStudyPage[]
├── BlogIndexPage
│ └── BlogPage[]
├── AboutPage
├── CareerIndexPage
│ └── CareerPage[]
└── ContactPage
```
Snippet(非页面树内容,用于跨页面复用):`TeamMember``Testimonial``Partner/LogoCloud``NavigationMenu``SiteSettings``BaseSiteSetting`,存放公司电话/ICP备案号/社交账号等全局配置)。
### 2.6 StreamField 组件体系(补全清单)
| Block | 用途 | 关键字段 | 状态 |
|---|---|---|---|
| HeroBlock | 首屏 | title, subtitle, cta_buttons(list), background_image/video | ✅ 已实现 |
| StatsBlock | 数据展示 | items: [{value, label}] | ✅ 已实现 |
| FeatureGridBlock | 能力/优势 | icon, title, description | ✅ 已实现(对应原 FeatureBlock |
| ProductCardBlock | 产品矩阵 | title, description, image, link | ✅ 已实现 |
| LogoCloudBlock | 客户 Logo 墙 | logos: [{url, title}](暂为图片列表,未接入 Partner Snippet | ✅ 已实现(简化版) |
| CaseStudyBlock | 案例卡片 | case_page: PageChooser(cases.CaseStudyPage), summary | ✅ 已实现 |
| PricingBlock | 定价 | plans: [{name, price, features, highlighted, button}] | ✅ 已实现 |
| FAQBlock | 常见问题 | items: [{question, answer(richtext)}] | ✅ 已实现 |
| TimelineBlock | 发展历程 | items: [{year, event}] | ✅ 已实现 |
| TeamBlock | 团队展示 | members: [SnippetChooser(TeamMember)]`get_api_representation` 展开完整字段(name/role/bio/photo)供前端渲染 | ✅ 已实现(依赖 TeamMember Snippet |
| TechStackBlock | 技术架构图 | items: [{icon, label}] | ✅ 已实现 |
| VideoBlock | 视频 | video_url(建议自建 OSS/COS 直链或腾讯视频/哔哩哔哩等国内平台嵌入地址),poster | ✅ 已实现 |
| FormBlock | 线索表单 | form: SnippetChooser(forms.FormDefinition)`get_api_representation` 展开完整字段定义供前端渲染 | ✅ 已实现 |
| CTABlock | 行动号召 | heading, button_text, button_link | ✅ 已实现 |
所有 Block 统一放在 `apps/core/blocks.py``COMMON_BLOCKS`,前端 `frontend/blocks/BlockRenderer.tsx` 维护对应的"type → 组件"映射表,目前两者手动保持一致;CI 自动化一致性校验仍待建立(见 §2.17 风险表)。
### 2.7 Headless API 设计规范(补全缺失细节)
**RESTWagtail API v2 + DRF 自定义)**
- 统一前缀:`/api/v2/`Wagtail 官方 API v2+ `/api/v1/custom/`(自定义业务接口,如表单提交)
- 分页:统一 `limit` / `offset`,响应体包含 `meta: {total, limit, offset}`
- 排序/过滤:`?order=-first_published_at``?type=blog.BlogPage`
- 错误响应统一结构:
```json
{ "error": { "code": "NOT_FOUND", "message": "..." } }
```
- 限流:DRF `ScopedRateThrottle`,公开只读接口 `100/min`,表单提交接口 `5/min`(防刷)
- 版本弃用策略:新版本上线后旧版本保留至少 2 个发布周期,响应头带 `Deprecation` / `Sunset`
**预览模式(已实现,详见 §2.16)**
- 后端引入 `wagtail-headless-preview`0.9.0),`SEOablePage` 混入 `HeadlessPreviewMixin`,编辑器点击"预览"时自动将草稿序列化存入 `PagePreview` 表,生成带签名 token 的预览链接并重定向到 `{FRONTEND_BASE_URL}/preview`
- 自定义 `PagePreviewAPIViewSet``apps/api/urls.py`)挂载于 `/api/v2/page_preview/`,根据 `content_type`+`token` 从 `PagePreview` 取回草稿快照并序列化返回
- Next.js `/preview` Route Handler 校验 token 有效后开启内置 Draft Modecookie 方式,非 Redis),并重定向到对应内容类型的前端路由;页面组件通过 `getPreviewOrFallback` 优先拉取草稿,否则回退到正常已发布内容
**发布后缓存失效(原文档只有框图,这里补齐实现)**
```python
# apps/core/signals.py
from wagtail.signals import page_published
@receiver(page_published)
def notify_frontend_revalidate(sender, instance, **kwargs):
requests.post(
settings.FRONTEND_REVALIDATE_URL,
json={"tags": [f"page:{instance.id}", instance.url_path]},
headers={"Authorization": f"Bearer {settings.REVALIDATE_SECRET}"},
timeout=3,
)
```
Next.js 侧对应 `revalidateTag` API Route,收到 webhook 后失效对应 ISR 缓存标签。
### 2.8 权限体系(RBAC 矩阵,细化落地)
| 角色 | 页面编辑 | 提交审核 | 审批发布 | 用户管理 | Snippet 管理 | Django Admin |
|---|---|---|---|---|---|---|
| Super Admin | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Tenant/Site Admin | ✅ | ✅ | ✅ | ✅(本站点) | ✅ | ❌ |
| Editor | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ |
| Reviewer | 查看 | ✅ | ✅ | ❌ | 查看 | ❌ |
| Author | ✅(仅自己) | ✅ | ❌ | ❌ | ❌ | ❌ |
| Viewer | 查看 | ❌ | ❌ | ❌ | ❌ | ❌ |
落地方式:使用 Wagtail 原生 `Group` + `GroupPagePermission` + `collections` 权限,不需要额外引入 RBAC 框架。
**已落地(`python manage.py setup_rbac_groups`,幂等命令,`apps/core/management/commands/setup_rbac_groups.py`):**
- Super Admin 直接对应 Django `is_superuser=True`,不建单独 Group;其余 5 个角色对应 5 个 Wagtail `Group`:站点管理员/编辑/审核员/作者/查看者。
- 页面权限(`GroupPagePermission`,挂载于页面树根节点 `depth=1`,适用于当前单站点部署):站点管理员拥有全部类型(add/change/publish/delete/bulk_delete/lock/unlock/view);编辑拥有 add/change/view(无 publish,创建的页面停留在草稿态等待发布);审核员拥有 change/publish/view(用于打开页面并执行发布,属于对“页面编辑=查看”矩阵条目的必要放宽);**作者仅拥有 add/view(不含 change**——利用 Wagtail 内置 `OwnershipPermissionPolicy`:只有 `add` 权限时,用户可创建新页面,且只能编辑/删除自己拥有(`owner`)的页面,天然满足“仅自己”要求,无需自定义 `wagtail_hooks`;查看者仅有 view。
- Snippet 权限(`TeamMember`/`Testimonial`/`Partner`/`NavigationMenu`/`FormDefinition`):站点管理员与编辑均为 add/change/delete/view,审核员仅 view,作者/查看者无权限。`Lead`(线索,含 PIPL 个人信息)出于数据保护考虑做了比矩阵更严格的收紧:仅站点管理员可管理,审核员仅 view,编辑/作者均不授予。`SiteSettings` 作为站点级单例配置仅站点管理员可 change/view。
- Collection(图片/文档)权限(`GroupCollectionPermission`,挂载于根 Collection):站点管理员 add/change/delete/choose/view;编辑与作者 add/change/choose/view(便于上传 StreamField 图片);审核员 choose/view;查看者无。
- 用户管理权限(`auth.add_user`/`change_user`/`delete_user`/`view_user`)仅授予站点管理员,对应矩阵“用户管理”列;“Django Admin”列不通过 Group 授予(Django Admin 访问由账号的 `is_staff` 属性决定,仅超级用户具备)。
- 简化说明:设计方案 §2.9 的 Workflow 审核模块尚未实现,因此“提交审核/审批发布”当前通过“编辑者无 publish、审核员/站点管理员有 publish”的权限差异形成审核闸门,而非正式的 Wagtail `Workflow`/`TaskState`;待 Workflow 模块落地后可在现有 Group 基础上叠加 `GroupApprovalTask`,无需重新设计权限组。
- 测试:`apps/core/tests.py` 新增 7 个用例覆盖 Group 创建、`access_admin` 授予、页面权限矩阵、Author 仅编辑自己页面、Lead 权限收紧、用户管理权限范围、命令幂等性。
### 2.9 工作流与内容审核
采用 Wagtail 5+ 内置 `Workflow` 模块,定义 `草稿 → 编辑自检 → 审核 → 通过 → 定时发布`,并在 `TaskState` 变更时通过企业微信机器人 Webhook 通知责任人(替代原文档 Slack 方案)。
### 2.10 搜索、SEO、多语言
- 搜索:中文分词是关键缺失点,OpenSearch 需配置 **IK 分词插件**(原文档完全没提中文分词,这是国内落地必须解决的问题)。
- SEO:站点级 `sitemap.xml``wagtail.contrib.sitemaps`+ 页面级 `schema_json`JSON-LD+ `robots.txt` 动态生成。
- 多语言:`WAGTAIL_I18N_ENABLED` + `wagtail-localize`URL 采用 `/zh/`、`/en/` 前缀,默认 `/zh/` 对国内用户免前缀(根路径直接是中文)。
### 2.11 安全设计(落地到具体配置)
```python
# config/settings/production.py
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SECURE_HSTS_SECONDS = 31536000
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_CONTENT_TYPE_NOSNIFF = True
X_FRAME_OPTIONS = "DENY"
# 阿里云 SLB / 腾讯云 CLB 等负载均衡在边缘终止 TLS 时,需要此项避免 SECURE_SSL_REDIRECT 死循环重定向
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
CORS_ALLOWED_ORIGINS = env.list("CORS_ALLOWED_ORIGINS", default=[])
CSRF_TRUSTED_ORIGINS = env.list("CSRF_TRUSTED_ORIGINS", default=[])
REST_FRAMEWORK = {
"DEFAULT_THROTTLE_CLASSES": ["rest_framework.throttling.ScopedRateThrottle"],
"DEFAULT_THROTTLE_RATES": {"public": "100/min", "forms": "5/min"},
}
```
富文本安全:`WAGTAILADMIN_RICH_TEXT_EDITORS` 限定可用 feature(禁用危险 HTML embed),后端渲染前统一走 `bleach` 白名单清洗。
**白名单配置说明**(三者含义不同,勿混淆):
- `ALLOWED_HOSTS`:填**后端自身**域名(如 `cms.example.com`),校验 HTTP `Host` 头,防 Host 头注入。
- `CORS_ALLOWED_ORIGINS`:填 **Next.js 前端**域名(如 `https://www.example.com`),允许浏览器跨域调用 Wagtail REST API`/api/v1/custom/leads/` 为匿名公开接口,仅受此白名单 + 限流保护,不受 CSRF 校验。
- `CSRF_TRUSTED_ORIGINS`:仅在"浏览器带 session Cookie 发起跨站不安全请求"时生效(如已登录编辑者调用需认证的预览/管理接口),填涉及认证态跨域请求的域名。
- 三者均通过 `.env` 中的 `ALLOWED_HOSTS`/`CORS_ALLOWED_ORIGINS`/`CSRF_TRUSTED_ORIGINS` 环境变量注入,部署时按实际域名填写即可,无需改代码(参见 `.env.example`)。
### 2.12 可观测性与运维
- 健康检查:`/healthz`(存活)、`/readyz`(含数据库/Redis 连通性检查),供 K8s liveness/readiness 探针使用。
- 日志:`structlog` 输出 JSON,贯穿 `request_id`(中间件生成,透传前端 `X-Request-Id`)。
- 监控:Prometheus + Grafana`django-prometheus`),错误监控 Sentry,链路追踪 OpenTelemetry。
### 2.13 测试策略(原文档缺失,新增)
| 层级 | 工具 | 覆盖内容 |
|---|---|---|
| 单元测试 | pytest + pytest-django + factory_boy | Model、Block、Serializer |
| 集成测试 | pytest + DRF APIClient | API 端到端响应结构 |
| 前端组件测试 | Vitest + React Testing Library | Block 组件渲染 |
| E2E | Playwright | 关键转化路径(首页 → 联系我们 → 表单提交) |
| 契约测试 | schemathesis(基于 OpenAPI schema | 防止后端 API 破坏性变更 |
CI 中要求单元测试覆盖率不低于 70%,核心 `apps/forms`(涉及线索转化)不低于 90%。
### 2.14 CI/CD 与环境管理
- `.env.example` 统一列出所有必需环境变量,密钥类使用 GitHub Actions Secrets / 云厂商 KMS,不进仓库。
- 分支策略:`main`(生产)/ `staging`(预发)/ 功能分支 PR,合并前必须通过 Lint + Test + Build。
- 部署流水线:`Lint → Test → Build Image → 推送镜像仓库(阿里云 ACR/腾讯云 TCR)→ 部署 K8s(或轻量场景用 Docker Compose + 云服务器)`。
### 2.15 合规与本地化清单(新增,落地检查表)
- [x] 完成 ICP 备案(域名 + 服务器均已在国内云厂商完成备案)
- [x] 页脚展示备案号并链接工信部备案查询(`frontend/components/layout/Footer.tsx`,实际备案号 冀ICP备2025130506号-1
- [x] 隐私政策内容已上线(`apps.core.SimpleContentPage` 通用富文本内容页模型 + `seed_privacy_policy` 管理命令 + 前端 `/privacy-policy` 页面 + 页脚链接,内容覆盖 PIPL 要求的收集目的/用途/用户权利/Cookie 说明等)
- [x] Cookie 同意条控件(`frontend/components/layout/CookieConsent.tsx``"use client"` 组件,首次访问展示"接受/拒绝"横幅,选择结果存入 `localStorage`,已挂载到根 layout
- [x] 字体/地图/验证码/CDN 排查:字体已用系统字体栈(`frontend/app/layout.tsx`,未引入 `next/font/google` 等国外字体服务);地图/视频等嵌入类 Block 尚未开发,落地时需选用腾讯地图/高德地图、腾讯视频/B站等国内可访问服务替代 Google Maps/YouTube(见 §2.6 待办);验证码此前仅靠限流(`ScopedRateThrottle` forms scope 5/min)防护,未接入任何第三方验证码,现补充蜜罐反垃圾字段 `website``apps/forms/serializers.py`/`views.py` + `frontend/blocks/form/LeadForm.tsx`,正常用户不可见,机器人误填后静默返回"成功"但不落库不发邮件),无需接入极验/腾讯云验证码 API 即可降低自动化垃圾提交,未来如需更强防护可再接入国内验证码厂商;CDN 方面为 `production.py` 新增 `AWS_S3_CUSTOM_DOMAIN``OSS_CDN_DOMAIN` 环境变量)用于绑定阿里云/腾讯云 CDN 加速域名到 OSS/COS 源站,避免直接暴露源站地址;另确认 `sentry-sdk` 虽在 `requirements/production.txt` 中但代码里从未调用 `sentry_sdk.init()`,为休眠依赖,未来若启用需评估国内可访问性(自建 GlitchTip 或国内 APM 替代 sentry.io
- [x] 表单收集的个人信息支持删除请求(PIPL 数据主体权利):新增 `LeadDeletionRequest` 模型(`apps/forms/models.py`),匿名用户凭提交表单时留下的邮箱在 `/privacy-policy/delete-request` 页面发起申请(`POST /api/v1/custom/leads/deletion-requests/`,限流 forms scope 5/min,无论邮箱是否存在关联数据均返回相同提示以避免探测),系统发送含 24 小时有效 token 的确认邮件,用户在 `/privacy-policy/delete-confirm?token=` 页面点击确认后(`POST /api/v1/custom/leads/deletion-requests/confirm/`)按邮箱在 `Lead.data` 中做匹配并删除对应记录;`LeadDeletionRequest` 处理记录通过 Django Admin(只读)供合规审计;隐私政策页面已加入申请入口链接
### 2.16 开发路线图(对齐当前仓库状态)
| 阶段 | 目标 | 状态 |
|---|---|---|
| **Phase 0** | 需求与设计文档完善(本文档) | ✅ 已完成 |
| **Phase 1** | Wagtail 基础 CMS + REST API + Next.js 首页渲染 + 基础 SEO | 🚧 进行中(进度见下方清单) |
| **Phase 2** | GraphQL、OpenSearch 中文搜索、工作流审核、多语言 | 待规划 |
| **Phase 3** | AI 能力(摘要/翻译/SEO 重写)、SaaS 化评估(视业务需要再决定是否引入多租户) | 待规划 |
**Phase 1 详细进度:**
已完成:
- [x] 后端 Django + Wagtail 项目骨架,分层 settingsbase/dev/production),独立 git 仓库并已提交
- [x] `apps/core``SEOablePage` 抽象基类、`COMMON_BLOCKS`14/14 个 Block 已全部实现,见 §2.6)、健康检查 `/healthz` `/readyz`、发布后 `page_published` signal → 前端 revalidate webhook
- [x] `apps/home`、`apps/blog`(含标签 `ClusterTaggableManager`
- [x] `apps/products`ProductIndexPage/ProductPage)、`apps/cases`CaseStudyIndexPage/CaseStudyPage)、`apps/solutions`SolutionIndexPage/SolutionPage
- [x] `apps/forms``FormDefinition`/`FormDefinitionField`/`Lead`Snippet 方式)+ 提交 API`/api/v1/custom/leads/`,限流 + 邮件通知)
- [x] Wagtail API v2 挂载、分页/过滤/排序(Wagtail 内置)
- [x] Next.js 前端脚手架(独立仓库):Header/Footer/layout、首页、博客/产品/案例/解决方案列表与详情页、`BlockRenderer`、ISR + revalidate route`lint`/`build` 已验证通过
- [x] pytest 基础测试框架(pytest-django + factory_boy):`pytest.ini` + `conftest.py`root_page/home_page fixtures),为 `apps/core`SEOablePage 字段、FormBlock API 表示、SimpleContentPage 页面树、Snippet 模型与只读 API)、`apps/products`、`apps/cases`、`apps/forms`(线索提交 API、蜜罐反垃圾字段、PIPL 数据删除权申请/确认流程)编写了基础单元测试,共 31 个用例均通过
- [x] `apps/core.SimpleContentPage`:通用富文本法务/说明类页面模型,用于隐私政策等内容,配套 `seed_privacy_policy` management command 用于幂等创建/更新隐私政策页面(需在 HomePage 实例存在后手动运行)
- [x] Snippet`TeamMember`/`Testimonial`/`Partner`(简单 `@register_snippet`,均含 `order` 排序字段)、`NavigationMenu`+`NavigationMenuItem``ClusterableModel`+`Orderable`+`InlinePanel`,内部页面/外部链接二选一)、`SiteSettings``wagtail.contrib.settings` + `BaseSiteSetting`,公司信息/ICP备案/社交账号全局配置)均已创建;只读 API 挂载于 `/api/v1/custom/core/``team/`、`testimonials/`、`partners/`、`navigation/?name=`、`site-settings/`,限流 public 100/min);前端 `Header`/`Footer` 已接入 `NavigationMenu`/`SiteSettings`(接口不可用时回退静态内容),`TeamMember`/`Testimonial`/`Partner` 已提供 service 层,页面级展示留待对应 StreamField Block(见下)落地时接入
- [x] 预览模式:后端引入 `wagtail-headless-preview`0.9.0),`SEOablePage` 混入 `HeadlessPreviewMixin`(自动覆盖全部 6 个页面模型),新增自定义 `PagePreviewAPIViewSet` 挂载于 `/api/v2/page_preview/`;前端新增 `services/preview.service.ts``getPreviewOrFallback`/`fetchPreviewPage`/`resolvePreviewPath`)、`/preview`与 `/preview/disable` 两个 Route Handler(基于 Next.js Draft Mode + httpOnly cookie),并在首页/博客/产品/案例/解决方案详情页接入草稿优先逻辑,`layout.tsx` 新增预览模式提示条(含退出链接);`lint`/`build` 均验证通过,后端 31 个现有用例均通过(本次未新增自动化测试,仅依靠人工验证)
- [x] 剩余 6 个 StreamField Block`ProductCardBlock`/`PricingBlock`/`TimelineBlock`/`TeamBlock`/`TechStackBlock`/`VideoBlock`)已全部开发完成:`apps/core/blocks.py` 新增对应 Block 类并扩充 `COMMON_BLOCKS`(现共 14 个内容 Block + 1 个 `richtext`),因 Wagtail StreamField 会将 Block 结构写入迁移(`use_json_field=True` 不代表可跳过迁移),已为 blog/cases/home/products/solutions 五个 Page 模型生成并应用 `alter field body` 迁移;`TeamBlock` 复用 `FormBlock` 的 `get_api_representation` 覆写模式,新增 `TeamMemberChooserBlock(SnippetChooserBlock)` 展开 `TeamMember` 完整字段(含 `serialize_image` 复用),并补充对应的空值/展开单元测试(新增 2 个用例,后端共 33 个测试全部通过);前端 `types/wagtail.ts` 新增 6 个 Block 值类型与 `StreamFieldBlock` 联合类型分支,新建 6 个渲染组件(`ProductCardGrid`/`Pricing`/`Timeline`/`Team`/`TechStack`/`Video`)并接入 `BlockRenderer.tsx` 映射表,`lint`/`build` 均验证通过
- [x] RBAC 落地:`python manage.py setup_rbac_groups` 幂等命令创建 5 个 Wagtail Group(站点管理员/编辑/审核员/作者/查看者)并按 §2.8 矩阵配置 `GroupPagePermission`/`GroupCollectionPermission`/Snippet 与用户管理 `Permission`Author“仅自己”利用 Wagtail 内置 `OwnershipPermissionPolicy`(仅 add 权限)原生实现,无需自定义 hook;新增 7 个后端单元测试,全仓库 pytest 从 33 增至 40,全部通过
未完成(待规划排期):
- [ ] 测试覆盖率仍不完整(已有基础单元测试,但集成测试、前端组件测试、E2E 均未编写,当前无 CI 流水线)
- [x] 生产安全 settings(§2.11 中 `SECURE_*`/CORS/CSRF 白名单等)已在 `production.py` 中逐项落实,并补充 `SECURE_PROXY_SSL_HEADER`(适配国内云厂商 SLB/CLB 边缘终止 TLS 场景);实际域名需在部署时通过 `.env` 填入
- [x] ICP 备案、页脚备案号展示、隐私政策内容、Cookie 同意条控件、表单 PIPL 数据删除权支持、字体/地图/验证码/CDN 国内可访问排查均已完成,§2.15 合规与本地化清单已全部完成
- [ ] 中文分词、OpenSearch 集成(Phase 2 提前项)未开始
### 2.17 风险与备选方案
| 风险 | 应对 |
|---|---|
| 过早引入多租户/微服务导致过度设计 | Phase 1/2 保持 Monolith,指标驱动再拆分 |
| 国内备案周期拖慢上线(通常 7-20 个工作日) | 项目启动即刻同步提交备案,不等开发完成 |
| Elasticsearch/OpenSearch 中文分词效果不佳 | 提前验证 IK 分词插件,或评估阿里云开放搜索托管方案 |
| StreamField Block 前后端字段不一致 | 建立 Block Schema 自动化一致性校验(CI |