# 错误处理与日志约定 zTools2 采用 Spring 风格分层架构,错误处理分三层:DAO fail-fast(抛异常),Service 捕获后转换业务异常并记日志,Controller 捕获后转 HTTP 状态码。 ## DAO 层(app/dao/) - **唯一发 SQL 的层**:所有写操作(create/update/delete)均在该层 commit,service 不直接操作 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-effort);WS 主循环异常 `logger.warning` 后正常关闭连接。 - 所有管理 API(`/api/admin/*`)经 `require_docs_auth` Basic Auth 守卫(常量时间比较)。 ## 日志位置 - `logs/app.log`(HTTP)、`logs/sftp.log`(SFTP);pidfile:`app.pid`、`sftp.pid`。 - 日志器命名:`zikai.pdf` / `zikai.whiteboard` / `zikai.tunnel` / `sftp`,便于按模块过滤。