8.9 KiB
8.9 KiB
yDropbox — 文件收发站设计规格
日期: 2026-08-26 状态: 已用户批准 项目名:
yoresee_dropbox(Go 模块yoresee_dropbox,部署二进制yDropbox)
1. 概述
一个自托管单用户文件收发站:用户通过浏览器上传/下载文件,无需 SSH。服务器上的本地程序(自动化脚本、CLI 工具等)通过共享文件系统目录与 Web 端交换文件:
- 用户 Web 上传 → 落入
workspace/inbox/,供本地程序消费。 - 本地程序产出 → 写入
workspace/outbox/,用户在页面下载。 - 支持生成公开分享链接(可选密码、可选有效期)。
目标
- 单 Go 二进制 +
go:embed内嵌前端,零外部依赖。 - 上传、列表、下载、删除、分享链接全流程可用。
- 服务器本地程序零 API 依赖(直接读写目录即可集成)。
非目标
- 不做多用户/权限体系。
- 不做断点续传、分块上传。
- 不做在线预览/编辑。
2. 架构
- 进程: 单个 Go 二进制
yDropbox,HTTP 服务默认监听127.0.0.1:8999(仅本机回环),--addr可配。 - 对外: 反向代理服务器上由 nginx 将 HTTPS 子域名反代至该端口;证书由 certbot 签发续期。
- 文件存储: 文件系统两目录 ——
workspace/inbox/、workspace/outbox/。本地程序与 Web 服务共享此目录。 - 元数据: SQLite(
modernc.org/sqlite,纯 Go 免 CGO),存文件记录、分享链接、密码哈希、有效期。数据库文件位于workspace/.ydropbox/db.sqlite。 - 前端: Alpine.js(vendor 单文件
alpine.min.js随仓库提交,go:embed内嵌),声明式响应式绑定(x-data/x-for/x-show),无构建链;不引 CDN,保持零外部依赖。
3. API 接口定义
后端用 Go 标准库 net/http + ServeMux(Go 1.22+ 方法+路径模式),不引第三方路由。所有 API 返回 JSON(Content-Type: application/json);上传与分享下载除外。
| 路由 | 方法 | 请求 | 成功响应 | 失败 |
|---|---|---|---|---|
/login |
GET | — | 登录页 | — |
/api/login |
POST | {"token":"..."} |
200 {} + Set-Cookie |
401 |
/api/logout |
POST | — | 200 {} 清 cookie |
401 |
/api/upload |
POST | multipart file=@... |
201 {"file":{...}} |
401/413/400 |
/api/files |
GET | ?dir=inbox|outbox 可选过滤 |
200 {"files":[...]} |
401 |
/api/files/{id}/download |
GET | — | 文件流(Content-Disposition 原始名) | 401/404 |
/api/files/{id} |
DELETE | — | 204 |
401/404 |
/api/files/{id}/share |
POST | {"password":"可选","expires_in_hours":N 或 "expires_at":ts,均可省略} |
201 {"url":"/s/<token>",...} |
401/404/400 |
/api/shares |
GET | — | 200 {"shares":[...]} 活跃分享列表 |
401 |
/api/shares/{id} |
DELETE | — | 204 撤销分享 |
401/404 |
/s/{token} |
GET | 密码时先 POST /s/{token} {"password":"..."} |
文件流 / 密码页 | 403/410/404 |
file 对象结构(列表与上传返回统一):
{"id":"uuid","name":"report.pdf","dir":"inbox","size":1048576,"created_at":1750000000}
说明
- 未登录访问页面类路由 → 302
/login;未登录访问/api/*→ 401 JSON。 - 上传限 100MB,超限 413。
- 分享有密码且未验证时,GET 返回密码输入页;密码校验通过后签发短期 share cookie 再放行下载流。
GET /api/shares用于页面管理已创建的分享(撤销入口依赖此列表能力)。
4. 数据模型
SQLite 两张表:
-- 文件记录
CREATE TABLE files (
id TEXT PRIMARY KEY, -- UUIDv4
original_name TEXT NOT NULL, -- 用户原始文件名(展示/下载用)
storage_name TEXT NOT NULL UNIQUE, -- 落盘名 = UUID(无扩展名)
dir TEXT NOT NULL CHECK (dir IN ('inbox','outbox')),
size INTEGER NOT NULL,
created_at INTEGER NOT NULL -- Unix 秒
);
-- 分享链接
CREATE TABLE shares (
id TEXT PRIMARY KEY, -- UUIDv4
file_id TEXT NOT NULL REFERENCES files(id) ON DELETE CASCADE,
token TEXT NOT NULL UNIQUE, -- crypto/rand 24 字节 → base64url
password_hash TEXT, -- bcrypt;NULL = 无密码
expires_at INTEGER, -- Unix 秒;NULL = 永不过期
created_at INTEGER NOT NULL,
last_accessed_at INTEGER -- 可选审计字段
);
设计要点
- 存储与元数据分离: 落盘文件名用纯 UUID,杜绝 path traversal 与重名冲突;原始文件名只存库,下载时写入
Content-Disposition,MIME 类型按original_name扩展名推断(默认application/octet-stream)。 - token 强度: 24 字节
crypto/rand→ base64url(32 字符),不可枚举;密码 bcrypt cost 10。 - 有效期语义: 未设置则长期有效;访问时校验
expires_at,过期返回 410 并删除该分享记录。 - 级联删除: 文件删除时其所有分享随
ON DELETE CASCADE失效。 - 目录同步: 后台 goroutine 每 10s 扫描
inbox/与outbox/两个目录(对称扫描——本地程序手动放进 inbox 的文件也能出现在页面),以磁盘为准做增删对账:新文件入库、已消失的文件删记录(级联撤销分享)。
5. 鉴权与安全
鉴权模型
- 登录凭证: 启动时传入访问 token ——
yDropbox listen --token=<T>或环境变量YDROPBOX_TOKEN。token 仅存在内存,用于/api/login单次校验。 - session: 校验通过后签发 httpOnly cookie(名
ydropbox_session,值 = 32 字节随机 id),session 映射存内存,进程重启即全部失效;属性HttpOnly; SameSite=Lax; Secure(Secure 由反代 HTTPS 场景决定)。 - session 过期: 默认 7 天滑动过期,登出即删。
- 分享密码:
POST /s/{token}校验 bcrypt 后,签发独立短期 cookie(仅绑定该 share id,有效期 1h),后续 GET 凭此放行下载流。
安全措施
| 威胁 | 对策 |
|---|---|
| 路径穿越 | 落盘/读取一律用 UUID 文件名,用户输入的文件名永不参与路径拼接 |
| 分享枚举 | 24 字节 CSPRNG token |
| 密码泄露 | bcrypt 哈希存储,日志与响应永不回显 |
| CSRF | API 全部要求 JSON Content-Type + SameSite=Lax cookie;上传 multipart 为例外但需 session cookie 且由同源页面发起 |
| 暴力破解 | /api/login 与分享密码校验均加固定延迟 + 连续 5 次失败锁定 60s(内存计数) |
| 大文件打爆内存 | http.MaxBytesReader 100MB 流式落盘;nginx 侧 client_max_body_size 100m 双保险 |
| 敏感头泄露 | 下载响应带 X-Content-Type-Options: nosniff;分享下载带 Content-Disposition: attachment |
错误码约定: 401 未登录 / 403 密码错或锁定 / 404 不存在 / 410 分享过期 / 413 超限。
6. 测试
单元测试(标准库 testing + httptest)
- store 层: 文件 CRUD、分享创建/撤销/级联失效、有效期判断(永不过期/时长到期/指定时刻过期)、bcrypt 校验。
- handler 层: 登录成功/失败与锁定、未登录 401、页面 302、上传 201 与超限 413、下载 Content-Disposition 正确性、删除 204 与分享级联、分享密码错 403、过期 410、scanner 对账逻辑(临时目录模拟新增/消失)。
集成测试
- 本地起服务,脚本走全链路:登录 → 上传 → 列表 → 下载 → 建分享 → 无密码公开下载 → 撤销后 404。
- 部署后浏览器人工走查一遍 HTTPS 全流程。
7. 部署
- 构建:
CGO_ENABLED=0 go build -o yDropbox .,纯静态单文件。 - 运行: systemd 服务,
ExecStart=/usr/local/bin/yDropbox listen --addr=127.0.0.1:8999,YDROPBOX_TOKEN经 systemd 环境或 credentials 注入;数据目录指向服务器上固定路径(如/var/lib/ydropbox/workspace,--workspace可配)。 - 反代: nginx 站点将 HTTPS 子域名反代至
127.0.0.1:8999,配置client_max_body_size 100m,certbot 签发续期证书。 - DNS: 子域名 A 记录指向反代服务器公网 IP(具体域名/IP 由部署时确定,不入仓库文档)。
8. 目录结构
yoresee_dropbox/
├── main.go # 入口: flag/env 解析、路由注册、启动 scanner
├── go.mod / go.sum
├── internal/
│ ├── server/
│ │ ├── auth.go # 登录/session/锁定
│ │ ├── files.go # 上传/列表/下载/删除
│ │ └── share.go # 分享创建/撤销/公开访问
│ ├── store/store.go # SQLite 层 (modernc.org/sqlite)
│ └── scanner/scanner.go # inbox+outbox 目录对账
├── web/ # go:embed:index.html, style.css, app.js, alpine.min.js(vendor)
└── workspace/ # 运行期生成(gitignore): inbox/, outbox/, .ydropbox/db.sqlite