Files
2fa/docs/ARCHITECTURE.md
wuzf 9e46357a4d docs: streamline deployment guide and fix outdated instructions (#13)
DEPLOYMENT.md 从 1445 行精简至 318 行:
- 删除「非开发者部署指南」整章,该路径本身不合理(无命令行经验的
  用户应走一键部署),且内容细至教用户按 Win+R、用记事本存盘
- 合并重复内容:设置密码流程原有 4 处、密码强度要求 3 处、
  创建 KV 命令 3 处、生成加密密钥 4 处、升级指南 2 处
- 移除 87 行「 页面正常加载」式验证清单,改为 4 行关键验证要点
- 一键部署章节删除手动创建/绑定 KV 的过时步骤(wrangler.toml 已
  声明 SECRETS_KV,首次部署自动创建)

修正过时或错误的内容:
- KV 命令改为 wrangler 4.x 语法(kv:namespace 在 4.78 下报
  Unknown arguments,正确写法是 kv namespace),同步修正
  DEVELOPMENT.md 与 build-release.js 内嵌的部署说明
- CORS 章节改写:实现是基于请求 Host 的动态同源判断,
  原文档描述的 ALLOWED_ORIGINS 常量并不存在
- 自定义域名示例补 custom_domain = true 并去掉路由通配符,
  补充「修改后需重新部署」
- 开发环境部署命令改用 npm run deploy:dev,保留版本注入流程
- 修正浏览器控制台生成加密密钥的示例(原代码产出无效 Base64)
- DEVELOPMENT.md 修正 Token 有效期为 30 天(实际值,原写 1 天)

修复失效锚点:
- DEVELOPMENT.md 目录 10 处及 docs/README.md 3 处引用,emoji
  标题在 GitHub 生成的锚点形如 #-测试指南 / #️-开发环境
2026-08-01 17:37:52 +08:00

1409 lines
40 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.
# 🏗️ 项目架构文档
## 📋 目录
- [总体架构](#总体架构)
- [技术栈](#技术栈)
- [代码结构](#代码结构)
- [核心模块详解](#核心模块详解)
- [数据流](#数据流)
- [前端架构](#前端架构)
- [设计模式](#设计模式)
---
## 总体架构
### 三层架构
```
┌─────────────────────────────────────────────────────┐
│ 用户层 │
│ 浏览器 / PWA / 移动设备 │
└──────────────────┬──────────────────────────────────┘
│ HTTPS
┌─────────────────────────────────────────────────────┐
│ Cloudflare Edge │
│ CDN + DDoS 保护 + SSL + 全球分布 │
└──────────────────┬──────────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│ Cloudflare Workers应用层
│ ┌─────────────┐ ┌─────────────┐ ┌────────────┐ │
│ │ 路由处理 │ │ API服务 │ │ UI渲染 │ │
│ └─────────────┘ └─────────────┘ └────────────┘ │
│ ┌─────────────┐ ┌─────────────┐ ┌────────────┐ │
│ │ 认证系统 │ │ 加密系统 │ │ 监控系统 │ │
│ └─────────────┘ └─────────────┘ └────────────┘ │
└──────────────────┬──────────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│ Cloudflare KV数据存储层
│ 全球分布式键值存储 + 自动加密 + 低延迟 │
└─────────────────────────────────────────────────────┘
```
### 核心特性
- **无服务器架构**: 基于 Cloudflare Workers无需维护服务器
- **全球分布**: 在全球 300+ 个城市的边缘节点上运行
- **高可用性**: 自动故障转移和负载均衡
- **低延迟**: 就近服务,平均响应时间 < 50ms
- **自动扩展**: 根据流量自动扩缩容
- **零冷启动**: V8 隔离技术,无冷启动延迟
---
## 技术栈
### 后端
| 技术 | 用途 | 版本 |
| ---------------------- | ---------- | --------- |
| **Cloudflare Workers** | 运行时环境 | V8 Engine |
| **ES Modules** | 模块系统 | ES2022 |
| **Web Crypto API** | 加密操作 | 标准 API |
| **Cloudflare KV** | 数据存储 | - |
### 前端
| 技术 | 用途 | 说明 |
| ----------------------- | ---------- | ------------------------- |
| **HTML5** | 页面结构 | 语义化标签 |
| **CSS3** | 样式系统 | 模块化 CSS |
| **JavaScript (ES2022)** | 交互逻辑 | 原生 JS无框架 |
| **PWA** | 应用增强 | Service Worker + Manifest |
| **jsQR** | 二维码识别 | CDN 引入 |
| **qrcode-generator** | 二维码生成 | CDN 引入 |
### 开发工具
| 工具 | 用途 |
| ---------------- | ---------------- |
| **Wrangler CLI** | 开发和部署工具 |
| **Git** | 版本控制 |
| **Node.js** | 构建工具运行环境 |
---
## 代码结构
### 完整目录树
```
src/
├── worker.js # 🎯 Worker 主入口
│ # - Fetch 事件处理
│ # - CORS 处理
│ # - 全局错误捕获
│ # - 监控系统初始化
├── router/
│ └── handler.js # 🛣️ 路由处理器
│ # - 路径解析和分发
│ # - 认证检查
│ # - API 路由映射
├── api/
│ ├── secrets/ # 🔌 密钥管理 API模块化
│ │ ├── index.js # 统一导出Barrel Export
│ │ ├── shared.js # 共享工具saveSecretsToKV, getAllSecrets
│ │ ├── crud.js # CRUD 操作
│ │ ├── batch.js # 批量导入
│ │ ├── backup.js # 备份创建和列表
│ │ ├── restore.js # 备份恢复和导出
│ │ └── otp.js # OTP 生成
│ └── favicon.js # 🌐 Favicon 代理 API
├── otp/
│ └── generator.js # 🔐 OTP 算法实现
│ # - TOTP (RFC 6238)
│ # - HOTP (RFC 4226)
│ # - Base32 编解码
├── ui/
│ ├── page.js # 🎨 主页面生成
│ │ # - HTML 结构
│ │ # - 样式集成
│ │ # - 脚本集成
│ │
│ ├── manifest.js # 📱 PWA Manifest
│ │ # - 应用信息
│ │ # - 图标定义
│ │ # - 快捷方式
│ │
│ ├── serviceworker.js # ⚙️ Service Worker
│ │ # - 缓存策略
│ │ # - 离线支持
│ │ # - CDN 资源缓存
│ │
│ ├── scripts/ # 📜 前端 JavaScript 模块
│ │ ├── index.js # 模块集成入口
│ │ ├── state.js # 全局状态管理
│ │ ├── auth.js # 认证逻辑
│ │ ├── core.js # 核心业务逻辑
│ │ ├── utils.js # 工具函数
│ │ └── pwa.js # PWA 功能
│ │
│ └── styles/ # 🎨 前端 CSS 模块
│ ├── index.js # 样式集成入口
│ ├── base.js # 基础样式
│ ├── components.js # 组件样式
│ ├── modals.js # 模态框样式
│ └── responsive.js # 响应式样式
└── utils/ # 🛠️ 工具模块
├── auth.js # 🔑 认证系统
│ # - Token 验证
│ # - HttpOnly Cookie
│ # - 自动刷新
├── backup.js # 💾 智能备份系统
│ # - 事件驱动备份
│ # - 并发合并 / 后台执行
│ # - 自动清理
├── constants.js # 📋 常量定义
│ # - KV 键名
│ # - 配置常量
├── crypto.js # 🔐 加密工具
│ # - HMAC-SHA1/256
│ # - Base32 编解码
├── encryption.js # 🔒 数据加密
│ # - AES-GCM 256
│ # - 密钥派生
│ # - 自动加解密
├── errors.js # ❌ 统一错误分类
│ # - 自定义错误类
│ # - 错误处理和响应格式
├── logger.js # 📝 日志系统
│ # - 结构化日志
│ # - 性能计时
│ # - 日志级别
├── monitoring.js # 📊 监控系统
│ # - 错误追踪
│ # - 性能监控
├── rateLimit.js # 🛡️ 请求限流
│ # - 滑动窗口算法
│ # - 可配置策略
│ # - 基于 KV 存储
├── response.js # 📡 响应工具
│ # - 标准化响应格式
│ # - CORS 头处理
├── security.js # 🔒 安全工具
│ # - CORS 配置
│ # - CSP 头
│ # - 预检请求
└── validation.js # ✅ 数据验证
# - Base32 验证
# - 输入校验
# - 业务规则检查
```
---
## 核心模块详解
### 1. Worker 主入口 (`worker.js`)
**职责**: Cloudflare Worker 的入口点,处理所有传入请求
**核心功能**:
```javascript
export default {
async fetch(request, env, ctx) {
// 1. 初始化日志和监控
// 2. 处理 CORS 预检请求
// 3. 开始请求追踪
// 4. 调用路由处理器
// 5. 记录响应和性能指标
// 6. 全局错误处理
},
async scheduled(event, env, ctx) {
// 定时任务:自动备份(每天)
},
};
```
**集成的系统**:
- 日志系统 (`logger.js`)
- 监控系统 (`monitoring.js`)
- 路由处理 (`router/handler.js`)
- CORS 处理 (`utils/security.js`)
---
### 2. 路由处理器 (`router/handler.js`)
**职责**: 解析 URL 路径并分发到对应的处理函数
**路由表**:
| 路由 | 方法 | 处理器 | 认证 |
| -------------------------- | ------ | ------------------------- | ---- |
| `/` | GET | `createMainPage()` | ❌ |
| `/setup` | GET | `createSetupPage()` | ❌ |
| `/manifest.json` | GET | `createManifest()` | ❌ |
| `/sw.js` | GET | `createServiceWorker()` | ❌ |
| `/icon-*.png` | GET | `createDefaultIcon()` | ❌ |
| `/modules/{name}` | GET | `getModuleCode()` | ✅ |
| `/api/setup` | POST | `handleFirstTimeSetup()` | ❌ |
| `/api/login` | POST | `handleLogin()` | ❌ |
| `/api/refresh-token` | POST | `handleRefreshToken()` | ✅ |
| `/api/secrets` | GET | `handleGetSecrets()` | ✅ |
| `/api/secrets` | POST | `handleAddSecret()` | ✅ |
| `/api/secrets/{id}` | PUT | `handleUpdateSecret()` | ✅ |
| `/api/secrets/{id}` | DELETE | `handleDeleteSecret()` | ✅ |
| `/api/secrets/batch` | POST | `handleBatchAddSecrets()` | ✅ |
| `/api/secrets/export` | POST | `handleExportSecrets()` | ✅ |
| `/api/backup` | GET | `handleGetBackups()` | ✅ |
| `/api/backup` | POST | `handleBackupSecrets()` | ✅ |
| `/api/backup/restore` | POST | `handleRestoreBackup()` | ✅ |
| `/api/backup/export/{key}` | GET | `handleExportBackup()` | ✅ |
| `/api/favicon/{domain}` | GET | `handleFaviconProxy()` | ✅ |
| `/otp/{secret}` | GET | `handleGenerateOTP()` | ❌ |
---
### 3. 密钥管理 API (`api/secrets/`)
**职责**: 处理 2FA 密钥的 CRUD 操作和备份管理
**模块化组织**:
- `shared.js` - 共享工具函数saveSecretsToKV, getAllSecrets
- `crud.js` - CRUD 操作GET/POST/PUT/DELETE
- `batch.js` - 批量导入
- `backup.js` - 备份创建和列表
- `restore.js` - 备份恢复和导出
- `otp.js` - OTP 生成
- `index.js` - 统一导出Barrel Export
**核心功能**:
#### 数据自动加密
```javascript
async function saveSecretsToKV(env, secrets, reason) {
// 1. 排序密钥
sortSecretsByName(secrets);
// 2. 加密数据(如果配置了 ENCRYPTION_KEY
const encryptedData = await encryptSecrets(secrets, env);
// 3. 保存到 KV
await env.SECRETS_KV.put('secrets', encryptedData);
// 4. 触发事件驱动备份
await triggerBackup(secrets, env, { reason });
}
```
#### 请求限流集成
```javascript
export async function handleAddSecret(request, env) {
// 1. 检查限流
const rateLimitResult = await checkRateLimit(request, env, RATE_LIMIT_PRESETS.api);
if (!rateLimitResult.allowed) {
return createRateLimitResponse(rateLimitResult);
}
// 2. 处理请求...
}
```
---
### 4. OTP 生成器 (`otp/generator.js`)
**职责**: 实现 TOTP/HOTP 算法,生成一次性密码
**支持的算法**:
#### TOTP (Time-based OTP) - RFC 6238
```javascript
/**
* 算法流程:
* 1. 计算时间计数器 (counter = floor(currentTime / 30))
* 2. 将 Base32 密钥解码为字节数组
* 3. 使用 HMAC-SHA1 计算哈希值
* 4. 动态截断生成 6 位数字 OTP
*/
export async function generateOTP(secret, loadTime, options = {}) {
const {
period = 30, // 时间步长(秒)
digits = 6, // OTP 长度
algorithm = 'SHA1', // 哈希算法
type = 'TOTP',
} = options;
const timeForCalculation = loadTime || Math.floor(Date.now() / 1000);
const counter = type === 'HOTP' ? options.counter || 0 : Math.floor(timeForCalculation / period);
return await generateHOTP(secret, counter, { digits, algorithm });
}
```
#### HOTP (HMAC-based OTP) - RFC 4226
```javascript
export async function generateHOTP(secret, counter, options = {}) {
// 1. Base32 解码
const key = base32Decode(secret);
// 2. 计数器转字节数组
const counterBytes = new ArrayBuffer(8);
const view = new DataView(counterBytes);
view.setUint32(4, counter, false); // 大端序
// 3. HMAC-SHA1
const hmac = await crypto.subtle.sign('HMAC', key, counterBytes);
// 4. 动态截断
const offset = hmac[hmac.length - 1] & 0x0f;
const binary =
((hmac[offset] & 0x7f) << 24) | ((hmac[offset + 1] & 0xff) << 16) | ((hmac[offset + 2] & 0xff) << 8) | (hmac[offset + 3] & 0xff);
// 5. 生成 OTP
const otp = binary % Math.pow(10, digits);
return otp.toString().padStart(digits, '0');
}
```
---
### 5. 认证系统 (`utils/auth.js`)
**职责**: 管理用户身份认证和授权
**架构设计**:
```
┌───────────┐ 登录请求 ┌───────────┐
│ 浏览器 │ ──────────────→ │ Worker │
└───────────┘ └─────┬─────┘
↑ │
│ ▼
│ 验证密码KV存储
│ │
│ ▼
│ Set-Cookie: 生成 JWT
│ auth_token=... │
│ HttpOnly; Secure │
│ ←───────────────────────────┘
│ 后续请求(自动携带 Cookie
│ ──────────────────────────────→
│ 验证 JWT + 自动刷新
│ ←──────────────────────────────
```
**核心功能**:
#### HttpOnly Cookie 认证
```javascript
// 生成认证 Cookie
function createAuthCookie(token, expiresAt) {
const maxAge = Math.floor((expiresAt - Date.now()) / 1000);
return [
`auth_token=${token}`,
'HttpOnly', // 防止 XSS
'Secure', // 仅 HTTPS
'SameSite=Strict', // 防止 CSRF
`Max-Age=${maxAge}`,
'Path=/',
].join('; ');
}
```
#### Token 自动刷新
```javascript
export async function handleRefreshToken(request, env) {
// 1. 验证当前 Token
const currentToken = extractTokenFromCookie(request);
if (!isValidToken(currentToken)) {
return createUnauthorizedResponse();
}
// 2. 生成新 Token
const newToken = await generateJWT({
iat: Math.floor(Date.now() / 1000),
exp: Math.floor(Date.now() / 1000) + 7 * 24 * 60 * 60, // 7天
});
// 3. 设置新 Cookie
return new Response(JSON.stringify({ success: true }), {
headers: {
'Set-Cookie': createAuthCookie(newToken, Date.now() + 7 * 24 * 60 * 60 * 1000),
'Content-Type': 'application/json',
},
});
}
```
---
### 6. 加密系统 (`utils/encryption.js`)
**职责**: 使用 AES-GCM 256 位加密保护敏感数据
**加密流程**:
```
明文数据 → JSON.stringify → UTF-8 编码
生成随机 IV (96 bits)
AES-GCM 256 加密
认证标签 (128 bits)
{encrypted: base64(密文), iv: base64(IV)}
JSON.stringify → Base64 编码
存储到 KV
```
**核心实现**:
```javascript
export async function encryptData(data, env) {
// 1. 检查是否配置了加密密钥
if (!env.ENCRYPTION_KEY) {
// 未配置密钥,返回明文
return typeof data === 'string' ? data : JSON.stringify(data);
}
// 2. 导入加密密钥
const keyBuffer = base64ToArrayBuffer(env.ENCRYPTION_KEY);
const key = await crypto.subtle.importKey('raw', keyBuffer, { name: 'AES-GCM' }, false, ['encrypt']);
// 3. 生成随机 IV
const iv = crypto.getRandomValues(new Uint8Array(12));
// 4. 加密数据
const plaintext = new TextEncoder().encode(typeof data === 'string' ? data : JSON.stringify(data));
const ciphertext = await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, key, plaintext);
// 5. 打包加密结果
const encrypted = {
encrypted: arrayBufferToBase64(ciphertext),
iv: arrayBufferToBase64(iv.buffer),
};
// 6. 添加加密标记并返回
return `__ENCRYPTED__${btoa(JSON.stringify(encrypted))}`;
}
```
**自动检测和解密**:
```javascript
export async function decryptSecrets(data, env) {
if (!data) return [];
// 检查是否已加密
if (isEncrypted(data)) {
// 数据已加密,需要解密
if (!env.ENCRYPTION_KEY) {
console.error('数据已加密但未配置 ENCRYPTION_KEY');
return [];
}
return await decryptData(data, env);
} else {
// 数据未加密(明文或旧数据)
try {
return JSON.parse(data);
} catch (error) {
console.error('解析数据失败:', error);
return [];
}
}
}
```
---
### 7. 备份系统 (`utils/backup.js`)
**职责**: 实现智能备份策略,防止数据丢失
**备份策略**:
#### 1. 事件驱动备份
```
用户操作 → 数据变更 → 触发备份
如有请求上下文则转入 waitUntil 后台执行
执行备份 → 加密 → 存储到 KV
自动清理 (保留最新100个)
```
#### 2. 定时备份(兜底)
```
Cron 触发 (每天)
检查数据是否变化
如果变化 → 执行备份
```
**核心实现**:
```javascript
class BackupManager {
constructor(env) {
this.env = env;
this.logger = getLogger(env);
this.backupInProgress = false;
this.pendingBackups = [];
}
/**
* 触发备份(支持并发合并)
*/
async triggerBackup(secrets, options = {}) {
const { immediate = false, reason = 'event-driven', ctx } = options;
if (this.backupInProgress) {
this.pendingBackups.push({ secrets, reason, ctx, immediate });
return { queued: true };
}
return this.executeBackup(secrets, reason, ctx, { immediate });
}
/**
* 执行备份
*/
async executeBackup(secrets, reason, ctx) {
this.backupInProgress = true;
try {
const backupEntry = await createBackupEntry(secrets, this.env, {
format: await resolveConfiguredBackupFormat(this.env, this.logger),
reason,
});
await putBackupRecord(this.env, backupEntry.backupKey, backupEntry.backupContent, backupEntry.metadata);
ctx?.waitUntil?.(pushToAllWebDAV(backupEntry.backupKey, backupEntry.backupContent, this.env));
ctx?.waitUntil?.(pushToAllS3(backupEntry.backupKey, backupEntry.backupContent, this.env));
ctx?.waitUntil?.(pushToAllOneDrive(backupEntry.backupKey, backupEntry.backupContent, this.env));
ctx?.waitUntil?.(pushToAllGoogleDrive(backupEntry.backupKey, backupEntry.backupContent, this.env));
await this._cleanupOldBackupsAsync();
return { success: true, backupKey: backupEntry.backupKey, format: backupEntry.format };
} catch (error) {
this.logger.error('❌ 备份失败', { reason }, error);
throw error;
} finally {
this.backupInProgress = false;
}
}
/**
* 清理旧备份
*/
async _cleanupOldBackupsAsync() {
const list = await this.env.SECRETS_KV.list({ prefix: 'backup_' });
const backups = list.keys;
if (backups.length > MAX_BACKUPS) {
// 按时间排序,删除最旧的备份
const toDelete = backups.sort((a, b) => a.name.localeCompare(b.name)).slice(0, backups.length - MAX_BACKUPS);
for (const backup of toDelete) {
await this.env.SECRETS_KV.delete(backup.name);
}
this.logger.info(`🗑️ 已清理 ${toDelete.length} 个旧备份`);
}
}
}
```
---
### 8. 监控系统 (`utils/logger.js` + `utils/monitoring.js`)
**职责**: 提供结构化日志和错误追踪
**日志系统架构**:
```
┌───────────────┐
│ Logger API │
│ (logger.js) │
└───────┬───────┘
├─→ Console (开发环境)
└─→ Cloudflare Analytics
```
**核心功能**:
#### 结构化日志
```javascript
class Logger {
constructor(env, context = {}) {
this.env = env;
this.context = context;
this.level = env.LOG_LEVEL || 'INFO';
}
info(message, meta = {}) {
this._log('INFO', message, meta);
}
error(message, meta = {}, error = null) {
this._log('ERROR', message, { ...meta, error: error?.stack });
// 同时发送到错误监控
if (error) {
monitoring.captureError(error, meta, ErrorSeverity.ERROR);
}
}
_log(level, message, meta) {
if (!this._shouldLog(level)) return;
const logEntry = {
level,
message,
timestamp: new Date().toISOString(),
context: this.context,
meta,
};
console.log(JSON.stringify(logEntry));
}
}
```
#### 性能计时
```javascript
class PerformanceTimer {
constructor(name, logger) {
this.name = name;
this.logger = logger;
this.startTime = Date.now();
this.checkpoints = [];
}
checkpoint(label) {
const elapsed = Date.now() - this.startTime;
this.checkpoints.push({ label, elapsed });
}
end(meta = {}) {
const duration = Date.now() - this.startTime;
this.logger.info(`⏱️ ${this.name} completed`, {
duration: `${duration}ms`,
checkpoints: this.checkpoints,
...meta,
});
// 记录到性能监控
monitoring.recordMetric(this.name, duration, 'ms', meta);
}
}
```
---
### 9. 限流系统 (`utils/rateLimit.js`)
**职责**: 防止 API 滥用和 DDoS 攻击
**算法**: 滑动窗口 (Sliding Window)
```
时间轴: ───────────────────────────→
[最近 60 秒滑动窗口]
↑ 当前请求
窗口内计数: 只统计最近 windowSeconds 内的请求
窗口推进: 每次请求到来时重新计算
```
**核心实现**:
```javascript
export async function checkRateLimit(key, env, options) {
const { maxAttempts = 5, windowSeconds = 60 } = options;
const rateLimitKey = `ratelimit:${key}`;
// 1. 获取当前限流数据
const data = await env.SECRETS_KV.get(rateLimitKey, 'json');
const now = Date.now();
// 2. 如果没有数据或窗口已过期,创建新窗口
if (!data || now > data.resetAt) {
await env.SECRETS_KV.put(
rateLimitKey,
JSON.stringify({
count: 1,
resetAt: now + windowSeconds * 1000,
firstRequest: now,
}),
{
expirationTtl: windowSeconds + 10,
},
);
return {
allowed: true,
remaining: maxAttempts - 1,
resetAt: now + windowSeconds * 1000,
limit: maxAttempts,
};
}
// 3. 检查是否超过限制
if (data.count >= maxAttempts) {
return {
allowed: false,
remaining: 0,
resetAt: data.resetAt,
limit: maxAttempts,
};
}
// 4. 增加计数
data.count++;
await env.SECRETS_KV.put(rateLimitKey, JSON.stringify(data), {
expirationTtl: Math.ceil((data.resetAt - now) / 1000) + 10,
});
return {
allowed: true,
remaining: maxAttempts - data.count,
resetAt: data.resetAt,
limit: maxAttempts,
};
}
```
**预设策略**:
```javascript
export const RATE_LIMIT_PRESETS = {
// 登录:每分钟 5 次
login: {
maxAttempts: 5,
windowSeconds: 60,
},
// API 操作:每分钟 30 次
api: {
maxAttempts: 30,
windowSeconds: 60,
},
// 敏感操作:每分钟 10 次
sensitive: {
maxAttempts: 10,
windowSeconds: 60,
},
// 批量操作:每 5 分钟 20 次
bulk: {
maxAttempts: 20,
windowSeconds: 300,
},
// 全局保护:每分钟 100 次
global: {
maxAttempts: 100,
windowSeconds: 60,
},
};
```
**算法选择理由**:
- ✅ 实现简单,性能高效(单次 KV 操作)
- ✅ 内存占用低(只存储计数和窗口结束时间)
- ✅ 完美适配 Cloudflare Workers 无状态架构
- ⚠️ 存在窗口边界效应(可能 2倍突发流量
---
## 数据流
### 完整请求处理流程
```mermaid
graph TD
A[用户请求] --> B{CORS 预检?}
B -->|是| C[返回 CORS 响应]
B -->|否| D[初始化日志和监控]
D --> E[开始性能追踪]
E --> F{需要认证?}
F -->|是| G{Token 有效?}
G -->|否| H[返回 401 未授权]
G -->|是| I[路由解析]
F -->|否| I
I --> J{路由类型}
J -->|静态页面| K[生成 HTML]
J -->|API 请求| L{检查限流}
J -->|PWA 资源| M[返回 Manifest/SW/Icon]
L -->|超过限流| N[返回 429]
L -->|通过| O[处理 API 请求]
O --> P{操作类型}
P -->|读取| Q[从 KV 读取]
P -->|写入| R[验证数据]
Q --> S[解密数据]
R --> T[加密数据]
S --> U[返回响应]
T --> V[保存到 KV]
V --> W[触发备份]
W --> U
K --> U
M --> U
U --> X[记录性能指标]
X --> Y[结束]
```
### 数据加密流程
```mermaid
graph LR
A[用户提交密钥] --> B[客户端验证]
B --> C[发送到 API]
C --> D{ENCRYPTION_KEY<br/>已配置?}
D -->|是| E[生成随机 IV]
E --> F[AES-GCM 加密]
F --> G[生成认证标签]
G --> H[打包: encrypted+iv]
H --> I[Base64 编码]
I --> J[添加加密标记]
J --> K[存储到 KV]
D -->|否| L[JSON.stringify]
L --> K
K2[从 KV 读取] --> M{检测加密标记}
M -->|已加密| N[Base64 解码]
N --> O[提取 encrypted+iv]
O --> P[AES-GCM 解密]
P --> Q[验证认证标签]
Q --> R[返回明文数据]
M -->|未加密| S[JSON.parse]
S --> R
```
### 备份触发流程
```mermaid
graph TD
A[数据变更操作] --> B[保存到 KV]
B --> C[触发备份]
C --> D{正在备份?}
D -->|是| E[合并到待处理备份队列]
D -->|否| I[立即执行备份]
I --> J[加密备份数据]
J --> K[生成备份ID]
K --> L[保存到 KV]
L --> M[更新备份列表]
M --> N{备份数量 > 100?}
N -->|是| O[删除最旧备份]
N -->|否| P[完成]
O --> P
Q[定时任务<br/>每天] --> R{数据有变化?}
R -->|是| C
R -->|否| S[跳过]
```
---
## 前端架构
### 模块化 JavaScript (5 个模块)
```
scripts/
├── state.js # 全局状态变量
│ └── 定义所有全局变量secrets, scannerStream, etc.
├── auth.js # 认证逻辑
│ ├── showLoginModal()
│ ├── hideLoginModal()
│ ├── checkAuth()
│ ├── refreshAuthToken()
│ └── authenticatedFetch()
├── core.js # 核心业务逻辑 (最大模块106KB)
│ ├── loadSecrets()
│ ├── renderSecrets()
│ ├── handleAddSecret()
│ ├── handleEditSecret()
│ ├── handleDeleteSecret()
│ ├── startOTPCountdown()
│ ├── showQRCodeModal()
│ ├── startQRScanner()
│ ├── handleBatchImport()
│ └── ... (50+ 个函数)
├── utils.js # 工具函数
│ ├── showCenterToast()
│ ├── copyToClipboard()
│ ├── validateBase32()
│ ├── generateQRCodeDataURL()
│ ├── waitForQRCodeLibrary()
│ ├── formatOTPAuthURL()
│ └── ... (20+ 个工具函数)
└── pwa.js # PWA 功能
├── Service Worker 注册
├── PWA 模式检测
└── 更新检查
```
**模块加载流程**:
```
page.js → scripts/index.js
├─ import state.js (全局变量初始化)
├─ import auth.js (认证函数定义)
├─ import core.js (核心业务逻辑)
├─ import utils.js (工具函数)
└─ import pwa.js (PWA 功能)
inline <script>
页面加载完成执行
```
### 模块化 CSS (4 个模块)
```
styles/
├── base.js # 基础样式
│ ├── * { box-sizing, margin, padding }
│ ├── body { font, background }
│ ├── .container
│ ├── .header
│ └── .theme-toggle
├── components.js # 组件样式
│ ├── .secret-card
│ ├── .otp-preview
│ ├── .progress-bar
│ ├── .action-menu
│ └── .search-bar
├── modals.js # 模态框样式
│ ├── .modal
│ ├── .modal-content
│ ├── .modal-header
│ ├── .form-group
│ └── .btn-*
└── responsive.js # 响应式样式
├── @media (max-width: 480px)
├── @media (min-width: 481px)
└── @media (min-width: 1200px)
```
**样式加载流程**:
```
page.js → styles/index.js
├─ import base.js
├─ import components.js
├─ import modals.js
└─ import responsive.js
合并为单个 <style> 标签
inline 到 HTML
```
### PWA 架构
#### Service Worker 缓存策略
```
Service Worker (sw.js)
├── Static Cache (v2)
│ ├── / (主页面)
│ ├── /manifest.json
│ ├── /icon-192.png
│ └── /icon-512.png
├── CDN Cache (白名单)
│ ├── jsQR.min.js
│ └── qrcode.min.js
└── Runtime Cache
└── API 响应(不缓存)
```
**缓存策略**:
- **静态资源**: Cache First (缓存优先)
- **CDN 资源**: Cache First + CORS 处理
- **API 请求**: Network Only (始终从网络获取)
- **其他外部资源**: Network Only + 静默失败
**更新机制**:
```javascript
// 1. Service Worker 安装
self.addEventListener('install', (event) => {
// 预缓存静态资源
event.waitUntil(
caches.open(CACHE_NAME).then((cache) => {
return cache.addAll(STATIC_RESOURCES);
}),
);
self.skipWaiting(); // 立即激活
});
// 2. Service Worker 激活
self.addEventListener('activate', (event) => {
// 清理旧缓存
event.waitUntil(
caches.keys().then((names) => {
return Promise.all(names.filter((name) => name !== CACHE_NAME && name !== RUNTIME_CACHE).map((name) => caches.delete(name)));
}),
);
self.clients.claim(); // 立即控制页面
});
// 3. 拦截请求
self.addEventListener('fetch', (event) => {
const url = new URL(event.request.url);
if (url.origin === location.origin) {
// 本地资源:缓存优先
event.respondWith(
caches.match(event.request).then((response) => {
return response || fetch(event.request);
}),
);
} else if (isCDNLibrary(url)) {
// CDN 资源:缓存优先 + CORS
event.respondWith(
caches.match(event.request).then((response) => {
return response || fetch(event.request, { mode: 'cors' });
}),
);
} else {
// 其他资源:直接透传
event.respondWith(fetch(event.request));
}
});
```
---
## 设计模式
### 1. 模块化设计 (Modular Design)
**原则**: 每个模块负责单一职责
```
✅ 好的模块设计:
- auth.js: 只处理认证相关逻辑
- encryption.js: 只处理加密解密
- backup.js: 只处理备份逻辑
❌ 不好的设计:
- utils.js: 混杂了认证、加密、备份等所有功能
```
### 2. 依赖注入 (Dependency Injection)
**应用**: 环境变量 (`env`) 通过参数传递
```javascript
// ✅ 好的设计
export async function handleGetSecrets(env) {
const logger = getLogger(env); // 注入依赖
const data = await env.SECRETS_KV.get('secrets');
}
// ❌ 不好的设计
let globalEnv;
export function initEnv(env) {
globalEnv = env;
}
export async function handleGetSecrets() {
const data = await globalEnv.SECRETS_KV.get('secrets');
}
```
### 3. 工厂模式 (Factory Pattern)
**应用**: 创建响应对象
```javascript
// 工厂函数
export function createJsonResponse(data, status = 200) {
return new Response(JSON.stringify(data), {
status,
headers: {
'Content-Type': 'application/json',
...getCORSHeaders(),
},
});
}
// 使用
return createJsonResponse({ success: true, data: secrets });
```
### 4. 策略模式 (Strategy Pattern)
**应用**: OTP 算法选择
```javascript
// 策略接口
const OTP_STRATEGIES = {
totp: generateTOTP,
hotp: generateHOTP,
};
// 使用策略
export async function generateOTP(secret, type = 'totp', options = {}) {
const strategy = OTP_STRATEGIES[type];
if (!strategy) {
throw new Error(`Unsupported OTP type: ${type}`);
}
return await strategy(secret, options);
}
```
### 5. 装饰器模式 (Decorator Pattern)
**应用**: 性能监控包装
```javascript
// 装饰器
function withPerformanceLogging(fn, name) {
return async function (...args) {
const timer = new PerformanceTimer(name, logger);
try {
const result = await fn(...args);
timer.end({ success: true });
return result;
} catch (error) {
timer.cancel();
throw error;
}
};
}
// 使用
const handleGetSecrets = withPerformanceLogging(async (env) => {
// 原始逻辑
}, 'GetSecrets');
```
### 6. 中间件模式 (Middleware Pattern)
**应用**: 请求处理流水线
```
请求 → CORS中间件 → 认证中间件 → 限流中间件 → 路由处理 → 响应
```
```javascript
// 中间件链
async function handleRequest(request, env) {
// 中间件 1: CORS
const corsResponse = handleCORS(request);
if (corsResponse) return corsResponse;
// 中间件 2: 认证
if (requiresAuth(pathname)) {
const isAuthorized = await verifyAuth(request, env);
if (!isAuthorized) return createUnauthorizedResponse();
}
// 中间件 3: 限流
const rateLimitResult = await checkRateLimit(request, env);
if (!rateLimitResult.allowed) {
return createRateLimitResponse(rateLimitResult);
}
// 最终处理
return await routeRequest(pathname, method, request, env);
}
```
### 7. 观察者模式 (Observer Pattern)
**应用**: 数据变更 → 备份触发
```javascript
// 主题 (Subject)
async function saveSecretsToKV(env, secrets, reason) {
// 保存数据
await env.SECRETS_KV.put('secrets', encrypted);
// 通知观察者
await triggerBackup(secrets, env, { reason }); // 观察者
}
// 观察者 (Observer)
export async function triggerBackup(secrets, env, options) {
// 响应数据变更事件
await backupManager.executeBackup(secrets, options.reason);
}
```
### 8. 单例模式 (Singleton Pattern)
**应用**: 备份管理器、监控系统
```javascript
// 单例模式
let backupManagerInstance = null;
export function getBackupManager(env) {
if (!backupManagerInstance) {
backupManagerInstance = new BackupManager(env);
}
return backupManagerInstance;
}
```
---
## 性能优化
### 1. 代码优化
- **模块化**: 拆分为小模块,便于维护和缓存
- **懒加载**: Service Worker 按需缓存资源
- **最小化**: 减少不必要的计算和内存使用
### 2. 缓存策略
- **静态资源**: 长期缓存PWA
- **CDN 资源**: 缓存优先策略
- **API 响应**: 不缓存(实时数据)
### 3. 数据库优化
- **批量操作**: 一次性读取和写入
- **数据压缩**: 使用加密同时压缩数据
- **索引优化**: 使用有意义的 KV key
### 4. 网络优化
- **全球 CDN**: Cloudflare Edge Network
- **HTTP/2**: 多路复用
- **压缩**: Gzip/Brotli 自动压缩
---
## 安全架构
### 多层安全防护
```
┌────────────────────────────────────────┐
│ 1. Cloudflare Edge 层 │
│ - DDoS 防护 │
│ - WAF (Web Application Firewall) │
│ - Bot 管理 │
└────────────┬───────────────────────────┘
┌────────────▼───────────────────────────┐
│ 2. 应用层安全 │
│ - HttpOnly Cookie 认证 │
│ - CORS 白名单 │
│ - CSP Header │
│ - Rate Limiting │
└────────────┬───────────────────────────┘
┌────────────▼───────────────────────────┐
│ 3. 数据层安全 │
│ - AES-GCM 256 位加密 │
│ - Cloudflare Secrets 存储密钥 │
│ - 加密备份 │
└────────────┬───────────────────────────┘
┌────────────▼───────────────────────────┐
│ 4. 监控和审计 │
│ - 结构化日志 │
│ - 错误追踪 │
│ - 性能监控 │
└────────────────────────────────────────┘
```
---
## 扩展性设计
### 水平扩展
- ✅ 无状态设计:每个请求独立处理
- ✅ 全球分布:自动在边缘节点运行
- ✅ 自动扩缩容:根据流量自动调整
### 功能扩展
- ✅ 插件式架构:新功能作为独立模块添加
- ✅ 策略模式:易于添加新的 OTP 算法
- ✅ 中间件模式:易于添加新的请求处理逻辑
---
## 总结
2FA 采用现代化的无服务器架构,具有以下特点:
| 特性 | 说明 |
| ---------- | ------------------------------------ |
| **高性能** | 全球 CDN + 边缘计算,平均响应 < 50ms |
| **高可用** | 99.99% SLA自动故障转移 |
| **高安全** | 多层安全防护 + AES-256 加密 |
| **易维护** | 模块化设计 + 完整监控 |
| **易扩展** | 无状态 + 插件式架构 |
| **低成本** | 按需计费 + 免费额度 |
---
**相关文档**:
- [部署指南](DEPLOYMENT.md) - 如何部署应用
- [API 参考](API_REFERENCE.md) - API 端点文档
- [功能文档](features/) - 各功能模块详解
---