# 文档生产及输出管理 — 规划设计及实施方案

## 一、概述

### 1.1 目标

基于 [ICT 服务平台](ICT服务平台概要.md) 的统一认证底座（OpenLDAP + Keycloak）和 Git 基础设施（Gitea），构建**从 Markdown 编写到浏览器阅读、反馈、统计分析**的全链路文档管理平台，覆盖文档生产、发布、阅读、协作、运营的完整生命周期。

### 1.2 核心设计原则

- **Git 为唯一真实源**：所有文档以 Markdown 格式存储于 Git 仓库，浏览器端为只读渲染视图
- **开源优先**：核心组件全部采用成熟开源方案，降低成本与锁定风险
- **认证复用**：登录用户接入 ICT 平台 Keycloak SSO 免密码秘钥登录；游客与访客免登录，通过浏览器指纹追踪
- **自动化驱动**：Git 推送即触发渲染、发布、同步，零人工干预

### 1.3 用户角色

| 角色 | 说明 | 典型场景 |
|------|------|---------|
| 作者 (Author) | 拥有 Git 仓库写权限的员工 | VS Code / Qoder 编写 Markdown 并推送 |
| 审阅者 (Reviewer) | 受邀阅读并点评文档的内部/外部人员 | 浏览器阅读、点评、反馈 |
| 读者 (Reader) | 通过 Keycloak 登录后访问文档的注册用户 | 登录后阅读 `login` / `restricted` 级别文档 |
| 游客 (Guest) | 免登录访问公开文档的匿名用户 | 通过 URL 自由阅读 `public` 级别文档，可匿名评论 |
| 访客 (Visitor) | 免登录仅限一次阅读的匿名用户 | 首次打开 `public_once` 级别文档可阅读，再次访问需登录 |
| 管理员 (Admin) | 管理发布策略、权限、生命周期的 IT 人员 | 配置发布规则与权限策略 |

---

## 二、系统架构

### 2.1 整体架构图

```plantuml
@startuml
skinparam componentStyle rectangle
title 文档生产及输出管理 — 系统架构

actor "作者" as Author
actor "游客/访客/读者" as Reader

node "VS Code + Qoder" as VSCode

node "互联网应用服务器 (腾讯云/阿里云)" as AppServer {
  component "Nginx + SSL" as Nginx
  component "Keycloak SSO" as KC

  package "文档生产" {
    component "Gitea" as Gitea
    component "DocForge" as Forge
  }

  package "文档阅读" {
    component "DocPortal" as Portal
    component "MeiliSearch" as Search
  }

  package "协作与运营" {
    component "Remark42" as Comment
    component "Umami" as Analytics
    component "MsgHub" as MsgHub
  }
}

node "私有云 [数据后端]" as PCloud {
  database "PostgreSQL" as DB
  database "OpenLDAP" as LDAP
  storage "Git 仓库存储" as Store
}

node "目标域名网站" as Sites {
  component "yink.ai" as SiteA
  component "docs.writech.cn" as SiteB
}

' == 作者流 ==
Author -down-> VSCode : Markdown 编写
VSCode -down-> Gitea : git push (SSH)
Gitea -down-> Store : 仓库数据

' == 渲染发布 ==
Gitea -right-> Forge : Webhook
Forge -right-> Portal : HTML + 摘要
Forge -down-> DB : 元数据入库
Forge ..> Sites : 自动部署

' == 读者流 ==
Reader -down-> Nginx : HTTPS
Nginx --> KC : 认证
Nginx --> Portal : 文档路由

' == 阅读交互 ==
Portal --> Comment : 评论
Portal --> Analytics : 访问记录
Portal --> Search : 搜索

' == 内部数据流 ==
Comment -right-> MsgHub : 评论通知
MsgHub -down-> DB : 消息存储
KC -down-> LDAP : 用户认证
@enduml
```

### 2.2 核心组件清单

| 组件 | 开源方案 | 用途 | 许可证 |
|------|---------|------|--------|
| Git 仓库 | **Gitea** | 文档源码存储与版本管理 | MIT |
| Markdown 渲染 | **Pandoc** + **markdown-it** | Markdown → HTML 转换，元数据提取 | GPL / MIT |
| 阅读平台 | **自研 DocPortal**（基于 Next.js） | 文档浏览、权限控制、URL 管理 | — |
| 评论系统 | **Remark42** | 自托管评论，支持匿名/认证评论 | MIT |
| 统计分析 | **Umami** | 自托管网站分析，页面访问与阅读时长 | MIT |
| 全文检索 | **MeiliSearch** | 文档标题、摘要、正文全文搜索 | MIT |
| PDF 导出 | **Pandoc** + **WeasyPrint** | Markdown → PDF 高质量排版 | GPL |
| 消息推送 | **自研 MsgHub**（基于 Socket.io） | 实时消息通知与任务看板集成 | — |
| 认证中心 | **Keycloak**（已有） | OIDC SSO 统一登录 | Apache 2.0 |
| 反向代理 | **Nginx**（已有） | SSL 终止、路由分发 | BSD |
| 静态站点生成 | **MkDocs** / **Hugo** | Git → 静态网站自动构建 | BSD / Apache |
| 浏览器指纹 | **FingerprintJS** (开源版) | 访客匿名标识，`public_once` 一次性阅读控制 | MIT |

---

## 三、文档生产流程

### 3.1 编写工具：VS Code + Qoder

- 作者使用 **VS Code** 编辑器 + **Qoder** AI 辅助工具进行 Markdown 文档编写
- 支持实时预览、AI 辅助补全、PlantUML 图表嵌入
- 所有文档遵循统一的 Markdown 规范（标题层级、Front Matter 元数据等）

### 3.2 Front Matter 元数据规范

每篇文档头部须包含 YAML Front Matter，声明文档元信息：

```yaml
---
title: "产品技术方案"
summary: "描述智能笔 BLE 通信协议的整体架构与关键技术细节"
author: "zhangsan@writech.cn"
tags: ["技术方案", "智能笔", "BLE"]
publish:
  access: login          # 可选：public / public_once / login / restricted
  allowed_users: []      # access=restricted 时生效，填写用户邮箱
  permissions:           # 各权限开关
    read: true
    comment: true
    save_as: false
    export_pdf: false
  lifecycle:
    expires_at: "2026-06-30"   # 可选：到期日期
    max_views: null             # 可选：最大阅读次数
---
```

### 3.3 Git 推送触发流程

```
作者在 VS Code / Qoder 中编写 Markdown
  │
  ├─ git add → git commit → git push (SSH 秘钥认证)
  │
  └─ Gitea 接收推送
       │
       ├─ Webhook → DocForge 渲染引擎
       │     ├─ 解析 Front Matter 元数据
       │     ├─ Markdown → HTML 渲染（Pandoc + markdown-it）
       │     ├─ 提取摘要（首段 / summary 字段）
       │     ├─ 生成文档 URL（基于仓库路径映射）
       │     ├─ 写入文档注册表（PostgreSQL）
       │     ├─ 推送到 MeiliSearch 索引
       │     └─ 若匹配域名映射规则 → 同步到目标网站
       │
       └─ 文档在 DocPortal 即时可访问
```

---

## 四、文档渲染与发布引擎（DocForge）

### 4.1 渲染流程

DocForge 是 Git Webhook 驱动的文档处理服务，负责将 Markdown 转换为可发布的 HTML 页面：

1. **接收 Webhook**：Gitea 推送事件触发，包含变更文件列表
2. **增量渲染**：仅处理本次变更的 Markdown 文件，非全量重建
3. **元数据解析**：提取 Front Matter 中的标题、摘要、权限、生命周期配置
4. **HTML 渲染**：通过 Pandoc 将 Markdown 转为带目录的 HTML，嵌入统一样式模板
5. **摘要生成**：优先使用 `summary` 字段，未填则自动取正文前 200 字
6. **URL 生成规则**：`https://docs.writech.cn/{仓库名}/{文档路径}`
7. **注册入库**：文档元数据（URL、标题、摘要、权限、生命周期）写入 PostgreSQL
8. **搜索索引**：文档标题 + 摘要 + 正文推送至 MeiliSearch

### 4.2 渲染引擎技术栈

```
DocForge (Node.js 服务)
  ├─ Express.js          — HTTP 接收 Webhook
  ├─ Pandoc (CLI 调用)   — Markdown → HTML 核心渲染
  ├─ markdown-it         — 辅助渲染（代码高亮、PlantUML 等）
  ├─ gray-matter          — Front Matter 解析
  ├─ pg (node-postgres)   — 元数据写入 PostgreSQL
  └─ meilisearch-js       — 搜索索引更新
```

---

## 五、文档阅读平台（DocPortal）

### 5.1 功能概览

DocPortal 是面向读者的 Web 应用，提供文档浏览、搜索、评论、导出等功能：

| 功能 | 说明 |
|------|------|
| 文档列表 | 按仓库/标签/时间浏览文档，显示标题 + 摘要 + URL |
| 全文搜索 | 基于 MeiliSearch 的标题、摘要、正文即时搜索 |
| 文档阅读 | 渲染后的 HTML 页面，带目录导航 |
| 评论反馈 | 嵌入 Remark42 评论组件，支持分段评论 |
| PDF 导出 | 有权限用户可导出 PDF（Pandoc + WeasyPrint） |
| 另存为 | 有权限用户可下载 Markdown 原文或 HTML |
| 源文件定位 | 有编辑权限的用户可见「编辑源文件」图标，一键复制 Git 仓库中对应 Markdown 文件的完整路径 |

### 5.2 URL 与摘要体系

每篇文档发布后具备：

| 元素 | 格式 | 示例 |
|------|------|------|
| 文档 URL | `https://docs.writech.cn/{repo}/{path}` | `https://docs.writech.cn/docs/技术方案/BLE通信协议` |
| 短链接 | `https://docs.writech.cn/d/{短码}` | `https://docs.writech.cn/d/a3Kx9` |
| 摘要 | Front Matter `summary` 或自动提取前 200 字 | 显示在文档列表、搜索结果、分享卡片中 |

短链接便于在邮件、即时通讯中分享，访问短链接自动重定向到完整 URL。

### 5.3 源文件快速定位（编辑图标）

对于拥有 Git 仓库写权限的用户，文档页面右上角显示 **「✏️ 编辑源文件」** 图标：

- **点击**：复制 Git 仓库中对应 Markdown 文件的完整路径到剪贴板，如 `docs/技术方案/BLE通信协议.md`
- **Tooltip**：显示仓库名 + 文件路径 + 最近修改者 + 修改时间
- 作者可直接在 VS Code / Qoder 中通过路径定位文件并修改

---

## 六、权限管理体系

### 6.1 文档访问级别

文档发布时，作者在 Front Matter 的 `publish.access` 字段选择访问级别：

| 级别 | 标识 | 说明 |
|------|------|------|
| **公开** | `public` | 游客可通过 URL 自由访问，无需登录，不限次数 |
| **公开一次** | `public_once` | 访客免登录可阅读一次，再次访问同一文档需登录；通过浏览器指纹 + Cookie 判定 |
| **需登录** | `login` | 需通过 Keycloak 登录后方可访问（读者 / 审阅者） |
| **限定人员** | `restricted` | 仅 `allowed_users` 列表中的用户可访问 |

### 6.2 细粒度权限矩阵

在 `publish.permissions` 中按功能逐项配置：

| 权限 | 说明 | 默认值 |
|------|------|--------|
| `read` | 阅读文档正文 | true |
| `comment` | 发表评论和反馈 | true（login/restricted）；public 游客可匿名评论（需验证码）；public_once 访客不可评论 |
| `save_as` | 下载 Markdown 原文或 HTML | false（游客 / 访客始终不可用） |
| `export_pdf` | 导出为 PDF 文件 | false（游客 / 访客始终不可用） |

### 6.3 权限校验流程

```plantuml
@startuml
skinparam componentStyle rectangle
title 文档访问权限校验流程

start
:用户访问文档 URL;

if (文档 access = public ?) then (是)
  :允许阅读;
  note right: 游客 - 免登录, 不限次数
else (否)
  if (文档 access = public_once ?) then (是)
    if (用户已登录 ?) then (是)
      :允许阅读;
      note right: 登录用户不受一次限制
    else (否)
      :生成浏览器指纹;
      if (已有该文档访问记录 ?) then (是)
        :提示 "请登录后继续访问";
        :重定向到 Keycloak 登录;
        stop
      else (否)
        :允许阅读;
        :写入指纹记录 + Cookie;
        note right: 访客 - 免登录, 仅此一次
      endif
    endif
  else (否)
    if (用户已登录 Keycloak ?) then (否)
      :重定向到 Keycloak 登录;
      stop
    else (是)
      if (文档 access = login ?) then (是)
        :允许阅读;
      else (restricted)
        if (用户在 allowed_users 列表中 ?) then (是)
          :允许阅读;
        else (否)
          :返回 403 无权访问;
          stop
        endif
      endif
    endif
  endif
endif

:加载文档页面;
:根据角色与 permissions 决定功能可用性;
note right
  游客: 可匿名评论(需验证码), 不可导出
  访客: 不可评论, 不可导出
  登录用户: 按 permissions 配置
  编辑图标: 仅 Git 写权限用户
end note
stop
@enduml
```

### 6.4 权限与认证底座的关系

| 层次 | 组件 | 职责 |
|------|------|------|
| 身份源 | OpenLDAP（私有云） | 用户账户、组织架构、角色 |
| 认证中心 | Keycloak（互联网服务器） | SSO 登录，签发 OIDC Token |
| 匿名访问控制 | DocPortal + FingerprintJS | 游客自由访问 `public` 文档；访客通过浏览器指纹 + Cookie 判定是否首次访问 `public_once` 文档 |
| 应用层权限 | DocPortal | 解析登录用户 Token 或匿名身份，匹配 Front Matter 权限配置 |
| Git 写权限 | Gitea | 判断用户是否有仓库写权限，控制「编辑图标」显示 |

### 6.5 访客一次性阅读的技术实现

`public_once` 级别文档的访客身份追踪方案：

| 技术手段 | 说明 |
|---------|------|
| FingerprintJS | 客户端浏览器指纹库，生成稳定的匿名标识符（无需 Cookie 即可识别回访） |
| HttpOnly Cookie | 辅助手段，首次阅读时种下 Cookie，防止清除指纹后绕过限制 |
| 服务端记录 | PostgreSQL 存储 `(fingerprint_hash, doc_id, visited_at)`，校验是否已阅读 |

校验流程：用户访问 `public_once` 文档时，DocPortal 前端通过 FingerprintJS 生成浏览器指纹，携带指纹向后端查询是否已有该文档的访问记录；无记录则放行并写入，有记录则提示登录。

> **注意**：浏览器指纹并非 100% 可靠（更换浏览器或隐身模式可重置），`public_once` 的定位是**轻量防护**，适用于营销试读、内容预览等场景。高价值内容建议使用 `login` 或 `restricted` 级别。

---

## 七、阅读统计分析

### 7.1 采集指标

基于 **Umami**（自托管 Web 分析）采集以下数据：

| 指标 | 采集方式 | 说明 |
|------|---------|------|
| 页面访问量（PV） | Umami JS 埋点 | 每次打开文档 URL 计一次 |
| 独立访客数（UV） | Umami 匿名指纹 | 去重统计独立访问用户（含游客 / 访客匿名指纹） |
| 阅读时长 | Umami 页面停留时间 + 自定义心跳事件 | 每 30 秒上报一次心跳，计算有效阅读时长 |
| 阅读进度 | 自定义事件（滚动百分比） | 记录用户阅读到文档的哪个位置 |
| 来源渠道 | Umami Referrer | 记录用户从哪个链接进入 |

### 7.2 阅读时长精确统计方案

标准的页面停留时间统计不够准确（用户可能切出标签页），采用**心跳 + 可见性检测**方案：

```javascript
// 嵌入文档页面的阅读时长统计脚本（伪代码）
let readingSeconds = 0;
let heartbeatTimer = null;

document.addEventListener('visibilitychange', () => {
  if (document.hidden) {
    clearInterval(heartbeatTimer);         // 页面不可见，暂停计时
  } else {
    heartbeatTimer = setInterval(() => {
      readingSeconds += 30;
      umami.track('reading-heartbeat', {   // 每30秒上报
        doc_id: DOC_ID,
        seconds: readingSeconds
      });
    }, 30000);
  }
});
```

### 7.3 统计看板

管理员和作者可在 DocPortal 中查看文档统计看板：

- **文档维度**：每篇文档的 PV / UV / 平均阅读时长 / 完读率
- **时间维度**：按日 / 周 / 月查看趋势
- **用户维度**：（需登录文档）哪些用户阅读了、阅读时长排名
- **导出**：统计数据可导出 CSV

---

## 八、文档生命周期管理

### 8.1 生命周期配置

在 Front Matter 的 `publish.lifecycle` 中配置：

| 参数 | 类型 | 说明 |
|------|------|------|
| `expires_at` | 日期字符串 | 到期后文档自动下架，页面显示「文档已过期」 |
| `max_views` | 整数 | 累计阅读次数达到上限后自动下架 |
| 无配置 | — | 永久有效，不自动下架 |

> **区分 `max_views` 与 `public_once`**：`max_views` 是文档级总阅读次数上限（达到后所有用户均不可访问），`public_once` 是访客级单次阅读限制（每位访客仅限免登录阅读一次，登录用户不受影响）。两者可同时配置、互不冲突。

### 8.2 生命周期状态流转

```
已发布 (Active)
  │
  ├─ 达到 expires_at → 已过期 (Expired)
  ├─ 达到 max_views  → 已达上限 (Limit Reached)
  ├─ 作者手动下架     → 已下架 (Archived)
  └─ Git 中删除文件   → 已删除 (Deleted)

已过期 / 已达上限 / 已下架
  └─ 作者更新 Front Matter 重新配置 → 已发布 (Active)
```

### 8.3 下架处理

- 下架后 URL 仍保留，但页面显示提示信息（如「此文档已过期，请联系作者」）
- 管理员可配置是否允许已登录用户查看过期文档的历史快照
- 文档的统计数据和评论不随下架删除，可供作者回溯

---

## 九、评论反馈系统

### 9.1 技术方案：Remark42

采用 **Remark42** 自托管评论系统：

- **部署位置**：互联网应用服务器（Docker 容器）
- **认证对接**：通过 Keycloak OIDC 实现评论者身份认证
- **匿名评论**：`public` 级别文档支持游客匿名评论（需验证码防刷）；`public_once` 级别文档访客不开放评论
- **数据存储**：Remark42 自带 BoltDB 存储，备份至私有云

### 9.2 评论功能

| 功能 | 说明 |
|------|------|
| 文档级评论 | 在文档末尾发表整体评论 |
| 段落级评论 | 选中文档段落后发表针对性评论 |
| 回复与嵌套 | 支持多级回复讨论 |
| @提及 | 评论中 @某用户，触发专属通知 |
| Markdown 格式 | 评论内容支持 Markdown 语法 |
| 管理操作 | 作者可置顶、删除评论；管理员可全局管理 |

### 9.3 评论触发消息通知

当文档收到新评论或反馈时：

1. Remark42 通过 Webhook 通知 MsgHub 消息中心
2. MsgHub 根据评论内容识别通知目标：
   - 文档作者（始终通知）
   - 被 @提及的用户
   - 该文档评论帖的参与者（可配置是否通知）
3. 消息实时推送到目标用户的消息中心

---

## 十、消息中心与任务看板集成

### 10.1 设计理念

消息中心采用**类微信群列表**的交互模式：

- 每篇有新消息的文档如同一个「对话」
- 新消息驱动该文档条目**置顶排列**（最新消息在最前）
- 点击文档条目直接在浏览器中打开对应文档及其评论区
- 未读消息显示红点 + 未读数
- 消息中心同时作为作者的**任务看板**，待处理的评论/反馈一目了然

### 10.2 消息类型

| 消息类型 | 触发条件 | 消息内容 |
|---------|---------|---------|
| 📝 新评论 | 文档收到新评论 | "{用户}在《{文档标题}》中发表了评论" |
| 💬 回复 | 某人回复了你的评论 | "{用户}回复了你在《{文档标题}》中的评论" |
| @提及 | 评论中 @了你 | "{用户}在《{文档标题}》的评论中提到了你" |
| 📊 阅读里程碑 | 文档阅读量突破阈值 | "《{文档标题}》阅读量已突破 100 次" |
| ⚠️ 生命周期告警 | 文档即将过期或达到阅读上限 | "《{文档标题}》将于 3 天后过期" |

### 10.3 消息中心界面结构

```
┌─────────────────────────────────────────┐
│  📬 消息中心                     🔍 搜索  │
├─────────────────────────────────────────┤
│  🔴 BLE通信协议技术方案           10:32  │
│     张三：建议补充低功耗模式的说明       │
├─────────────────────────────────────────┤
│  🔴 产品需求文档 v2.1             09:15  │
│     李四 回复了你的评论                  │
├─────────────────────────────────────────┤
│     智能笔固件升级指南             昨天   │
│     阅读量已突破 100 次                  │
├─────────────────────────────────────────┤
│     Q3 项目计划                   周一   │
│     王五：时间节点需要再确认             │
└─────────────────────────────────────────┘
```

- 红点标识未读消息
- 列表按最新消息时间倒序排列
- 点击任一条目 → 浏览器打开该文档页面，自动滚动到对应评论
- 支持标记已读、批量已读、按类型筛选

### 10.4 技术实现

```
MsgHub 消息中心 (Node.js)
  ├─ Socket.io        — WebSocket 实时推送
  ├─ PostgreSQL       — 消息持久化存储
  ├─ Redis            — 未读计数缓存、在线状态
  ├─ Remark42 Webhook — 评论事件接入
  ├─ DocForge Webhook — 生命周期事件接入
  └─ Umami API        — 阅读量里程碑检测
```

---

## 十一、域名网站自动发布

### 11.1 映射机制

Git 仓库中的 Markdown 可配置自动发布到指定域名网站，映射关系存储于 PostgreSQL：

| Git 仓库 / 路径 | 目标域名 | 构建工具 | 说明 |
|-----------------|---------|---------|------|
| `web-doc/` | yink.ai | Hugo | 品牌官网内容 |
| `docs/对外文档/` | docs.writech.cn | MkDocs | 对外技术文档站 |
| `edu-doc/` | edu.writech.cn | MkDocs | 教育平台文档 |

### 11.2 自动发布流程

```plantuml
@startuml
skinparam componentStyle rectangle
title Git → 域名网站自动发布流程

start
:作者 git push 更新 Markdown;
:Gitea Webhook 触发 DocForge;

:DocForge 检查变更文件路径;
if (匹配域名映射规则 ?) then (是)
  :调用对应构建工具\n(Hugo / MkDocs);
  :生成静态 HTML 站点;
  :通过 rsync + SSH\n部署到目标服务器;
  :目标域名网站内容更新;
else (否)
  :仅更新 DocPortal\n内部阅读平台;
endif

stop
@enduml
```

### 11.3 映射配置文件

映射关系通过 Git 仓库中的配置文件 `.docforge.yml` 管理：

```yaml
# .docforge.yml — 仓库根目录
site_mappings:
  - source: "对外文档/"
    target:
      domain: "docs.writech.cn"
      builder: "mkdocs"
      deploy:
        method: "rsync"
        host: "106.55.191.177"
        path: "/var/www/docs.writech.cn/"
  - source: "/"
    target:
      domain: "docs.writech.cn"
      builder: "mkdocs"
      base_path: "/internal/"
      deploy:
        method: "rsync"
        host: "106.55.191.177"
        path: "/var/www/docs.writech.cn/internal/"
```

映射关系一旦设定，后续 Markdown 更新即自动推送到对应域名，无需手动操作。

---

## 十二、互联网推广与海报链接体系

### 12.1 设计目标

将文档系统产出的内容转化为**互联网营销落地页**（海报链接），通过主流流量平台分发，获取 2B / 2C 新客户。核心链路：

```
文档系统 (Markdown)
  │
  ├─ DocForge 渲染为静态 HTML 落地页
  │
  ├─ 内嵌 JS 与服务器 API 交互（埋点 / 表单 / 动态内容）
  │
  ├─ CDN 全网加速分发
  │
  ├─ 生成海报链接（短链接 + 渠道追踪参数）
  │
  └─ 投放到微信 / 淘宝 / 美团 / 抖音 / 短信
       │
       └─ 客户点击/扫码 → 浏览器打开落地页 → 转化
```

### 12.2 海报链接架构

#### 落地页结构

海报链接指向的是一张**静态 HTML 落地页**，由 DocForge 从 Markdown 源文档自动生成，包含：

| 层次 | 内容 | 说明 |
|------|------|------|
| 静态 HTML | 产品介绍、图文排版、CTA 按钮 | CDN 分发，毫秒级加载 |
| 内嵌 JS | 埋点脚本 + 交互逻辑 | 页面加载后调用服务器 API |
| 服务器 API | 渠道统计、表单提交、动态内容 | 运行在互联网应用服务器 |

#### 落地页内嵌 JS 交互能力

静态落地页加载后，内嵌的 JS 脚本自动调用服务器接口完成以下交互：

```javascript
// 落地页内嵌 JS 核心逻辑（伪代码）
document.addEventListener('DOMContentLoaded', () => {

  // 1. 渠道来源埋点 — 记录用户从哪个平台进入
  const params = new URLSearchParams(location.search);
  fetch('/api/promo/track', {
    method: 'POST',
    body: JSON.stringify({
      page_id: PAGE_ID,
      channel: params.get('ch'),      // wx / tb / mt / dy / sms
      campaign: params.get('cmp'),    // 活动标识
      referer: document.referrer
    })
  });

  // 2. 动态内容加载 — 价格、库存、活动信息
  fetch('/api/promo/dynamic/' + PAGE_ID)
    .then(r => r.json())
    .then(data => {
      document.getElementById('price').textContent = data.price;
      document.getElementById('stock').textContent = data.stock;
    });

  // 3. 表单提交 — 客户留资 / 咨询 / 预约
  document.getElementById('lead-form').addEventListener('submit', (e) => {
    e.preventDefault();
    fetch('/api/promo/lead', {
      method: 'POST',
      body: new FormData(e.target)
    });
  });
});
```

### 12.3 CDN 全网加速分发

落地页为纯静态 HTML + CSS + JS + 图片，通过 CDN 分发到全网边缘节点，确保各渠道用户点击后毫秒级加载：

| 项目 | 方案 | 说明 |
|------|------|------|
| CDN 服务 | 阿里云 CDN | 覆盖国内及海外节点，配合已有域名架构 |
| 源站 | 互联网应用服务器 | DocForge 生成的静态文件存储于源站 |
| 缓存策略 | HTML 5分钟 / 图片字体 30 天 | HTML 短缓存确保内容更新及时生效 |
| HTTPS | ZeroSSL 通配符证书 | 复用 ICT 平台证书体系 |
| 缓存刷新 | DocForge 发布后自动调用 CDN Purge API | Markdown 更新后落地页即时刷新 |

发布流程：

```
DocForge 渲染落地页 HTML
  │
  ├─ 写入源站目录 /var/www/promo/
  │
  ├─ 调用阿里云 CDN Purge API 刷新缓存
  │
  └─ 全网边缘节点同步更新（秒级）
```

### 12.4 五大渠道投放方案

每个渠道生成专属海报链接，带渠道追踪参数，确保来源可精确归因：

| 渠道 | 渠道码 | 链接形式 | 投放载体 | 打开方式 |
|------|---------|---------|---------|----------|
| **微信** | `ch=wx` | 短链接 + 二维码 | 朋友圈图文、公众号文章、群聊卡片 | 微信内置浏览器打开 |
| **淘宝** | `ch=tb` | 淘口令 + 短链接 | 商品详情页、店铺首页、客服消息 | 手淘内置浏览器打开 |
| **美团** | `ch=mt` | 短链接 | 店铺介绍、团购详情、评价回复 | 美团 App 内置浏览器打开 |
| **抖音** | `ch=dy` | 短链接 | 视频评论区、主页链接、直播间小黄车 | 抖音内置浏览器打开 |
| **短信** | `ch=sms` | 短链接 | 营销短信文案 | 系统默认浏览器打开 |

链接格式示例：

```
完整 URL：https://go.writech.cn/p/smart-pen-intro?ch=wx&cmp=2026q1
短链接： https://go.writech.cn/s/Xk9mP
淘口令： ￥smart-pen-introXk9mP￥
二维码： 将短链接编码为 QR Code，嵌入海报图片
```

### 12.5 渠道适配与浏览器兼容

各平台客户端点击链接后均通过其**内置浏览器**打开落地页，需针对性适配：

| 内置浏览器 | 特征 | 适配策略 |
|-------------|------|----------|
| 微信 WebView | 不支持部分 Web API，有 JS-SDK | 检测 UA 含 "MicroMessenger"，加载微信 JS-SDK，启用分享卡片自定义 |
| 淘宝 WebView | 限制外跳，访问受限 | 落地页自包含，避免外跳；表单交互在页内完成 |
| 美团 WebView | 与标准浏览器接近 | 标准响应式适配即可 |
| 抖音 WebView | 限制外跳，支持基础 Web API | 落地页自包含；复杂交互引导“用浏览器打开” |
| 系统浏览器 (短信) | 完整 Web 能力 | 无需特殊适配 |

落地页 JS 自动检测运行环境并适配：

```javascript
// 环境检测与适配（伪代码）
const ua = navigator.userAgent;
if (/MicroMessenger/i.test(ua)) {
  loadWxJSSDK();    // 加载微信 JS-SDK，配置分享卡片
} else if (/AliApp.*TB/i.test(ua)) {
  disableExternalLinks();  // 淘宝内禁止外跳
} else if (/aweme/i.test(ua)) {
  showOpenInBrowser();     // 抖音内显示“用浏览器打开”引导
}
```

### 12.6 推广流量追踪与转化分析

#### 追踪参数体系

每个海报链接携带统一追踪参数，全部流入 Umami 统计平台：

| 参数 | 说明 | 示例 |
|------|------|------|
| `ch` | 渠道来源 | wx / tb / mt / dy / sms |
| `cmp` | 活动标识 | 2026q1 / summer-sale |
| `ct` | 内容类型 | product / article / event |
| `kw` | 关键词（付费投放时） | smart-pen / ink-screen |

#### 转化漏斗

从点击到转化的全链路数据均录入 PostgreSQL，供营销团队分析：

```
点击链接 (Impression)
  │
  ├─ 打开落地页 (PageView)         ← Umami 埋点
  │
  ├─ 页面停留 > 10s (Engaged)       ← 心跳上报
  │
  ├─ 提交表单 / 点击 CTA (Lead)      ← 表单提交 API
  │
  └─ 完成购买 / 注册 (Conversion)  ← 业务系统回调
```

#### 推广效果看板

在 DocPortal 管理后台提供推广效果看板：

| 维度 | 指标 |
|------|------|
| 渠道维度 | 各渠道 PV / UV / 平均停留 / 留资数 / 转化率 |
| 内容维度 | 各落地页访问量 / 转化率 / 热度图 |
| 活动维度 | 各活动投入产出比 (ROI) |
| 时间维度 | 按日 / 周 / 月的流量与转化趋势 |

### 12.7 推广落地页生成与管理流程

```plantuml
@startuml
skinparam componentStyle rectangle
title 推广落地页生成与分发流程

start
:营销人员在 Markdown 中编写推广内容;
note right: 使用 promo 模板，配置表单/CTA

:设置 Front Matter 推广配置;
note right
  publish.type: promo
  publish.channels: [wx, tb, dy]
  publish.cdn: true
end note

:git push 推送到 Gitea;
:DocForge 渲染生成静态落地页;
note right
  内嵌埋点 JS + 表单交互 JS
  生成各渠道专属短链接
  微信渠道额外生成 QR Code
end note

:部署到源站 + CDN 刷新;

:营销人员获取各渠道链接;
note right
  微信: 短链接 + QR Code
  淘宝: 淘口令 + 短链接
  美团: 短链接
  抖音: 短链接
  短信: 短链接
end note

:投放到各平台;

:客户点击/扫码;
:平台内置浏览器打开落地页;
note right
  CDN 加速加载静态 HTML
  JS 调用服务器 API 记录渠道+加载动态内容
end note

:客户浏览/交互/留资;
:Umami + API 记录全链路数据;
stop
@enduml
```

### 12.8 推广文档 Front Matter 扩展

推广类文档在标准 Front Matter 基础上增加推广专属配置：

```yaml
---
title: "智能笔新品发布"
summary: "自然写 Qink 智能笔，笔迹实时同步至大屏"
author: "marketing@writech.cn"
tags: ["推广", "智能笔", "Qink"]
publish:
  type: promo                           # 推广类型文档
  access: public                        # 推广页通常 public；高价值预览内容可用 public_once
  cdn: true                             # 启用 CDN 分发
  channels: [wx, tb, mt, dy, sms]       # 投放渠道
  landing_page:
    template: product                   # 落地页模板：product / article / event
    cta_text: "立即了解"                # CTA 按钮文案
    cta_url: "/contact"                 # CTA 跳转目标
    form: true                          # 是否包含留资表单
  lifecycle:
    expires_at: "2026-06-30"
---
```

---

## 十三、部署方案

### 13.1 服务部署清单

全部服务运行于**互联网应用服务器**（腾讯云 / 阿里云），数据后端在私有云：

| 服务 | 部署方式 | 运行位置 | 端口 |
|------|---------|---------|------|
| DocForge 渲染引擎 | Docker | 互联网应用服务器 | 3001 |
| DocPortal 阅读平台 | Docker (Next.js) | 互联网应用服务器 | 3000 |
| Remark42 评论系统 | Docker | 互联网应用服务器 | 8080 |
| Umami 统计分析 | Docker | 互联网应用服务器 | 3002 |
| MeiliSearch 搜索 | Docker | 互联网应用服务器 | 7700 |
| MsgHub 消息中心 | Docker (Node.js) | 互联网应用服务器 | 3003 |
| Redis | Docker | 互联网应用服务器 | 6379 |
| PostgreSQL | 原生安装 | 私有云 | 5432 |
| Gitea | Docker（已有） | 互联网应用服务器 | 22/443 |
| Keycloak | Docker（已有） | 互联网应用服务器 | 8443 |
| PromoAPI 推广接口 | Docker (Node.js) | 互联网应用服务器 | 3004 |
| 阿里云 CDN | SaaS | 全网边缘节点 | — |

### 13.2 Nginx 路由规则

```nginx
# DocPortal 阅读平台
location /docs/ {
    proxy_pass http://127.0.0.1:3000;
}

# Remark42 评论 API
location /api/comment/ {
    proxy_pass http://127.0.0.1:8080;
}

# Umami 统计
location /api/analytics/ {
    proxy_pass http://127.0.0.1:3002;
}

# MeiliSearch 搜索
location /api/search/ {
    proxy_pass http://127.0.0.1:7700;
}

# MsgHub WebSocket
location /ws/msg/ {
    proxy_pass http://127.0.0.1:3003;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
}

# DocForge Webhook（仅允许 Gitea 内部调用）
location /hook/docforge {
    allow 127.0.0.1;
    deny all;
    proxy_pass http://127.0.0.1:3001;
}

# PromoAPI 推广接口
location /api/promo/ {
    proxy_pass http://127.0.0.1:3004;
}

# 推广落地页静态文件（CDN 回源）
location /p/ {
    alias /var/www/promo/;
    expires 5m;
}
```

---

## 十四、实施路线图

| 阶段 | 任务 | 预计周期 |
|------|------|--------|
| **第一阶段：基础** | DocForge 渲染引擎开发，Gitea Webhook 对接 | 第 1-2 周 |
| **第一阶段** | DocPortal 阅读平台 MVP（文档列表 + 阅读 + URL + 摘要） | 第 2-3 周 |
| **第一阶段** | MeiliSearch 全文检索集成 | 第 3 周 |
| **第一阶段** | Front Matter 权限解析 + Keycloak OIDC 认证对接 | 第 3-4 周 |
| **第二阶段：协作** | Remark42 评论系统部署与 Keycloak 对接 | 第 5 周 |
| **第二阶段** | MsgHub 消息中心开发（WebSocket + 消息列表） | 第 5-6 周 |
| **第二阶段** | 消息中心与任务看板集成（类微信列表交互） | 第 6-7 周 |
| **第二阶段** | 评论通知 → 消息中心 → 打开文档全链路打通 | 第 7 周 |
| **第三阶段：运营** | Umami 统计分析部署，阅读时长精确统计方案 | 第 8 周 |
| **第三阶段** | 文档生命周期管理（过期、阅读上限、自动下架） | 第 8-9 周 |
| **第三阶段** | PDF 导出功能（Pandoc + WeasyPrint） | 第 9 周 |
| **第四阶段：发布** | 域名网站自动发布（.docforge.yml 映射 + Hugo/MkDocs 构建） | 第 10-11 周 |
| **第四阶段** | 统计看板（作者维度 + 文档维度 + 趋势图） | 第 11-12 周 |
| **第四阶段** | 全链路测试与上线 | 第 12 周 |
| **第五阶段：推广** | 推广落地页模板开发 + CDN 接入 | 第 13 周 |
| **第五阶段** | 五渠道海报链接生成 + 渠道埋点 + 浏览器适配 | 第 13-14 周 |
| **第五阶段** | 推广效果看板（渠道归因 + 转化漏斗） | 第 14 周 |
