Files
zTools2/README.md
zikai ff2ad3fcb3 feat: 所有入口统一到 /api/ 前缀
将 zTools2 托管的页面/静态/探针/WebSocket 路由全部从顶级路径迁移到 /api/ 下:
- 页面:/pdf -> /api/pdf、/pdf-admin -> /api/pdf-admin、/upload -> /api/upload、
  /files -> /api/files-page、/wb/{id} -> /api/wb-page/{id}、/wb-admin -> /api/wb-admin
  (页面类加 -page 后缀以规避同名 REST API /api/files、/api/wb/{id})
- 静态资源:/static -> /api/static
- 探针:/health -> /api/health
- WebSocket:/ws/wb/{id} -> /api/ws/wb/{id}
- 前端 HTML 壳与 JS 中的资源/页间链接/WS URL 同步更新
- 手动测试脚本 BASE_WS 同步

这样反代与 vite proxy 只需一条 /api/ 规则即可转发全部入口;
前端 iframe 用同源相对路径 /api/pdf,与环境无关,不再误打到其它环境域名。
README 新增「路由约定」说明。
2026-07-27 15:42:47 +08:00

215 lines
11 KiB
Markdown
Raw 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.

# zikai file service
基于 FastAPI 的个人 Web 服务,提供**文件上传/浏览/下载、共享记事本(实时协作)、
主机监控、SFTP 暂存、反向隧道、PDF 转换**。采用 Spring 风格分层架构,自带 API 文档。
## 功能一览
| 模块 | 页面 / 接口 | 鉴权 |
|------|------------|------|
| 文件上传 | `POST /api/files/upload`(流式)/ `POST /api/files/chunk-uploads/*`(分片+断点续传) | 公开 |
| 上传页 | `GET /api/upload`(拖拽/多文件/分片/去重) | 公开 |
| 文件浏览 | `GET /api/files-page`(多选/批量下载删除/分页) | Basic Auth |
| 文件管理 API | `GET /api/admin/files``GET/DELETE /api/admin/files/{id}``GET /api/admin/files/{id}/download` | Basic Auth |
| 共享记事本 | `GET /api/wb-page/{id}`(公开,不存在则新建) | 公开 |
| 记事本实时同步 | `WS /api/ws/wb/{id}`(心跳 3s5 次失活移除) | 公开 |
| 记事本管理 | `GET /api/wb-admin`(查看/删除) | Basic Auth |
| 记事本管理 API | `GET /api/admin/wb``DELETE /api/admin/wb/{id}` | Basic Auth |
| PDF 转换 | `GET /api/pdf`(上传 epub->PDF进度轮询下载凭 cookie 记住任务) | 公开cookie |
| PDF 转换 API | `POST /api/pdf/jobs``GET /api/pdf/jobs[/{id}]``GET /api/pdf/jobs/{id}/download``DELETE /api/pdf/jobs/{id}` | 公开cookie |
| PDF 转换管理 | `GET /api/pdf-admin`(全部任务,含已软删标记,硬删) | Basic Auth |
| PDF 转换管理 API | `GET /api/admin/pdf/jobs``DELETE /api/admin/pdf/jobs/{id}` | Basic Auth |
| 主机监控 | `GET /api/system/status`CPU/内存/磁盘HTML+JSON 内容协商) | 公开 |
| 反向隧道反代 | `ALL /api/userPort/{userName}`(经 SSH 隧道转发到 user 本地服务) | 公开 |
| SFTP/SSH | 端口 2022密码+公钥chroot 到上传目录,承载隧道转发) | SSH |
| API 文档 | `GET /docs`Swagger/ `GET /redoc` | Basic Auth |
## 路由约定
**所有 zTools2 托管的入口(页面 / 静态资源 / 健康探针 / WebSocket / REST API统一挂在 `/api/` 前缀下**,只有元信息/文档例外(`/``/docs``/redoc``/openapi.json`)。这样反向代理与 vite dev proxy 都只需一条 `/api/` 规则即可把请求转给当前环境的 zTools2前端 iframe 用**同源相对路径** `/api/pdf` 即可,与运行环境(本地 / 测试 / 生产)无关,无需区分 dev/prod 指向。
页面类入口为避免与同名 REST API 冲突,统一加 `-page` 后缀:
| 类型 | 页面入口 | REST API同名不加后缀 |
|------|---------|------------------------|
| 文件浏览 | `GET /api/files-page` | `GET /api/files``/api/files/{id}` 等 |
| 记事本 | `GET /api/wb-page/{id}` | `GET /api/wb/{id}` |
其余入口:`/api/health`(探针)、`/api/static/*`JS/CSS`/api/ws/wb/{id}`WebSocket`/api/upload``/api/pdf``/api/pdf-admin``/api/wb-admin`
## 项目结构
```
server/
├── app/
│ ├── main.py # FastAPI 应用工厂、路由注册、生命周期reaper
│ ├── config.py # 从 config.yaml 加载的类型化 Settingspydantic-settings
│ ├── database.py # SQLAlchemy 引擎/Session/Base/get_db 依赖
│ ├── security.py # Basic Authrequire_docs_auth常量时间比较
│ ├── controllers/ # 路由层(@RestControllerfile/system/chunk/tunnel/whiteboard/pdf/admin
│ ├── services/ # 业务层UploadService/ChunkUploadService/SystemService/
│ │ # WhiteboardService/WhiteboardHub/TunnelService/sftp_server
│ │ # PdfService转换编排+ pdf_converterepub->pdf 纯 Python
│ ├── dao/ # 数据访问层:唯一发 SQL 的层SQLAlchemy ORM 参数化)
│ ├── models/ # ORM 实体UploadedFile/UploadSession/Whiteboard/TunnelSession/PdfJob
│ ├── schemas/ # pydantic 请求/响应 DTO
│ ├── views/ # 服务端渲染 HTML系统状态页、上传页
│ ├── static/ # 前端静态资源common + file_browser + whiteboard + pdf + pdf_admin
│ └── scripts/init_db.py # 数据库初始化(建库建账、随机密码写回 config.yaml
├── sql/schema.sql # 建表 DDL参考实际由 ORM 自动建表)
├── config.example.yaml # 配置模板(含注释)
├── config.yaml # 实际配置git-ignored含密码
├── requirements.txt
├── setup.sh # 一次性初始化venv + 依赖 + 建库 + SFTP 密钥
├── start.sh / stop.sh # 启停 HTTP6867+ SFTP2022
└── logs/ # app.log / sftp.log
```
**请求流程**`controller → service → dao → ORM model → MySQL`
DB Session 由 `get_db` 依赖注入。前端页面走「StaticFiles 挂载 + 具名 HTML 路由」前后端分离JS 调同源 `/api/...`
## 从零安装Ubuntu 22.04+
### 1. 安装系统依赖
```bash
apt update
apt install -y python3-venv python3-pip mysql-server apache2 \
libssl-dev build-essential # build-essential 给 bcrypt/asyncssh 编译
# PDF 转换依赖 weasyprint需 pango/cairo 系统库Ubuntu 通常已随桌面环境安装,缺则补装):
apt install -y libpango-1.0-0 libpangoft2-1.0-0 libcairo2 libgdk-pixbuf-2.0-0
```
### 2. 获取代码
```bash
git clone <repo> /root/zikai
cd /root/zikai/server
```
### 3. 初始化venv + 依赖 + 建库 + SFTP 密钥)
```bash
./setup.sh
```
`setup.sh` 会:
- 创建 `.venv` 并安装 `requirements.txt`
- 复制 `config.example.yaml → config.yaml`(若不存在)
- 通过本机 root socket 建独立 MySQL 库 `zikai_filesvc` + 应用账户,随机密码写回 `config.yaml`
- 生成 SFTP 主机密钥(`keys/ssh_host_*`
### 4. 配置凭据
编辑 `config.yaml`(见下方[配置说明](#配置说明)
- `docs.username` / `docs.password`:管理页与 API 文档的 Basic Auth 凭据
- `sftp.users[].password_hash`SFTP 用户bcrypt生成方式见下
- `tunnel.users[]`:反向隧道用户(可选)
```bash
# 生成 bcrypt hash
.venv/bin/python -c "import bcrypt;print(bcrypt.hashpw(b'yourpass',bcrypt.gensalt()).decode())"
```
### 5. 启动
```bash
./start.sh # 启动 HTTP(127.0.0.1:6867) + SFTP(0.0.0.0:2022)
./stop.sh # 停止
```
### 6. 配置 Apache 反向代理
服务只绑 `127.0.0.1:6867`,通过 Apache 对外提供 HTTPS。安装模块并配置 vhost
```bash
a2enmod ssl proxy proxy_http proxy_wstunnel rewrite headers
```
创建 `/etc/apache2/sites-available/f.zikai.wang.conf`(关键部分):
```apache
<VirtualHost *:443>
ServerName f.zikai.wang
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/f.zikai.wang/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/f.zikai.wang/privkey.pem
ProxyPreserveHost On
ProxyPass /fdata !
# 所有 zTools2 入口(页面/静态/探针/WS/API统一在 /api/ 下,一条规则即可;
# WebSocket 走 /api/ws/wb/{id},靠 proxy_wstunnel 透传 Upgrade 头
ProxyPass /api/ http://127.0.0.1:6867/api/
ProxyPassReverse /api/ http://127.0.0.1:6867/api/
ProxyTimeout 300
</VirtualHost>
```
```bash
a2ensite f.zikai.wang
systemctl reload apache2
```
> **防火墙**:放开 443HTTPS与 2022SFTP。6867 不对外(仅 loopback
> **大文件上传**Apache 全局 `Timeout 300`,慢链路建议走分片上传(`/api/upload`)或 SFTP。
## 配置说明
所有运行时配置在 `config.yaml`git-ignored。完整 schema 见 `config.example.yaml`
| 段 | 关键项 | 说明 |
|----|--------|------|
| `server` | `host`/`port` | 绑定地址,保持 `127.0.0.1:6867`Apache 反代) |
| `database` | `host`/`port`/`user`/`password`/`database` | MySQL 连接;密码由 `setup.sh` 自动生成写回 |
| `storage` | `upload_dir` | 文件存储根目录(默认 `uploads` |
| | `chunk_bytes` | 流式上传分片大小(默认 1 MiB |
| | `chunk_session_dir` | 分片会话暂存目录(默认 `uploads/.work` |
| | `chunk_session_ttl_seconds` | 被放弃会话存活秒数(默认 300 |
| `docs` | `username`/`password` | `/docs``/api/files-page``/api/wb-admin``/api/pdf-admin``/api/admin/*` 的 Basic Auth明文常量时间比较 |
| `sftp` | `enabled`/`host`/`port` | SFTP 服务,默认 `0.0.0.0:2022` |
| | `users[].username`/`password_hash` | SFTP 用户bcrypt |
| | `host_key_path`/`authorized_keys_path` | 主机密钥与公钥白名单路径 |
| `tunnel` | `enabled`/`users[]` | 反向隧道:`username`/`password_hash`/`tunnel_port`/`local_port` |
| `whiteboard` | `heartbeat_interval_seconds` | 心跳间隔(默认 3s |
| | `heartbeat_miss_threshold` | 失活阈值(默认 5 次 = 15s |
| | `max_board_id_length` | board_id 长度上限(默认 64 |
| | `max_connections_per_board` | 单白板并发连接上限(默认 50 |
| | `list_limit` | 管理页单次列表上限(默认 100 |
## 访问入口
| 入口 | URL |
|------|-----|
| API 文档 | https://f.zikai.wang/docsBasic Auth |
| 上传页 | https://f.zikai.wang/api/upload |
| 文件浏览 | https://f.zikai.wang/api/files-pageBasic Auth |
| 共享记事本 | https://f.zikai.wang/api/wb-page/{id}(公开,`{id}``[a-zA-Z0-9_-]{1,64}` |
| 记事本管理 | https://f.zikai.wang/api/wb-adminBasic Auth |
| PDF 转换 | https://f.zikai.wang/api/pdf公开凭 cookie |
| 系统状态 | https://f.zikai.wang/api/system/statusHTML`?format=json` 切 JSON |
| curl 上传 | `curl -F file=@big.iso https://f.zikai.wang/api/files/upload` |
| SFTP | `sftp -P 2022 uploader@f.zikai.wang` |
## 运维
- **日志**`logs/app.log`HTTP`logs/sftp.log`SFTPpidfile`app.pid``sftp.pid`
- **临时文件清理**:分片上传完成后立即删 `.work/<id>/`;被放弃会话(`pending` 超 5 分钟)由后台 reaper 每 60s 清理;`start.sh` 启动时兜底清残留。
- **重新生成 DB 密码**`.venv/bin/python -m app.scripts.init_db`(保留现有:`KEEP_DB_PASSWORD=1`)。
- **多 worker 限制**:记事本 hub 是进程内存,多 uvicorn worker 下不互通,保持 `workers: 1`
- **数据库表**ORM 启动时自动建表(`init_db_schema``sql/schema.sql` 供参考/手动初始化。
## 持久化部署systemd
`./start.sh` 适合手动运维;生产推荐用 systemd 管理实现开机自启 + 崩溃自动重启:
```bash
./deploy/install-systemd.sh # 安装并启动 ztools2 服务(开机自启)
systemctl status ztools2 # 状态
journalctl -u ztools2 -f # 日志
sudo systemctl restart ztools2 # 重启(更新代码后)
```
脚本自动检测 `.venv/bin/uvicorn` 或系统 `uvicorn`,用实际路径填充 `deploy/ztools2.service` 模板。卸载:`systemctl disable --now ztools2 && rm /etc/systemd/system/ztools2.service && systemctl daemon-reload`
> 与 zMainPage 整体部署配合systemd 管后端,`zMainPage/deploy.sh --no-restart` 部署前端。详见 zMainPage 的 [`docs/deployment.md`](https://git.zikai.wang/zikai/zMainPage/src/branch/main/docs/deployment.md)。