更新
This commit is contained in:
@@ -0,0 +1,925 @@
|
|||||||
|
# MkDocs 用户阅读管理系统 — 规划设计及实施方案
|
||||||
|
|
||||||
|
## 一、概述
|
||||||
|
|
||||||
|
### 1.1 目标
|
||||||
|
|
||||||
|
针对以 MkDocs 生成的静态文档站点(如 docs.writech.cn),构建一套**用户阅读管理系统**,实现用户登记与身份识别、文档分级权限控制、阅读次数与有效期管理、防爬虫保护和阅读行为统计分析。系统在保障文档安全可控的同时,为大部分公开文档提供**免登录流畅阅读**体验,仅在访问受控文档时才触发轻量认证。
|
||||||
|
|
||||||
|
### 1.2 与现有平台的关系
|
||||||
|
|
||||||
|
本系统基于 [ICT 服务平台](ICT服务平台概要.md) 已有基础设施构建,复用以下底座能力:
|
||||||
|
|
||||||
|
| 已有组件 | 本系统中的角色 |
|
||||||
|
|---------|-------------|
|
||||||
|
| Keycloak(SSO) | 登录用户的 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 SSO(admin 角色) |
|
||||||
|
| 部署 | 与 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 周 | 性能调优;安全审计;用户体验打磨;正式上线 | 系统全功能投入运营 |
|
||||||
Reference in New Issue
Block a user