原理文档
技术架构、数据模型、分片上传会话、鉴权与安全设计。原生客户端:iOS「文链(手机端)」、macOS「DocLinks」。
总体架构
D1 存元数据、用户、上传 pending 会话、速率限制;R2 存二进制;Worker 编排鉴权与读写。docs/ 经 [assets] 与 Worker 同部署。客户端 App Store 名称:iOS 文链(手机端),macOS DocLinks(详见 原生客户端)。
技术栈
| 层级 | 技术 | 用途 |
|---|---|---|
| 运行时 | Cloudflare Workers | 边缘 HTTP |
| 框架 | Hono 4.x | 路由、中间件 |
| 数据库 | D1 (SQLite) | 12 版迁移 schema |
| 存储 | R2 + Multipart API | 对象与分片上传 |
| 直传 | aws4fetch | S3 预签名 PUT |
| 管理台 | Tailwind CDN + 原生 JS | SPA,Cookie / Bearer 会话 |
| iOS | SwiftUI · App Store 名 文链(手机端) | 钥匙串 Bearer,与 /files API 对齐 |
| macOS | SwiftUI · App Store 名 DocLinks(菜单栏) | 共用 iOS 业务代码,App Sandbox |
| 静态文档 | wrangler assets | docs/ → /*.html |
代码结构
src/
├── index.ts
├── types.ts # Env, AppVariables
├── middleware/auth.ts # Cookie/Bearer 鉴权
├── routes/ # public, api, admin, files
├── controllers/
│ ├── fileController.ts # HTML + auth/login/logout/token/me
│ ├── shareController.ts # 分享 + 分片 API + 公开访问
│ ├── userController.ts # 用户 CRUD、注册、资料
│ ├── indexController.ts # 公开项目目录 /
│ ├── adminController.ts
│ └── filesController.ts
├── services/
│ ├── fileService.ts
│ ├── projectService.ts
│ ├── shareService.ts
│ ├── userService.ts # 注册、配额、会话
│ ├── shareMultipartUpload.ts # pending 会话、abort、complete
│ ├── r2PresignedUpload.ts
│ └── adminTokenService.ts
├── utils/
│ ├── adminAuth.ts # Cookie、Token 哈希
│ ├── accessPassword.ts # v2 加盐密码
│ ├── security.ts # escapeHtml、CSP、timingSafeEqual
│ ├── rateLimit.ts
│ ├── htmlInjector.ts
│ └── contentDisposition.ts
└── views/
├── adminPage.ts
├── filesPage.ts
├── indexPage.ts
└── shareViewPage.ts 等
数据库设计
核心业务表
| 表 | 用途 |
|---|---|
files | HTML 托管文件元数据(slug 唯一) |
projects | 项目备注、access_password_hash |
share_groups | 分享组:备注、密码、过期、status、user_id |
shared_file_items | 组内文件,UNIQUE(group_id, relative_path) |
users | 成员/管理员:配额、用量、资料、密码哈希 |
user_sessions | 成员 Bearer 会话(token_hash、过期) |
系统表(0006–0012)
| 表 / 配置 | 字段要点 | 用途 |
|---|---|---|
app_settings | admin_token、default_user_quota_bytes | Token 哈希;默认注册配额 50 MB |
pending_share_uploads | upload_id, storage_key, part_count, client_path | 分片上传会话,complete 校验 |
rate_limits | key, count, window_start | IP 速率限制 |
Admin 凭证解析:resolveAdminCredentialHash() → D1 哈希 → 回退 SHA-256(env.ADMIN_TOKEN)。
R2 存储结构
| 类型 | Object Key 格式 |
|---|---|
| HTML 托管 | files/{slug} |
| 文链(DocLinks) | shared/{groupId}/{relativePath} |
分享上传使用 R2 Multipart Upload;complete 调用 multipart.complete(parts)。
HTML 上传流程
- 管理台 XHR
POST /api/upload(withCredentials,30min 超时) authMiddleware验证 Cookie 或 Bearer- 构建 slug,冲突 409,R2 put + D1 upsert
文链(DocLinks)分片上传流程
- init:创建
offlineshare_group → 每文件createMultipartUpload→ 写入pending_share_uploads(含 clientPath) - upload:直传(part-url + PUT R2)或代理(chunk 经 Worker);part-url/chunk 校验 pending 会话
- complete:对照 pending 校验 uploadId、分片数 → R2 complete → D1 batch 写 items + 置 online + 删 pending
- abort:对每个 uploadId 调用
multipart.abort(),删 R2 前缀对象与 D1 记录 - init/上传失败时前端或服务端触发 abort,避免 R2 幽灵 multipart
限制:≤500 文件,总大小 ≤50GB,路径 sanitize 后不可重复。上传前校验成员 quota_bytes。
HTML 公开访问
GET /p/{path}→ resolvePublicSlug → 必要时 302- 检查 online、项目密码 Cookie(timingSafeEqual)
- R2 get → HTML 注入 base + viewport
分享公开访问
GET /f/{id}— 过期/下线/密码检查GET /f/{id}/{path}— Range 视频流,RFC 5987 下载名- 密码 Cookie:
SHA-256(share:id:hash:adminSecret)
路径重定向(HTML)
鉴权机制
管理员
| 环节 | 实现 |
|---|---|
| 登录 | POST /api/auth/login + Admin Token → Cookie htmls_admin_session 或 Bearer |
| API 鉴权 | Cookie / Bearer → SHA-256 与 D1/env 哈希比对 |
| 在线轮换 | PATCH /api/auth/token → D1 存新哈希 |
| 限流 | login 10次/5min,check 30次/min(按 IP) |
成员用户
| 环节 | 实现 |
|---|---|
| 注册 | POST /api/auth/register → 默认 50 MB 配额 |
| 登录 | 密码验证 → user_sessions + Cookie / Bearer |
| 注销 | DELETE /api/auth/account → 删除分享(含 R2)→ 删除用户与会话 |
| 分享归属 | share_groups.user_id 关联创建者 |
| 配额 | 上传前 assertUserQuota,超额 413 |
访客密码
- 新密码:
v2:{salt}:{SHA-256(salt:password)} - 旧密码:纯 SHA-256(password),verify 时自动兼容
- 解锁限流:10 次/5min/IP
静态文档(assets)
仓库 docs/ 目录在 wrangler.toml 中配置为静态资源,与 Worker 同部署:
| URL | 文件 | 用途 |
|---|---|---|
/privacy-policy.html | privacy-policy.html | App Store 隐私政策 |
/ios-app-guide.html | ios-app-guide.html | iOS App Store 支持 URL |
/mac-app-guide.html | mac-app-guide.html | Mac App Store 支持 URL |
/index.html | index.html | 文档中心 |
/user-guide.html | user-guide.html | 使用手册 |
/architecture.html | architecture.html | 原理文档 |
run_worker_first 将 /、/api/*、/htmls、/files、/p/*、/f/* 优先交给 Worker;其余 *.html 由 assets 直接返回。iOS / Mac 应用内链接为 {serverURL}/{path}。根页面 / 页脚亦链接至 /index.html。
原生客户端
两客户端共用 Swift 业务层与 REST API;App Store 名称与 Web 品牌「文链 · DocLinks」区分如下:
| 平台 | App Store 名称 | 版本 | Bundle ID | 代码路径 | 文档 |
|---|---|---|---|---|---|
| iOS 17+ | 文链(手机端) | 1.0.2 | com.chengyusheng.docsshare.upload | ios/ShareUpload/ | ios-app-guide.html |
| macOS 14+ | DocLinks | 1.0.1 | com.chengyusheng.doclinks-mac | mac/DocLinksMac/ | mac-app-guide.html |
Bearer Token 存钥匙串;与 Web /files 调用相同 API。应用免费、无 IAP/订阅;账户删除走 DELETE /api/auth/account,服务端级联删除 R2 对象。
iOS 端附加行为(1.0.2+)
- 分享页 2×2 图标操作网格(与 Mac 共用
ShareRow组件) - 前台
isIdleTimerDisabled— 上传期间防锁屏 UIUserInterfaceStyle = Light— 固定浅色界面
HTML 自动增强
htmlInjector.ts 注入 <base target="_blank"> 与 viewport(缺失时)。
安全考量
| 威胁 | 对策 |
|---|---|
| XSS 窃取 Token | HttpOnly Cookie;管理台 escapeHtml;CSP 头 |
| Token 撞库 | auth/check、login 限流;恒定时间比较 |
| 密码暴力破解 | unlock 限流;v2 加盐哈希 |
| D1 泄露 | Admin Token 仅存哈希;密码存哈希 |
| 上传滥用 | 文件数/总大小上限;pending 会话校验 |
| R2 幽灵 multipart | abort 调用 multipart.abort();失败自动清理 |
| 错误信息泄露 | 500 不返回内部 detail |
| 直传越权 | part-url 校验 pending uploadId + storage_key |
| 成员越权 | 分享 API 按 user_id 过滤;管理员可见全部 |
| 配额滥用 | 注册默认 50 MB;上传前校验 used_bytes |
wrangler.toml 绑定
[[d1_databases]]
binding = "DB"
database_name = "htmls-lib-db"
[[r2_buckets]]
binding = "MY_BUCKET"
bucket_name = "htmls-lib-bucket"
[vars]
R2_BUCKET_NAME = "htmls-lib-bucket"
[assets]
directory = "./docs"
html_handling = "none"
run_worker_first = ["/", "/api/*", "/htmls", "/htmls/*", "/files", "/files/*", "/p/*", "/f/*", "/admin"]
# Secrets: ADMIN_TOKEN, R2_ACCOUNT_ID, R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY