开发规范
框架版本要求
当前后端运行基线:
| 组件 | 要求 |
|---|---|
| Python | >=3.10,<3.14 |
| Node.js | 22.23.1(以仓库 .nvmrc 为准) |
| FastAPI | >=0.109.2 |
| Pydantic | pydantic>=1.10.26,<2(v1 钉死;兼容层见 tg_signer/pydantic_compat.py) |
| SQLAlchemy | 当前使用 1.x 风格 API |
| APScheduler | 进程内调度器,单后端实例运行 |
新增代码必须兼容 Python 3.10,不使用 3.11+ 专属语法。
Pydantic 使用规范
当前项目仍运行在 Pydantic v1:
- 使用
BaseModel、Field、validator等 v1 API。 - 不要在生产代码中直接引入 v2 专属的
field_validator、ConfigDict或model_validate,除非同时提供兼容层。 - 对外 API schema 保持字段名稳定,新增字段优先设为可选并提供默认值。
- 输入模型必须做边界校验,例如空字符串、超长文本、非法 URL 和非法枚举值。
如果后续迁移到 Pydantic v2:
- 用兼容模块集中封装
model_dump/dict、model_validate/parse_obj差异。 - 先迁移测试覆盖充分的边界模型,再迁移核心任务配置模型。
- 不在业务服务中散落版本判断。
当前状态:依赖钉死为 pydantic>=1.10.26,<2(v1 路径)。统一使用 tg_signer.pydantic_compat(model_validate / model_dump / try_import_field_validator)。业务模型与路由逐步迁入 compat,为后续放宽到 <3 预留兼容层。
SQLAlchemy 使用规范
当前项目使用 SQLAlchemy 1.x 风格:
- 路由层通过依赖注入获取
Session。 - 服务函数接收显式
Session,不要在深层函数里隐式创建连接。 - 写操作必须提交或回滚清晰,异常路径不要留下未关闭 session。
- 查询结果对外返回前转换为 Pydantic / dict,不直接暴露 ORM 对象给前端。
新增索引或约束时需要同时说明查询场景、影响的写入路径和对已有数据的兼容性。
日志规范
- 使用模块级 logger,例如
logging.getLogger("backend.tasks")。 - 不记录明文 token、密码、API key、session string、验证码。
- 面向用户的任务日志写清楚动作、目标和失败原因。
- 异常日志保留上下文,但避免把完整请求体或密钥写入日志。
- 日志级别遵循:调试细节用
DEBUG,状态变化用INFO,可恢复异常用WARNING,功能失败用ERROR。
类型注解规范
- 新增函数必须标注参数和返回值。
- Python 3.10 使用内置泛型:
dict[str, Any]、list[str]。 - 可空值使用
Optional[T]或T | None,同一文件内保持一致。 - 对 JSON-like 配置使用
dict[str, Any],不要用裸dict扩散到新代码。 - 公共服务方法的返回类型要表达失败语义,例如
Optional[dict[str, Any]]或显式结果对象。
代码变更原则
- 优先保持现有模块边界,避免把路由、持久化和执行逻辑混在一起。
- 文件写入使用原子写入或明确锁保护,避免损坏用户配置。
- 后台任务必须记录异常,不能让
asyncio.create_task的异常静默丢失。 - 新增环境变量必须同步更新
docs/reference/configuration.md。 - 涉及部署行为的变更必须同步更新
docs/deploy/docker.md或docs/reference/ops.md。
测试规范
测试基础设施
测试目录 tests/ 下包含完整的测试基础设施:
| 目录/文件 | 说明 |
|---|---|
conftest.py | 全局 fixtures(数据库会话、测试客户端、Mock 对象) |
fixtures/ | 预定义测试数据(账号、任务、消息) |
mocks/ | Mock 对象(Telegram 客户端、数据库会话、AI 服务) |
factories/ | 测试数据工厂(AccountFactory、TaskFactory 等) |
utils/ | 测试辅助函数(时间工具、环境管理、断言增强) |
运行测试
bash
# 运行全部测试
pytest
# 运行指定模块
pytest tests/test_core.py -v
# 运行并查看覆盖率
pytest --cov=tg_signer --cov-report=term-missing
# 跳过覆盖率阈值检查(用于新增 backend/ 模块测试)
pytest --no-cov前端测试
前端使用 Vitest 进行单元测试:
bash
nvm use
cd frontend
# 运行全部测试
npm test
# 监听模式
npm run test:watch测试文件位于 frontend/src/test/ 目录。
本地、GitHub Actions 和 Docker 前端构建统一使用 .nvmrc 指定的 Node.js 22.23.1。不要使用其他 Node 主版本验证后直接提交;升级 Node 时必须同步更新 .nvmrc、两个 package.json 的 engines、Dockerfile,并重新运行完整前端测试与构建。
jsdom 与 Node Web API 测试约束
Vitest 的 jsdom 环境和 Node/Undici 可能分别提供 Blob、File、Response、 ReadableStream 等 Web API。不同 realm 的对象即使名称相同,也不保证可以互相传递。
- 构造真实
Response测试夹具时,正文优先使用字符串或字节数组,再通过response.blob()验证 Blob 消费路径。 - 不要把 jsdom 创建的
Blob直接传给 Node/Undici 的Response;这在部分平台会因 缺少兼容的stream()方法而失败。 - 必须测试流异常时,使用
ReadableStream明确触发controller.error(),不要伪造可重复 读取的简化响应对象。 - fake timer 与异步断言应放入同一个已等待的
Promise.all(),避免留下未等待的expect(...).resolves。 - 涉及 Node Web API 的测试在提交前至少执行
nvm use && cd frontend && npm test,并运行npm run typecheck与npm run build。
覆盖率配置
pyproject.toml 中配置了 pytest-cov:
fail_under阈值为 40%,同时统计tg_signer与backend两个包(addopts中--cov=tg_signer --cov=backend)- 新增测试用例时,优先覆盖核心逻辑和边界条件
新增依赖
近期新增的运行时依赖:
| 依赖 | 用途 |
|---|---|
aiofiles | 异步文件读写(历史/配置等可选非阻塞 IO) |
psutil>=5.9.0 | 内存监控(backend/utils/memory_monitor.py) |
前端新增依赖:
| 依赖 | 用途 |
|---|---|
vue-i18n@9 | 多语言支持(中英文切换) |
前端 API 请求超时
统一入口:frontend/src/lib/api/core.ts。
| 常量 | 默认 | 用途 |
|---|---|---|
DEFAULT_TIMEOUT_MS | 30s | 普通 JSON API;覆盖发起到 body 读完 |
MEDIUM_TIMEOUT_MS | 120s | WebDAV 探测/列表、设备保活、会话检测、聊天列表等 |
LONG_TIMEOUT_MS | 600s | 完整备份、WebDAV 下载、配置导入导出、同步 run |
说明:
request/requestBlob/requestText:整段墙钟超时(headers + body)。- 直接
fetchWithAuth:仅 TTFB;长任务应外层createRequestAbort覆盖 body。 - 超时 →
NETWORK_TIMEOUT;调用方AbortSignal取消 →NETWORK_ABORTED。 - 页面轮询优先用
startChainPoll,避免setInterval+ async 叠请求。