Files
jiahong 6333cf1091 更新
2026-03-25 22:52:11 +08:00

926 lines
29 KiB
Markdown
Raw Permalink 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.
# MkDocs 用户阅读管理系统 — 规划设计及实施方案
## 一、概述
### 1.1 目标
针对以 MkDocs 生成的静态文档站点(如 docs.writech.cn),构建一套**用户阅读管理系统**,实现用户登记与身份识别、文档分级权限控制、阅读次数与有效期管理、防爬虫保护和阅读行为统计分析。系统在保障文档安全可控的同时,为大部分公开文档提供**免登录流畅阅读**体验,仅在访问受控文档时才触发轻量认证。
### 1.2 与现有平台的关系
本系统基于 [ICT 服务平台](ICT服务平台概要.md) 已有基础设施构建,复用以下底座能力:
| 已有组件 | 本系统中的角色 |
|---------|-------------|
| KeycloakSSO | 登录用户的 OIDC 认证中心,对接微信开放平台 Social IdP |
| OpenLDAP(身份源) | 企业员工账户存储 |
| DocForge(渲染引擎) | MkDocs 构建触发与权限元数据解析 |
| Umami(统计分析) | 页面级阅读埋点与看板 |
| PostgreSQL(数据库) | 用户注册信息、权限策略、阅读记录持久化 |
| Nginx(反向代理) | 请求拦截、静态资源分发、SSL 终止 |
### 1.3 设计原则
1. **静态站点不改造**:MkDocs 仍按标准流程生成静态 HTML,用户系统以**反向代理网关层**注入,不侵入 MkDocs 构建产物
2. **最小摩擦登录**:公开文档免登录访问;受控文档首次需认证后自动记住,不逐页弹登录框
3. **双通道注册**:支持微信扫码和手机短信验证码两种轻量注册方式,适配 PC 和移动端
4. **颗粒度可粗可细**:权限可设置到单篇文档,也可按目录/标签/仓库整体授权
5. **全栈开源**:核心组件均采用开源方案,数据自托管
---
## 二、系统架构
### 2.1 整体架构图
```plantuml
@startuml
skinparam componentStyle rectangle
skinparam nodesep 10
skinparam ranksep 25
title MkDocs 用户阅读管理系统 - 整体架构
actor "PC 浏览器" as PCUser
actor "手机浏览器" as MobileUser
rectangle "互联网应用服务器" {
package "接入层" as P_Access {
[Nginx\n反向代理] as Nginx
[MkDocs Guard\n认证网关] as Guard
}
package "认证服务" as P_Auth {
[Keycloak SSO] as KC
[微信扫码服务] as WxAuth
[短信验证服务] as SMS
}
package "用户管理" as P_User {
[用户注册中心] as UC
[权限引擎] as Perm
[反爬虫模块] as AntiBot
}
package "文档服务" as P_Doc {
[MkDocs\n静态站点] as MkDocs
[DocForge\n渲染引擎] as Forge
}
package "数据与分析" as P_Data {
database "PostgreSQL" as DB
[Redis\n会话/限流] as Redis
[Umami 统计] as Umami
}
}
' === 分层布局控制 ===
P_Access -[hidden]down-> P_Auth
P_Access -[hidden]down-> P_User
P_Auth -[hidden]right-> P_User
P_Auth -[hidden]down-> P_Doc
P_User -[hidden]down-> P_Data
P_Doc -[hidden]right-> P_Data
' === 用户入口 ===
PCUser -down-> Nginx
MobileUser -down-> Nginx
Nginx -down-> Guard : 文档请求
' === Guard 请求分发 ===
Guard -down-> KC : OIDC 认证
Guard -down-> Perm : 查询权限
Guard -down-> AntiBot : 请求检测
Guard -down-> MkDocs : 放行静态页面
' === 注册中心 ===
UC --> WxAuth : 微信登录
UC --> SMS : 短信验证
' === 数据持久化 ===
UC -down-> DB : 用户数据
Perm -down-> DB : 权限策略
Perm -down-> Redis : 缓存
Forge -down-> DB : 文档元数据
Umami -down-> DB : 阅读埋点
@enduml
```
### 2.2 核心组件清单
| 组件 | 技术方案 | 用途 | 许可证 |
|------|---------|------|--------|
| 认证网关 | **MkDocs Guard**(自研,Go / Node.js | Nginx 子请求认证,拦截文档请求并校验权限 | — |
| 用户注册中心 | **UserCenter**(自研,Node.js + Express) | 微信扫码、短信验证码注册/登录,用户管理 | — |
| 权限引擎 | **PermEngine**(自研,Node.js) | 文档 - 用户权限匹配、次数/期限校验 | — |
| 反爬虫 | **AntiBot**(自研 + 开源中间件) | 频率限制、浏览器指纹、行为检测 | — |
| 微信 OAuth | **微信开放平台** + Keycloak Social IdP | 微信扫码登录/注册 | — |
| 短信网关 | **阿里云短信服务** / **腾讯云短信** | 验证码发送 | — |
| 会话缓存 | **Redis** | 登录会话、验证码、频率计数器 | BSD |
| 浏览器指纹 | **FingerprintJS**(开源版) | 匿名用户标识、反爬辅助 | MIT |
| 静态站点 | **MkDocs** + **Material 主题** | Markdown 文档构建为静态 HTML | BSD |
| 统计分析 | **Umami**(已有) | 页面访问量、阅读时长、用户来源 | MIT |
| 数据库 | **PostgreSQL**(已有) | 用户、权限、阅读记录持久化 | PostgreSQL |
### 2.3 请求处理流程
```plantuml
@startuml
skinparam componentStyle rectangle
title 文档请求处理流程
start
:用户请求 MkDocs 文档页面;
:Nginx 转发至 MkDocs Guard;
:AntiBot 检测;
if (疑似爬虫 ?) then (是)
:返回 429 / 验证码挑战;
stop
else (否)
endif
:Guard 查询文档权限级别;
if (文档为 open 级别 ?) then (是)
:直接放行, 返回静态页面;
:Umami 记录匿名访问;
stop
else (否)
endif
if (用户已持有有效会话 ?) then (是)
:从 Redis 读取用户身份;
else (否)
:展示登录浮层;
note right
PC - 微信扫码 / 短信验证码
手机 - 微信授权 / 短信验证码
end note
:完成认证, 建立会话;
endif
:PermEngine 校验用户权限;
if (用户有权访问 ?) then (是)
if (阅读次数/有效期已超限 ?) then (是)
:提示 "使用权已到期, 请联系管理员";
stop
else (否)
:放行, 返回静态页面;
:记录阅读日志;
:Umami 记录实名访问;
stop
endif
else (否)
:返回 403 无权限页面;
stop
endif
@enduml
```
---
## 三、用户注册与登录
### 3.1 注册方式
系统支持两种轻量注册方式,用户无需设置密码:
#### 3.1.1 微信扫码注册/登录
```plantuml
@startuml
skinparam componentStyle rectangle
title 微信扫码注册/登录流程
start
:用户在 PC 浏览器访问受控文档;
:Guard 弹出登录浮层;
:浮层展示微信登录二维码;
note right: 调用微信开放平台生成临时二维码
:用户使用微信扫码;
:微信弹出授权确认;
:用户点击确认授权;
:微信回调 Keycloak 携带 code;
:Keycloak 换取微信 OpenID + 用户信息;
if (OpenID 已绑定系统用户 ?) then (是)
:直接登录, 建立会话;
else (否)
:自动创建用户;
note right
昵称 - 微信昵称
头像 - 微信头像
标识 - 微信 OpenID
角色 - 默认读者
end note
:登录并建立会话;
endif
:回到文档页面, 自动刷新;
stop
@enduml
```
**技术实现要点**
- Keycloak 配置微信开放平台作为 Social Identity Provider
- 使用微信开放平台「网站应用」的扫码登录能力(非公众号)
- PC 端展示二维码供手机扫描;手机端直接调起微信授权页面
- 微信 OpenID 与系统用户 1:1 绑定,存储于 PostgreSQL
#### 3.1.2 手机短信验证码注册/登录
```plantuml
@startuml
skinparam componentStyle rectangle
title 短信验证码注册/登录流程
start
:用户在浮层选择 "手机号登录";
:输入手机号;
:UserCenter 检查发送频率;
if (60 秒内已发送 ?) then (是)
:提示 "请稍后重试";
stop
else (否)
endif
:生成 6 位随机验证码;
:验证码存入 Redis (有效期 5 分钟);
:调用短信网关发送;
:用户输入验证码;
if (验证码正确 ?) then (是)
if (手机号已注册 ?) then (是)
:直接登录, 建立会话;
else (否)
:自动创建用户;
note right
标识 - 手机号
角色 - 默认读者
end note
:登录并建立会话;
endif
else (否)
:提示 "验证码错误";
stop
endif
:回到文档页面;
stop
@enduml
```
**技术实现要点**
| 配置项 | 值 |
|-------|-----|
| 验证码长度 | 6 位纯数字 |
| 有效期 | 5 分钟 |
| 发送间隔 | 60 秒 |
| 单手机号日上限 | 10 次 |
| 短信模板 | 「您的 Writech 文档验证码为 {code}5 分钟内有效。」 |
| 频率计数器 | Redis INCR + TTL |
### 3.2 PC 浏览器认证体验
PC 端用户访问受控文档时,页面内弹出**半透明遮罩 + 居中登录浮层**(非跳转),浮层包含两个标签页:
| 标签页 | 内容 |
|-------|------|
| 微信扫码 | 显示动态二维码,扫码后自动完成登录并关闭浮层 |
| 短信验证 | 手机号输入框 + 发送按钮 + 验证码输入框 |
浮层设计原则:
- 背景半透明可窥见文档内容(制造阅读意愿)
- 登录成功后浮层自动消失,无页面跳转
- 会话有效期 7 天,有效期内不再弹出
### 3.3 手机浏览器认证体验
- **微信内打开**:直接调起微信授权(静默授权 + 头像/昵称),无需扫码
- **其他浏览器**:展示短信验证码登录界面,微信扫码二维码作为备选
### 3.4 会话管理
| 参数 | 值 | 说明 |
|------|-----|------|
| 会话存储 | Redis | key = `session:{token}`value = 用户信息 JSON |
| 会话有效期 | 7 天 | 自最后一次请求起滑动续期 |
| 会话 Token | HttpOnly Cookie | `mkdocs_session`Secure + SameSite=Lax |
| 并发会话 | 不限 | 同一用户可在多设备同时登录 |
---
## 四、用户管理
### 4.1 用户数据模型
```
users
├── id UUID 主键
├── phone VARCHAR(20) 手机号(唯一,可为空)
├── wechat_openid VARCHAR(64) 微信 OpenID(唯一,可为空)
├── wechat_unionid VARCHAR(64) 微信 UnionID(可为空)
├── nickname VARCHAR(50) 昵称
├── avatar_url TEXT 头像 URL
├── role ENUM 角色:reader / reviewer / blocked
├── list_type ENUM 名单类型:normal / whitelist / blacklist
├── group_id UUID 所属用户组(外键)
├── registered_at TIMESTAMP 注册时间
├── last_login_at TIMESTAMP 最后登录时间
└── status ENUM 状态:active / suspended / deleted
```
### 4.2 用户角色
| 角色 | 标识 | 权限说明 |
|------|------|---------|
| 读者 | reader | 默认角色,可阅读被授权的文档,可提交反馈 |
| 审阅者 | reviewer | 受邀审阅指定文档,可阅读、评论并提交修改建议 |
| 被封禁 | blocked | 黑名单用户,禁止访问所有受控文档 |
| 管理员 | admin | 管理用户、权限策略、查看统计(复用 Keycloak 管理员角色) |
> 说明:「作者」角色由 Gitea 仓库写权限定义,不在本系统管理范围内。
### 4.3 白名单与黑名单
#### 白名单机制
- 白名单用户可访问**所有 `login` 级别文档**,无需逐文档授权
- 适用场景:VIP 客户、长期合作伙伴、内部测试人员
- 管理方式:管理员在后台设置 `list_type = whitelist`
#### 黑名单机制
- 黑名单用户访问任何受控文档均返回 403
- 触发条件:
- 管理员手动加入
- AntiBot 检测到恶意爬取行为自动加入
- 用户连续提交违规反馈内容
- 管理方式:管理员在后台设置 `list_type = blacklist`
#### 名单优先级
```
黑名单 > 文档级 restricted > 白名单 > 文档级 login > 普通用户
```
### 4.4 用户分组
用户可归入多个组,方便按组授权:
```
user_groups
├── id UUID 主键
├── name VARCHAR(50) 组名(如"经销商"、"教育局客户"
├── description TEXT 说明
└── created_at TIMESTAMP
user_group_members
├── user_id UUID 外键 → users
├── group_id UUID 外键 → user_groups
└── joined_at TIMESTAMP
```
授权时可指定用户组,该组全部成员自动获得对应权限。
---
## 五、文档权限模型
### 5.1 权限分级
在现有 DocPortal 权限体系基础上,MkDocs 用户系统使用以下**五级访问控制**:
| 级别 | 标识 | 说明 | 是否需要登录 |
|------|------|------|-------------|
| 完全公开 | `open` | 任何人可无限次阅读,不触发任何认证 | 否 |
| 试读公开 | `open_once` | 匿名用户首次可阅读,再次访问需登录 | 首次否,再次是 |
| 登录可读 | `login` | 登录用户(含白名单)可阅读 | 是 |
| 指定可读 | `restricted` | 仅授权用户/用户组可阅读 | 是 |
| 内部机密 | `confidential` | 仅管理员和显式授权用户,额外审计日志 | 是 + 审计 |
### 5.2 文档操作权限
每篇文档可独立配置以下操作权限:
| 权限 | 标识 | 说明 |
|------|------|------|
| 只读 | `read` | 仅可在线阅读,不可复制/下载 |
| 反馈 | `feedback` | 可提交反馈意见(文字/评分),不可修改文档 |
| 评论 | `comment` | 可在文档段落级别发表评论 |
| 下载 | `download` | 可下载 Markdown 原文或 PDF |
| 修改建议 | `suggest` | 可提交修改建议(生成 Git PR),需作者审核 |
### 5.3 权限配置方式
#### 方式一:Front Matter 声明(文档级)
在 Markdown 文件头部的 YAML Front Matter 中声明权限:
```yaml
---
title: "智能笔 BLE 通信协议"
access: restricted
permissions:
read: true
feedback: true
comment: true
download: false
suggest: false
allowed_users:
- "138****1234"
- "group:经销商"
allowed_groups:
- "经销商"
- "教育局客户"
usage:
max_views: 50
expires_at: "2027-03-31"
---
```
#### 方式二:目录级继承(`.access.yml` 配置文件)
在 MkDocs 目录下放置 `.access.yml` 文件,该目录及子目录下所有文档继承此权限配置:
```yaml
# docs/技术方案/.access.yml
default_access: login
default_permissions:
read: true
feedback: true
comment: false
download: false
suggest: false
overrides:
- pattern: "公开简介.md"
access: open
- pattern: "机密/**"
access: confidential
```
#### 方式三:管理后台配置(全局策略)
管理员在 Web 后台按**仓库、目录路径通配符、标签**维度设置批量策略:
| 策略维度 | 示例 | 说明 |
|---------|------|------|
| 仓库 | `com-shware-doc` | 整个仓库默认 `login` |
| 目录通配符 | `*/对外文档/**` | 所有仓库的「对外文档」目录 `open` |
| 标签 | `tag:机密` | 含「机密」标签的文档 `confidential` |
| 单文档 | `SmartPen/硬件设计/xxx.md` | 指定文档覆盖策略 |
### 5.4 权限优先级(从高到低)
```
1. 黑名单 → 403 拒绝(最高优先)
2. 单文档 Front Matter 声明
3. 管理后台单文档策略
4. 目录级 .access.yml
5. 管理后台目录通配符策略
6. 管理后台标签策略
7. 管理后台仓库默认策略
8. 系统默认(open
```
### 5.5 避免登录繁杂的设计策略
为满足"颗粒度到单文档但不逐页登录"的要求,采用以下策略:
| 策略 | 机制 |
|------|------|
| 会话全站有效 | 登录一次后 7 天内访问同站点任何受控文档均免再次登录 |
| 目录级继承 | 同目录下文档共享权限配置,无需逐文件设置 |
| 公开文档零感知 | `open` 级文档不注入任何认证逻辑,加载速度等同纯静态页 |
| 延迟认证 | 用户浏览目录/搜索时不触发登录,点击具体受控文档才弹浮层 |
| 渐进式体验 | 受控文档先展示标题+摘要(前 3 段),余下内容模糊化并提示登录 |
---
## 六、文档使用控制
### 6.1 阅读次数限制
通过 Front Matter 或管理后台配置 `max_views` 参数:
```yaml
usage:
max_views: 50 # 该文档最多可被在线阅读 50 次
max_views_per_user: 5 # 每个用户最多阅读 5 次
```
**计数规则**
| 规则 | 说明 |
|------|------|
| 计数触发 | 用户打开文档页面并停留超过 5 秒才计为 1 次有效阅读 |
| 去重窗口 | 同一用户 30 分钟内多次打开同一文档仅计 1 次 |
| 存储位置 | PostgreSQL `doc_view_logs` 表 |
| 缓存加速 | Redis 缓存当前计数,每 10 分钟同步至数据库 |
### 6.2 有效期限制
```yaml
usage:
expires_at: "2027-03-31" # 到期后文档不可访问
available_from: "2026-06-01" # 生效日期(可选)
```
**到期处理策略**
| 场景 | 行为 |
|------|------|
| 到期后匿名访问 | 返回"文档已过期"提示页 |
| 到期后登录访问 | 返回"文档已过有效期,请联系管理员"提示页 |
| 管理员访问 | 不受限制,可正常查看 |
| 即将到期提醒 | 到期前 7 天向管理员发送 MsgHub 通知 |
### 6.3 使用控制数据表
```
doc_usage_config
├── doc_id VARCHAR 文档唯一标识(仓库 + 路径)
├── max_views INT 总阅读次数上限(NULL = 不限)
├── max_views_user INT 单用户阅读次数上限(NULL = 不限)
├── available_from DATE 生效日期(NULL = 即时生效)
├── expires_at DATE 到期日期(NULL = 永不过期)
└── updated_at TIMESTAMP
doc_view_logs
├── id UUID 主键
├── doc_id VARCHAR 文档标识
├── user_id UUID 用户 ID(匿名用户为 NULL)
├── fingerprint VARCHAR 浏览器指纹(匿名用户标识)
├── ip_address INET 访问 IP
├── user_agent TEXT 浏览器 UA
├── duration_sec INT 停留时长(秒)
├── viewed_at TIMESTAMP 访问时间
└── is_counted BOOLEAN 是否计入有效阅读
```
---
## 七、防爬虫策略
### 7.1 多层防护体系
```plantuml
@startuml
title 防爬虫多层防护体系
start
:外部请求;
partition "第一层 - Nginx 基础防护" {
:IP 频率限制;
:User-Agent 黑名单;
:robots.txt 声明;
}
if (命中拦截?) then (是)
:返回 429 / 403;
stop
else (否)
endif
partition "第二层 - AntiBot 行为检测" {
:浏览器指纹验证;
:JS 执行检测;
:鼠标/滚动行为分析;
}
if (疑似爬虫?) then (是)
:验证码挑战;
stop
else (否)
endif
partition "第三层 - 内容保护" {
:关键内容异步加载;
:文本防选择/防复制;
:动态水印注入;
}
:返回受保护的文档内容;
partition "第四层 - 智能封禁(后台异步)" {
:分析访问行为模式;
if (触发封禁条件?) then (是)
:自动加入黑名单;
else (否)
endif
}
stop
@enduml
```
### 7.2 各层策略详情
#### 第一层:Nginx 基础防护
| 策略 | 配置 | 说明 |
|------|------|------|
| IP 频率限制 | `limit_req_zone` 10 次/秒 | 单 IP 超限返回 429 |
| 并发连接限制 | `limit_conn` 20 连接/IP | 超限返回 503 |
| User-Agent 黑名单 | 拦截 curl/wget/scrapy/python-requests 等 | 返回 403 |
| robots.txt | 仅允许搜索引擎索引 `open` 级文档路径 | 受控文档 Disallow |
#### 第二层:AntiBot 行为检测
| 策略 | 实现 | 说明 |
|------|------|------|
| JS 执行检测 | 页面注入一段 JS,执行后生成 Token 回传 | 无 JS 执行能力的爬虫无法通过 |
| 浏览器指纹 | FingerprintJS 生成指纹,与 User-Agent 交叉验证 | 指纹缺失或矛盾判定为爬虫 |
| 行为分析 | 监测鼠标移动、页面滚动、停留时长 | 零行为数据判定为爬虫 |
#### 第三层:内容保护
| 策略 | 实现 | 适用级别 |
|------|------|---------|
| 关键内容异步加载 | 受控文档正文通过 AJAX 请求获取,HTML 源码不含全文 | login / restricted / confidential |
| CSS 防选择 | `user-select: none` + 右键菜单拦截 | restricted / confidential |
| 动态水印 | 用户昵称 + 手机号后 4 位半透明水印 | confidential |
#### 第四层:智能封禁
| 触发条件 | 处置 |
|---------|------|
| 同一 IP 10 分钟内访问 > 100 个不同文档 | 自动加入 IP 黑名单 24 小时 |
| 同一用户 1 小时内访问 > 50 个文档 | 账户临时冻结 + 管理员通知 |
| 被 JS 检测拦截 > 3 次 | IP + 指纹 永久封禁 |
---
## 八、阅读统计
### 8.1 统计维度
基于 Umami 埋点和系统自有日志,提供以下统计维度:
| 维度 | 数据来源 | 说明 |
|------|---------|------|
| 页面访问量 (PV) | Umami | 每篇文档的总访问次数 |
| 独立访客数 (UV) | Umami + 指纹 | 去重后的独立用户数 |
| 平均阅读时长 | Umami | 用户在文档页面的平均停留时间 |
| 阅读完成率 | 前端 JS 埋点 | 滚动到文档底部的用户占比 |
| 用户阅读清单 | doc_view_logs | 每个登录用户阅读过的文档列表 |
| 文档热度排行 | 聚合计算 | 按 PV / UV 排序的文档热度榜 |
| 时段分布 | Umami | 按小时/日/周分布的访问趋势 |
| 来源渠道 | Umami (Referrer) | 文档从哪些渠道/页面被引流 |
### 8.2 管理看板
管理员可在 Web 后台查看以下看板:
| 看板 | 内容 |
|------|------|
| 文档总览 | 各级别文档数量、总 PV/UV、活跃用户数 |
| 文档详情 | 单篇文档的访问趋势、用户列表、阅读时长分布 |
| 用户画像 | 单用户阅读历史、偏好标签、活跃时段 |
| 权限使用 | 各文档的已用/剩余阅读次数、即将到期提醒 |
| 安全日志 | 爬虫拦截记录、黑名单触发记录、异常访问报警 |
### 8.3 自定义埋点
在 MkDocs 主题模板中注入以下自定义事件:
```javascript
// 页面加载时
umami.track('doc_view', {
doc_id: '{{ doc_id }}',
access_level: '{{ access_level }}',
user_type: '{{ user_type }}' // anonymous / registered / whitelist
});
// 阅读完成时(滚动到底部)
window.addEventListener('scroll', function() {
if (isScrolledToBottom()) {
umami.track('doc_complete', { doc_id: '{{ doc_id }}' });
}
});
// 反馈提交时
function onFeedbackSubmit(rating, content) {
umami.track('doc_feedback', {
doc_id: '{{ doc_id }}',
rating: rating
});
}
```
---
## 九、MkDocs Guard 技术实现
### 9.1 架构模式:Nginx auth_request
MkDocs Guard 采用 **Nginx `auth_request` 子请求模式**,不修改 MkDocs 静态文件:
```
# Nginx 配置片段
location /docs/ {
auth_request /auth/check;
auth_request_set $auth_user $upstream_http_x_auth_user;
auth_request_set $auth_role $upstream_http_x_auth_role;
# 认证通过后,代理到 MkDocs 静态文件
proxy_pass http://127.0.0.1:8001;
# 注入用户信息到响应头(供前端 JS 使用)
add_header X-Auth-User $auth_user;
add_header X-Auth-Role $auth_role;
}
location = /auth/check {
internal;
proxy_pass http://127.0.0.1:3100/guard/check;
proxy_set_header X-Original-URI $request_uri;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header Cookie $http_cookie;
}
```
### 9.2 Guard 服务端逻辑
```
MkDocs Guard (Node.js, 端口 3100)
├── GET /guard/check — Nginx auth_request 回调
│ ├─ 解析 X-Original-URI 获取文档路径
│ ├─ 查询文档权限级别(Redis 缓存 → PostgreSQL
│ ├─ open 级别 → 200 放行
│ ├─ 检查 Cookie 中的会话 Token
│ ├─ 有效会话 → 调用 PermEngine 校验 → 200/403
│ └─ 无会话 + 非 open → 401(触发前端弹登录浮层)
├── POST /guard/login/wechat — 微信扫码登录回调
├── POST /guard/login/sms — 短信验证码登录
├── POST /guard/logout — 登出
└── GET /guard/session — 查询当前会话状态
```
### 9.3 PermEngine 权限引擎
```
PermEngine (Node.js 模块)
├── checkAccess(userId, docPath)
│ ├─ 检查用户黑名单 → blocked 则拒绝
│ ├─ 加载文档权限配置(优先级链)
│ ├─ open → 放行
│ ├─ open_once → 查浏览器指纹记录
│ ├─ login → 已登录即放行;白名单用户放行
│ ├─ restricted → 检查 allowed_users / allowed_groups
│ ├─ confidential → 检查显式授权 + 写审计日志
│ └─ 检查 usage 限制(次数 + 日期)
├── loadDocConfig(docPath)
│ ├─ 查 Redis 缓存
│ ├─ 缓存未命中 → 查 PostgreSQL
│ ├─ 合并优先级链(Front Matter > 单文档 > 目录 > 仓库)
│ └─ 写入 Redis 缓存(TTL 5 分钟)
└── getPermissions(userId, docPath)
└─ 返回用户在该文档上的操作权限集合
```
### 9.4 数据库完整设计
```plantuml
@startuml
skinparam componentStyle rectangle
title 用户阅读管理系统 - 数据表关系
rectangle "users\n用户表" as Users {
rectangle "id, phone, wechat_openid\nnickname, role, list_type\ngroup_id, status" as UF
}
rectangle "user_groups\n用户组表" as Groups {
rectangle "id, name, description" as GF
}
rectangle "user_group_members\n组成员表" as Members {
rectangle "user_id, group_id" as MF
}
rectangle "doc_access_policies\n文档权限策略表" as Policies {
rectangle "id, doc_pattern, access_level\npermissions, allowed_users\nallowed_groups, priority" as PF
}
rectangle "doc_usage_config\n使用控制表" as Usage {
rectangle "doc_id, max_views\nmax_views_user\navailable_from, expires_at" as UGF
}
rectangle "doc_view_logs\n阅读日志表" as Logs {
rectangle "id, doc_id, user_id\nfingerprint, ip, duration\nviewed_at" as LF
}
rectangle "antibot_blocklist\n封禁记录表" as Block {
rectangle "id, ip, fingerprint\nuser_id, reason\nblocked_until" as BF
}
Users -right-> Members
Groups -down-> Members
Users -down-> Logs
Policies -down-> Usage
@enduml
```
---
## 十、管理后台
### 10.1 功能模块
管理后台作为 MkDocs Guard 的 Web 管理界面,提供以下功能:
| 模块 | 功能 |
|------|------|
| 用户管理 | 查看用户列表、搜索、设置白名单/黑名单、编辑用户组 |
| 权限管理 | 配置文档访问策略、目录级/标签级批量授权、授权用户/组 |
| 使用控制 | 设置文档阅读次数上限、有效期、查看用量 |
| 阅读统计 | 文档热度排行、用户阅读画像、渠道来源分析 |
| 安全中心 | 爬虫拦截日志、封禁记录管理、异常告警配置 |
| 系统配置 | 短信网关配置、微信开放平台参数、会话策略调整 |
### 10.2 管理后台技术栈
| 组件 | 技术 |
|------|------|
| 前端框架 | React + Ant Design |
| 后端 API | Node.js + Express(与 Guard 同进程) |
| 认证 | Keycloak SSOadmin 角色) |
| 部署 | 与 MkDocs Guard 同容器,路由 `/admin/` |
---
## 十一、部署方案
### 11.1 服务清单
| 服务 | 端口 | 部署位置 | 说明 |
|------|------|---------|------|
| Nginx | 443 | 互联网应用服务器 | SSL 终止 + auth_request |
| MkDocs 静态站 | 8001 | 互联网应用服务器 | 由 DocForge 自动构建 |
| MkDocs Guard | 3100 | 互联网应用服务器 | 认证网关 + 权限引擎 + 管理后台 |
| Keycloak | 8080 | 互联网应用服务器(已有) | SSO 认证中心 |
| Redis | 6379 | 互联网应用服务器 | 会话 + 缓存 + 限流 |
| PostgreSQL | 5432 | 私有云(已有) | 用户数据 + 权限策略 + 日志 |
| Umami | 3000 | 互联网应用服务器(已有) | 统计分析 |
### 11.2 Docker Compose 部署
```yaml
services:
mkdocs-guard:
image: node:20-alpine
ports:
- "3100:3100"
environment:
- DATABASE_URL=postgresql://guard:***@db:5432/mkdocs_guard
- REDIS_URL=redis://redis:6379/2
- KEYCLOAK_URL=https://sso.writech.cn
- KEYCLOAK_REALM=writech
- WECHAT_APP_ID=${WECHAT_APP_ID}
- WECHAT_APP_SECRET=${WECHAT_APP_SECRET}
- SMS_ACCESS_KEY=${SMS_ACCESS_KEY}
- SMS_ACCESS_SECRET=${SMS_ACCESS_SECRET}
depends_on:
- redis
mkdocs-site:
image: nginx:alpine
ports:
- "8001:80"
volumes:
- ./site:/usr/share/nginx/html:ro
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- redis_data:/data
volumes:
redis_data:
```
---
## 十二、实施路线图
| 阶段 | 时间 | 任务 | 交付物 |
|------|------|------|--------|
| **一期:基础认证** | 第 1-3 周 | MkDocs Guard + Nginx auth_request 集成;微信扫码登录;短信验证码登录;会话管理 | 用户可通过微信/短信登录访问受控文档 |
| **二期:权限引擎** | 第 4-6 周 | PermEngine 开发;Front Matter 权限解析;目录级 `.access.yml` 继承;白名单/黑名单管理 | 文档可按级别和用户/组授权 |
| **三期:使用控制** | 第 7-8 周 | 阅读次数限制;有效期管理;渐进式体验(摘要预览 + 模糊化) | 文档使用可按次数和时间控制 |
| **四期:防爬虫** | 第 9-10 周 | AntiBot 四层防护部署;智能封禁规则;内容异步加载 | 爬虫有效拦截 |
| **五期:统计与后台** | 第 11-13 周 | Umami 自定义埋点;管理后台开发;阅读统计看板;安全日志 | 管理员可视化管理全部功能 |
| **六期:优化上线** | 第 14-15 周 | 性能调优;安全审计;用户体验打磨;正式上线 | 系统全功能投入运营 |