refactor: 清理死代码/提前失败/日志/高内聚低耦合

死代码移除:
- whiteboard_hub.py: 移除未引用的 reset_hub 单例重置函数
- tunnel_service.py: 移除未引用的 is_port_allowed (逻辑已在 sftp_server 内联)
- tunnel_session_dao.py: 移除未引用的 get_active_by_port
- pdf_job_dao.py: 移除未用 datetime 导入
- pdf_converter.py: 移除未用 shutil 导入
- pdf_service.py: 移除未用 PdfSubmitResponse 导入 + _do_convert 内未用 hashlib 导入
- upload_html.py: 移除未用 escape 导入 (JS 侧自有 escapeHtml)
- pdf_controller.py: 移除 _resolve_cookie 内未用 cfg 局部变量

提前失败/分层修复:
- database.py init_db_schema: 建表后用 inspector 校验既有表列与模型一致,
  缺列即抛 RuntimeError (fail-fast on schema drift), 避免运行期才暴露
- whiteboard_dao.get_or_create: 仅 IntegrityError 才回滚重读, 其他异常向上抛
  (原 except Exception 会掩盖 schema/连接等真实故障)
- pdf_service.admin_delete/_safe_delete_file: 改用 PdfJobDAO.delete /
  UploadedFileDAO.delete, 不再直接操作 job_dao.db / file_dao.db (修复分层契约:
  DAO 头注释声明 service 不直接操作 session)
- PdfJobDAO 新增 delete(job) 方法

日志补全 (8 处 silent catch):
- whiteboard_hub.py disconnect/close_board 关闭 ws: logger.debug
- whiteboard_controller _safe_send/_safe_close: logger.debug
- sftp_server _close_tunnel_dao/读用户名: logger.debug
- sftp_server validate_public_key: logger.warning (auth 路径, 避免静默失败)

文档:
- 新增 docs/routes.md, docs/configuration.md, docs/error-handling.md
- README.md 精简为简介/结构/外部依赖/apache2 配置/Ubuntu 安装/docs 链接
This commit is contained in:
2026-07-28 11:34:35 +08:00
parent 1c0f776571
commit 9af28f41b4
16 changed files with 229 additions and 192 deletions

39
docs/configuration.md Normal file
View File

@@ -0,0 +1,39 @@
# 配置说明
所有运行时配置在 `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 |
## 生成 bcrypt hashSFTP/隧道用户密码)
```bash
.venv/bin/python -c "import bcrypt;print(bcrypt.hashpw(b'yourpass',bcrypt.gensalt()).decode())"
```
把输出填入 `config.yaml` 对应 `password_hash` 字段。
## 重新生成 DB 密码
```bash
.venv/bin/python -m app.scripts.init_db # 重置密码(写回 config.yaml
KEEP_DB_PASSWORD=1 .venv/bin/python -m app.scripts.init_db # 保留现有密码,仅建库建账
```

29
docs/error-handling.md Normal file
View File

@@ -0,0 +1,29 @@
# 错误处理与日志约定
zTools2 采用 Spring 风格分层架构错误处理分三层DAO fail-fast抛异常Service 捕获后转换业务异常并记日志Controller 捕获后转 HTTP 状态码。
## DAO 层app/dao/
- **唯一发 SQL 的层**所有写操作create/update/delete均在该层 commitservice 不直接操作 session。
- `get_or_create`whiteboard_dao.py`IntegrityError`(并发下另一事务已插入违反唯一约束)才回滚重读;其他异常向上抛,避免掩盖 schema/连接等真实故障。
- `delete`pdf_job_dao.py / uploaded_file_dao.py硬删 DB 行并 commit不存在返回 False。
- schema 漂移检测:`init_db_schema`database.py建表后用 inspector 检查既有表的列是否与模型声明齐全,缺列即抛 `RuntimeError`fail-fast避免运行期才以晦涩的 `OperationalError` 暴露。
## Service 层app/services/
- **PdfService**`submit` 校验扩展名/大小,超限清理已落盘文件后抛 `HTTPException`;后台转换 `_convert_async` 捕获 `TimeoutError` / 通用异常,经 `_mark_failed` 落库 + `logger.warning``admin_delete` 磁盘删除失败仅 `logger.warning`,仍清 DB 行保证列表不再显示。
- **UploadService / ChunkUploadService**:流式落盘出错清理临时文件后 `raise`(向上传播);分片会话被放弃由后台 reaper 每 60s 清理。
- **WhiteboardHub**`disconnect` / `close_board` 关闭 websocket 出错 `logger.debug`(尽力关闭,可能已关闭);`broadcast` 单连接发送失败立即 disconnect不影响其他连接reaper 循环异常 `logger.warning` 后继续。
- **TunnelService**`register` / `close` / `reap_orphans` 均记 `logger.info`SSH 连接断开时清理会话失败 `logger.warning`
- **sftp_server**`validate_public_key` 校验异常 `logger.warning`auth 路径,避免静默失败);`_close_tunnel_dao` / 读会话用户名失败 `logger.debug`(尽力清理)。
## Controller 层app/controllers/
- **pdf_controller**`_resolve_cookie` 解析用户 cookie无则生成新值挂 request.state 供响应 set_cookie。
- **whiteboard_controller**`_safe_send` / `_safe_close` 发送/关闭 WS 失败 `logger.debug`best-effortWS 主循环异常 `logger.warning` 后正常关闭连接。
- 所有管理 API`/api/admin/*`)经 `require_docs_auth` Basic Auth 守卫(常量时间比较)。
## 日志位置
- `logs/app.log`HTTP`logs/sftp.log`SFTPpidfile`app.pid``sftp.pid`
- 日志器命名:`zikai.pdf` / `zikai.whiteboard` / `zikai.tunnel` / `sftp`,便于按模块过滤。

50
docs/routes.md Normal file
View File

@@ -0,0 +1,50 @@
# 路由与访问入口
## 路由约定
**所有 zTools2 托管的入口(页面 / 静态资源 / 健康探针 / WebSocket / REST API统一挂在 `/api/` 前缀下**,只有元信息/文档例外(`/``/docs``/redoc``/openapi.json`)。这样反向代理与 vite dev proxy 都只需一条 `/api/` 规则即可把请求转给当前环境的 zTools2前端用**同源相对路径**(如 `/api/pdf/jobs``/api/health`)即可,与运行环境(本地 / 测试 / 生产)无关,无需区分 dev/prod 指向。
> PDF 转换的用户侧 UI 由 zMainPage 的 zPDF_package 组件提供(构建期 import非 iframezTools2 仅提供 `/api/pdf/jobs` 等 REST API 与 `/api/pdf-admin` 管理页。
页面类入口为避免与同名 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-admin``/api/wb-admin`
## 功能一览
| 模块 | 页面 / 接口 | 鉴权 |
|------|------------|------|
| 文件上传 | `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 转换 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 |
## 访问入口
| 入口 | 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 转换 | 经 zMainPage 的 PDF 页签zPDF_package 组件)调用 `/api/pdf/jobs` 等 REST API公开凭 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` |