feat: 文件快传新增共享剪贴板(跨客户端粘贴、查看与一键复制)

- 后端:新增 /api/clips(列表/新建)、/api/clips/<id>(取全文/删除)、/api/clips/<id>/copy(复制计数),列表仅返回 240 字符预览
- 存储:SQLite 新增 clips 表(含 expires_at 索引),启动自动建表,线上老库无需手工迁移
- 前端:剪贴板卡片支持读取本机剪贴板、来源备注、查看全文、一键复制到本机剪贴板、单条删除与复制次数
- 清理:systemd 定时任务与 /api/cleanup 同时清理过期剪贴板
- 配置:新增 max_clip_length / max_clips / clip_default_expires_hours
- 部署:deploy_transfer.py 新增 --keep-credentials(不重置登录密码与会话密钥)与 SSH 连接重试
- 文档:README / setup.md / CHANGELOG 同步更新
This commit is contained in:
2026-10-11 22:20:54 +08:00
parent 67caaa428e
commit 5af69d694f
9 changed files with 773 additions and 45 deletions
+24 -3
View File
@@ -271,9 +271,10 @@ echo | openssl s_client -connect lzwlab.cn:443 -servername lzwlab.cn -alpn h2,ht
- 后端:Flask 3.0.2 + gunicorn 20.1(apt 安装),监听 `127.0.0.1:8090`,systemd 服务 `lzwlab-transfer.service`。
- 前端:单页 HTML(内嵌 CSS/JS),PC 与手机自适应,登录后可用。
- 存储:上传文件保存在 `/var/lib/lzwlab-transfer/files/`,元数据在 SQLite `/var/lib/lzwlab-transfer/files.db`。
- 存储:上传文件保存在 `/var/lib/lzwlab-transfer/files/`,元数据在 SQLite `/var/lib/lzwlab-transfer/files.db`;共享剪贴板为纯数据库存储,同一库中的 `clips` 表(启动时自动建表,老库无需手工迁移)。
- 共享剪贴板:与文件快传同一页面,登录用户共用一块剪贴板——任一客户端粘贴文本发送,其他客户端列表可见、可“一键复制”写入本机剪贴板;接口为 `/api/clips`(GET 列表 / POST 新建)、`/api/clips/<id>`(GET 全文 / DELETE 删除)、`/api/clips/<id>/copy`(POST 复制计数)。列表只返回前 240 字符预览,全文按需获取;有效期默认 1 天(可选 1 小时~30 天),单条上限 262144 字符(`max_clip_length`),最多保留 200 条(`max_clips`)。
- 配置:`/etc/lzwlab-transfer/config.json`(root:lzwlab 640),登录密码只保存 scrypt 哈希,明文经部署环境变量传入。
- 清理:`lzwlab-transfer-cleanup.timer` 每 10 分钟执行 `cleanup.py` 删除过期文件;访问列表/手动清理时也会触发检查。
- 清理:`lzwlab-transfer-cleanup.timer` 每 10 分钟执行 `cleanup.py` 删除过期文件与过期剪贴板;访问列表/手动清理时也会触发检查。
- nginx:`/transfer/` 反向代理到 8090,`proxy_request_buffering off` + `proxy_buffering off` 保证大文件上传下载流式传输,`client_max_body_size 5120m`。
### 8.2 部署命令(本地运维机)
@@ -281,9 +282,13 @@ echo | openssl s_client -connect lzwlab.cn:443 -servername lzwlab.cn -alpn h2,ht
```bash
# 依赖脚本见仓库 deploy_transfer.py;密码经环境变量传入,不会写入仓库
LZWLAB_APP_PASSWORD='$PASS' python deploy_transfer.py --username admin
# 只更新代码(例如新增功能后上线):保留服务器上已有的 secret_key 与密码哈希,
# 不重置登录密码,也无需提供密码
python deploy_transfer.py --keep-credentials
```
脚本会依次完成:apt 安装依赖 → 创建系统用户 `lzwlab` 与目录 → 上传/安装应用、配置、systemd 单元 → 启动服务与清理定时器 → 校验并 reload nginx。重复执行幂等。
脚本会依次完成:apt 安装依赖 → 创建系统用户 `lzwlab` 与目录 → 上传/安装应用、配置、systemd 单元 → 启动服务与清理定时器 → 校验并 reload nginx。重复执行幂等;加 `--skip-apt` 可跳过依赖安装(依赖已装好时更快)。
### 8.3 验证结果
@@ -295,11 +300,27 @@ curl -s -o /dev/null -w "%{http_code} -> %{redirect_url}\n" http://lzwlab.cn/tra
公网 API 实测:登录成功返回会话 Cookie;未登录访问 `/api/files` 返回 401;上传 25 字节测试文件后列表可查,下载返回 200 且支持 `Range`(206);删除后列表为空;将测试文件过期时间强制置为过去并启动清理服务后,日志输出“清理完成:过期文件 1 个”,列表随即清空。
共享剪贴板(2026-10-11 上线)实测:
```bash
# 未登录访问剪贴板接口(路由存在即返回 401,缺路由会是 404)
curl -s -o /dev/null -w "%{http_code}\n" https://lzwlab.cn/transfer/api/clips # 401
```
- 接口自测(服务器 `/tmp` 下用 Flask `test_client` 跑,不影响线上服务):58 项全通过,覆盖未登录 401、写入校验(缺 `X-Requested-With`、非字符串、空内容、超长、有效期越界、非 JSON)、正文换行归一化与 NUL 清理、中文/emoji 往返、列表预览不返回全文、复制计数、删除与 404、过期清理、条数上限裁剪,以及“老库(只有 files 表)升级后自动建 clips 表且原记录保留”的迁移场景。
- 存储层自测:26 项全通过(`clips` 表增删改查、预览截断、字符数统计、同秒插入排序、过期清理、条数裁剪,以及原文件表功能回归)。
- 线上端到端(走 `https://lzwlab.cn/transfer/` 真实链路 nginx + gunicorn + 线上配置与数据库):原密码登录成功(说明 `--keep-credentials` 未重置密码)、发送含中文与 emoji 的多行文本返回 200、列表可见且预览/字符数/来源备注正确、取全文一致、复制计数累计为 1、删除后列表不再出现、`/api/cleanup` 返回新增的 `clips` 字段;验证数据已删除。
- 数据库确认:`files.db` 中已存在 `clips` 表与 `idx_clips_expires_at` 索引;`journalctl -u lzwlab-transfer-cleanup` 输出“清理完成:过期文件 0 个,孤儿缓存 0 个,过期剪贴板 0 条”,即定时任务已使用新代码。
### 8.4 踩坑
- `deploy_transfer.py` 在 Windows 本地生成远端路径时不能使用 `os.path.join`(会把 `/` 变成 `\`),SQLite 路径必须手工拼接 Linux 路径,否则服务报 `unable to open database file`。
- gunicorn 手动排障时不要在 SSH 会话里前台运行(会话断开会留下占端口的孤儿进程),应直接通过 `systemctl` 管理;排障后可 `sudo pkill -u lzwlab -f gunicorn && sudo systemctl restart lzwlab-transfer.service`。
- Windows 下 curl 的 `-F 'file=@...;filename=中文名'` 对非 ASCII 文件名支持不佳,测试时用 ASCII 文件名,浏览器上传不受影响。
- 本机(Windows)装不上 Flask 时,接口自测可以直接在服务器 `/tmp` 里跑:把 `transfer/*.py`、`templates/index.html` 与测试脚本传到 `/tmp/<dir>`,用 `LZWLAB_TRANSFER_CONFIG` 指向一份临时配置,再跑 Flask `test_client`,既能验证真实 Python 版本又不碰线上服务。
- `test_client` 用例里必须把临时配置的 `cookie_path` 设为 `/`:线上会话 Cookie 的 Path 是 `/transfer/`(浏览器侧路径),而测试客户端直接请求 `/api/*`,否则登录后仍会因带不上 Cookie 而全部 401。
- `auth.DEFAULT_CONFIG_PATH` 在 `import auth` 时读取 `LZWLAB_TRANSFER_CONFIG`,所以测试脚本必须**先**设置该环境变量再 `import auth`/`import app`,否则会去读线上的 `/etc/lzwlab-transfer/config.json` 而报权限错误。
- 部署脚本若不保留凭据(不加 `--keep-credentials`),每次部署都会重建 `secret_key` 与密码哈希:既有会话失效、登录密码变成环境变量里传入的值。
## 9. 部署 RustDesk 自建服务器(Docker)