docs: 文档整理

main
NoahLan 2 weeks ago
parent 47355a0507
commit 69c845aacd

@ -1,122 +0,0 @@
---
name: netx-coding
description: Use when implementing code changes in the netx repository across Rust crates, controller/core runtime, protocol DTOs, Admin Console, Desktop Core UI, build scripts, or verification flows.
---
# netx 编码
## 使用场景
`/root/Projects/Mine/netx` 中写代码、修 bug、改构建、改 API、改 UI 时使用。
## 先读
按任务读取:
- 通用编码:`docs/specs/coding-guide.md`
- 架构边界:`docs/specs/architecture.md`
- 协议字段:`docs/specs/protocol.md`
- UI`docs/specs/ui-design.md`
- 需求状态:`docs/specs/traceability.md`
## 当前边界
只维护:
- `netx-controller`
- `netx-core`
- `apps/netx-desktop`
- `web/admin`
新增入口必须归入这些产品边界。
## 修改位置
| 任务 | 位置 |
| --- | --- |
| 控制 API、状态投影、任务、controller state/bootstrap/metrics/API DTO 聚合 | `crates/netx-controller/src/state.rs`、`bootstrap_plan.rs`、`runtime_metrics.rs`、`control_api_models.rs`、其它 `control_api_*` 模块 |
| Controller audit/session API、managed session 执行和 system metrics/diagnostics | `crates/netx-controller/src/sessions.rs`、`sessions_diagnostics.rs` |
| Controller core plan、gateway assignment helper 和 service projection | `crates/netx-controller/src/core_planner.rs`、`core_planner_gateway.rs`、`core_planner_http.rs`、`core_planner_services.rs` |
| Controller embedded gateway handler、bridge accept、hosted service supervisor、public/mixed entry、HTTP entry、HTTP3、HTTPS passthrough/terminate、routing/http/body/backend/connection 适配 | `crates/netx-controller/src/gateway_http_entry.rs`、`gateway_bridge_accept.rs`、`gateway_hosted_services.rs`、`gateway_public_entry.rs`、`gateway_mixed_entry.rs`、`gateway_http3.rs`、`gateway_https_passthrough.rs`、`gateway_https_terminate.rs`、`gateway_routing.rs`、`gateway_http.rs`、`gateway_body.rs`、`gateway_backend.rs`、`gateway_connection.rs` |
| Core 运行编排、heartbeat loop state、DeliveredConfig managed client/service/proxy/overlay runtime config selection、initial service/proxy selection、NAT probe binding snapshot、peer engine tick/requested-attempt TTL state、punch ready/候选排序/端口扫描策略和 punch attempt 到 peer identity 映射 | `crates/netx-core-runtime` |
| 共享路由、执行计划、path selection | `crates/netx-core-engine` |
| 本机 Local API | `crates/netx-core-local` |
| Core service 命令、低阶 CLI runtime、Controller API 命令、解析和报告 | `apps/netx-core/src/cli_service.rs`、`cli_local_runtime.rs`、`cli_controller_api.rs`、`cli_parse.rs`、`cli_reports.rs` |
| Core app session 前置/attached/bootstrap/startup/overlay/loop | `apps/netx-core/src/client_session.rs`、`client_session_attached.rs`、`client_session_bootstrap.rs`、`client_session_startup.rs`、`client_session_overlay.rs`、`client_session_loop.rs` |
| Core overlay hosts/DNS/resolved/NRPT 和 Linux transparent TCP intercept 执行胶水 | `apps/netx-core/src/overlay_integration.rs`、`overlay_transparent_proxy.rs` |
| Core local proxy 监听、协议 helper、NETX path、上游 TLS 和 proxy chain helper | `apps/netx-core/src/local_proxy.rs`、`local_proxy_protocol.rs`、`local_proxy_netx.rs`、`local_proxy_tls.rs`、`local_proxy_chain.rs` |
| Core NAT probe、punch poll、UDP/TCP punch 执行和直连/relay 隧道循环 | `apps/netx-core/src/punch_nat_probe.rs`、`punch.rs`、`punch_tunnel.rs` |
| 配置 | `crates/netx-config` |
| Wire DTO | `crates/netx-proto/src/wire.rs` |
| UI DTO | `crates/netx-ui-api` |
| 存储 service 注册/加载、service row mapping 和 overlay relay port 分配 | `crates/netx-control/src/service_store.rs` |
| 存储 service validation、service parse/normalize helper 和 service auth JSON helper | `crates/netx-control/src/service_validation.rs` |
| 存储控制面入口、overview、core state 聚合和剩余共享 validation/helper 方法 | `crates/netx-control/src/lib.rs` |
| StoreExecutor async wrapper | `crates/netx-control/src/executor.rs` |
| 存储 schema/open/migration/backfill | `crates/netx-control/src/schema.rs` |
| 存储 kv/singleton JSON helper | `crates/netx-control/src/kv.rs` |
| 存储 service token 生命周期 | `crates/netx-control/src/service_tokens.rs` |
| 存储 task/audit 持久层 | `crates/netx-control/src/audit_tasks.rs` |
| 存储 admin principal/token | `crates/netx-control/src/admin_store.rs` |
| 存储节点接入、心跳、NAT/overlay probe、enrollment 和 blocked identity | `crates/netx-control/src/node_store.rs` |
| 存储 Network/resource/membership、overlay subnet routes、node service capability 和 service gateway assignment KV store | `crates/netx-control/src/network_store.rs` |
| 存储 service config、local proxy config、managed client config、overlay policy 和 setup draft | `crates/netx-control/src/config_store.rs` |
| 存储公共记录/错误类型 | `crates/netx-control/src/models.rs` |
| Admin API client | `web/admin/src/lib/api/*` |
| Desktop 状态编排 | `apps/netx-desktop/src/composables/use-client-workbench.ts` |
| Desktop Tauri 命令/DTO/IPC/local/profile/projection/runtime/service/remote 边界 | `apps/netx-desktop/src-tauri/src/core_control.rs`、`core_control_models.rs`、`core_control_ipc.rs`、`core_control_local.rs`、`core_control_profile.rs`、`core_control_projection.rs`、`core_control_runtime.rs`、`core_control_service.rs`、`core_control_remote.rs` |
## 实现规则
- Handler 做编排,不堆业务内核。
- SQLite 访问走 `StoreExecutor`
- 协议新增先落 `netx-proto`
- UI 不自己推导 runtime plan。
- 会话路径要同时看 Controller、Core、bridge executor、CLI/UI。
- 拆模块时同步修测试显式 import。
- 大验证分层跑,先轻后重。
## 重构期间
本 skill 描述当前有效工程结构,不用于阻止已确认的重构。
当重构改变以下内容时,同步更新本 skill 和 `docs/specs/coding-guide.md`、`docs/specs/architecture.md`
- 产品边界。
- app / crate / module 责任。
- 前端目录归属。
- API 真相源。
- 构建命令。
- 验证命令。
- 运行入口。
若本 skill 与当前源码或已确认重构目标冲突,以当前源码和重构目标为准,并在同次修改中修正本 skill。
## 验证
轻量:
```bash
cargo check --workspace --all-targets
pnpm -C web/admin exec vue-tsc --noEmit
pnpm -C apps/netx-desktop exec vue-tsc --noEmit
```
仓库:
```bash
make verify-workspace
make verify-linux
make verify-windows
```
前端:
```bash
pnpm -C web/admin check:design-contracts
pnpm -C web/admin test
pnpm -C web/admin build
pnpm -C apps/netx-desktop check:design-contracts
pnpm -C apps/netx-desktop test
pnpm -C apps/netx-desktop build
```

@ -1,91 +0,0 @@
---
name: netx-design
description: Use when changing or reviewing netx Web Admin, Desktop Core UI, visual design contracts, navigation, components, page layout, copy, or frontend interaction behavior.
---
# netx UI 设计
## 使用场景
`/root/Projects/Mine/netx` 中处理这些内容时使用:
- `web/admin`
- `apps/netx-desktop`
- 导航信息架构。
- 页面布局。
- 组件复用。
- UI 文案。
- 设计契约检查失败。
## 先读
按顺序读取:
1. `docs/specs/ui-design.md`
2. `web/admin/src/app/routes.ts`
3. `web/admin/src/components/netx/*`
4. `web/admin/src/components/common/DataWorkbench.vue`
5. `apps/netx-desktop/src/App.vue`
6. `apps/netx-desktop/src/components/client/*`
视觉资产在 `docs/design/netx-ui-dev-assets`。页面源码不得直接引用该目录。
## Admin 规则
- Admin 是高信息密度控制台。
- 一级导航固定走 `Overview / Nodes / Networks / Gateways / Services / Routes / DNS / Security / Diagnostics / Settings`
- `Gateways` 是具有 Gateway capability 的节点视图。
- `Services` 覆盖 HTTP、HTTPS、TCP、UDP、SOCKS5、Shadowsocks。
- HTTP、Tunnel、SOCKS5、Shadowsocks 都归入 `Services`
- 页面优先复用 `components/netx``DataWorkbench`
## Desktop 规则
- Desktop 是本机 Core 工作台。
- 交互按 PC 分屏处理。
- Desktop 不承载网络逻辑。
- 通过 Local API / IPC 管理 `netx-core`
- 使用分屏工作台壳。
## 重构期间
本 skill 描述当前 UI 结构,不用于阻止已确认的 UI 或产品重构。
当重构改变以下内容时,同步更新本 skill 和 `docs/specs/ui-design.md`
- Admin 目录归属。
- Desktop 目录归属。
- 导航信息架构。
- 组件入口。
- 设计资产路径。
- 设计检查命令。
- 页面交互主模式。
若本 skill 与当前源码或已确认重构目标冲突,以当前源码和重构目标为准,并在同次修改中修正本 skill。
## 必跑检查
改 Admin
```bash
pnpm -C web/admin check:design-contracts
pnpm -C web/admin test
pnpm -C web/admin build
```
改 Desktop
```bash
pnpm -C apps/netx-desktop check:design-contracts
pnpm -C apps/netx-desktop test
pnpm -C apps/netx-desktop build
```
## 禁止项
- 不用原生 `<select>`
- 不用 `window.confirm`
- 不直接引用 `docs/design`
- 不新增手写 Logo SVG。
- 不加全局假搜索和假环境切换。
- 不为单页创造新视觉体系。

@ -1,69 +0,0 @@
---
name: netx-requirements
description: Use when working in the netx repository on requirements, feature scope, product boundaries, implementation gap analysis, or docs-to-code traceability for controller, core, desktop, and web/admin surfaces.
---
# netx 需求判断
## 使用场景
`/root/Projects/Mine/netx` 中处理这些问题时使用:
- 判断某能力是否属于当前产品范围。
- 对照需求和实现。
- 补功能缺口。
- 改需求文档。
- 回答产品边界、产品名、交付形态。
## 先读
按顺序读取:
1. `docs/specs/requirements.md`
2. `docs/specs/traceability.md`
3. `docs/specs/architecture.md`
4. 当前源码、`Cargo.toml`、`Makefile`
## 当前产品主语
只使用这些产品主语:
- `netx-controller`
- `netx-core`
- `apps/netx-desktop`
- `web/admin`
Gateway 是 `netx-core` 的 capability不是独立程序。
## 判断规则
- 需求判断必须回到当前源码和新规格。
- 文档说已实现但源码没有入口时,按未实现处理。
- 源码已有入口但缺真实网络、系统权限或跨平台证据时,按待人工验收处理。
- 发现真实缺口后,默认继续补齐,不停在报告。
- 自动化验证和最终人工验收分开汇报。
## 重构期间
本 skill 描述当前有效结构,不用于阻止已确认的重构。
当重构改变以下内容时,同步更新本 skill 和 `docs/specs/requirements.md`、`docs/specs/traceability.md`
- 产品边界。
- 程序入口。
- 目录归属。
- 能力域划分。
- 需求状态。
- 验收口径。
若本 skill 与当前源码或已确认重构目标冲突,以当前源码和重构目标为准,并在同次修改中修正本 skill。
## 常见陷阱
| 陷阱 | 处理 |
| --- | --- |
| 控制面入口不明确 | 改查 `apps/netx-controller`、`crates/netx-controller` |
| 把 `core-gateway` 当产品 | 它只是构建档位 |
| 把 Desktop 当网络内核 | Desktop 只是本机管理壳 |
| 只看文档判断现状 | 改读 `docs/specs/*` 和源码 |
| 默认安全优先 | netx 当前前提是效率和功能优先,安全可配置 |

@ -1,55 +0,0 @@
# Grill: iTi-Flask 框架重构
Date: 2026-05-08
## Intent
iTi-Flask 要成为多个长期业务项目复用的后端基座。它需要提供稳定的系统域、模块边界、服务调用实践和项目模板,同时不承载具体业务逻辑。
## Constraints
- 业务项目必须依赖版本化 Git tag不复制框架源码。
- 业务项目可以扩展框架,但不能修改或覆盖框架实现。
- 框架保留系统域 API并作为核心默认能力。
- 框架必须保持轻量。它不是微服务平台。
- 生产只从 `main` 发布。
- Python 基线是 `>=3.11`
- 第一版服务调用只支持 HTTP JSON。
- 第一版任务运行器是单进程能力。
- 前端构建产物不作为框架内容发布。
- 运行时数据库和生成物不是源码资产。
## Key decisions
- Decision: 使用 Python 包 + Copier 模板。Reason: 多个业务项目需要共享运行时代码和一致项目骨架。Alternative considered: 只用 Copier 复制代码。
- Decision: 先用 Git tag 发布。Reason: 在私有 PyPI 建立前,私有 Git tag 已够用。Alternative considered: 本地路径依赖或一开始就上私有 PyPI。
- Decision: 系统域留在 core并默认启用。Reason: 用户、角色、菜单、部门、字典、配置、文件、认证、审计日志和用户扩展属性是后台基座共同能力。Alternative considered: 把系统域拆成独立包。
- Decision: 业务项目只能扩展框架行为。Reason: 直接覆盖会破坏后续升级。Alternative considered: 本地覆盖和 monkey patch。
- Decision: ERP 离开 core成为独立 Gateway 服务。Reason: ERP 是多个业务系统共享的 Monitor API 和 ODBC 解释层应只有一个口径。Alternative considered: ERP 作为 core module 或每个项目各接一次。
- Decision: 框架同时支持进程内 module 和 service client。Reason: module 是代码边界service 是部署边界。Alternative considered: 全部微服务化。
- Decision: module 跨调用走 facade。Reason: 保留 Python 调用的简单性同时避免穿透内部实现。Alternative considered: module 之间全走 HTTP 或随意 import。
- Decision: 业务项目模型集中在结构化 `models/` 树中,并保留一条 migration 流。Reason: 降低重复 migrations同时支持多人按 module 协作。Alternative considered: module 自有 migrations。
- Decision: migrations 版本化并提交。Reason: 多人开发和生产升级需要可复现数据库历史。Alternative considered: 忽略 migrations 和手工改库。
- Decision: 业务项目使用 `main` 做生产、`dev` 做集成、个人分支做开发。Reason: migration 顺序必须在生产前收敛。Alternative considered: 生产从个人分支发布。
- Decision: migration 文件名使用日期时间、revision 和 message slug。Reason: 作者名放在自由 message 中不强制业务域格式。Alternative considered: 固定 domain/action message 格式。
- Decision: 框架系统迁移复制或同步到业务项目 migrations。Reason: 生产只保留一个 `flask db upgrade` 路径。Alternative considered: 框架和业务两套迁移命令。
- Decision: seed 使用 Python幂等只写系统初始数据。Reason: 运行时 SQLite 文件不是源码资产MySQL 等数据库应由代码初始化。Alternative considered: 保留 dev 数据库或使用 JSON/SQL seed。
- Decision: 后台 API 可以保留 envelope 和业务失败 HTTP 200。服务 API 使用真实 HTTP 状态码。Reason: 重试、熔断和监控需要真实状态码语义。Alternative considered: 所有 API 都继续 HTTP 200。
- Decision: 同时保留显式 module protocol 和现有 runtime plugin 思路。Reason: 业务模块是稳定应用代码插件是部署时可选扩展。Alternative considered: 只保留插件扫描或不保留插件能力。
- Decision: 第一阶段必须让 service client 和 task runner 可用。Reason: 架构要可验证不能只停在契约。Alternative considered: 第一阶段只写契约。
## Surfaced assumptions
- ERP 不是普通业务逻辑,而是面向既有外部系统的公共 Gateway。
- 团队更常用个人 Git 分支,而不是严格功能分支。
- 历史上可能存在手工生产库变更,但目标纪律是 migration-based。
- 开发和生产后续可能使用 MySQL 或其它数据库,所以 SQLite 快照不是可靠 seed 策略。
- 轻量微服务只服务于职责边界、协作和复用,不追求 service mesh、服务发现、分布式事务或平台复杂度。
## Out of scope
- 服务发现。
- Kubernetes service mesh。
- 分布式事务。
- Saga 编排。
- gRPC。
- 消息总线平台。
- 自动弹性伸缩平台。
- 多租户网关。
- 完整 async service client。
- 分布式任务锁或 exactly-once 保证。
- 内置前端后台应用。

@ -1,85 +1,92 @@
# iTi-Flask
iTi-Flask 是基于 APIFlask / Flask 的轻量后端框架基座。
iTi-Flask 是基于 APIFlask / Flask 的后端框架基座。
它只提供工程底座和架构约束。
它不是业务应用。
它不内置用户、角色、菜单等系统业务。
它不内置前端产物。
它可以按配置承载业务项目自己的 SPA 静态目录。
它只提供应用创建、配置、扩展、模块协议、服务调用、任务运行、迁移集成和基础工具。
它不是业务系统。
系统业务能力不在框架内。
需要系统业务时,业务项目使用独立包 `iTi-System`
## 当前边界
## 技术栈
框架内置:
- Python 3.11+
- APIFlask / Flask
- SQLAlchemy / Flask-Migrate
- Flask-JWT-Extended
- Flask-Limiter
- Flask-Caching
- Marshmallow
- httpx
- uv
- Copier
- 应用工厂和配置加载。
- SQLAlchemy、Flask-Migrate、JWT、缓存、限流、日志、错误处理。
- 进程内 module 协议。
- HTTP JSON service client。
- 单进程 task runner。
- 可选 SPA 静态目录承载。
- Copier 业务项目模板。
## 安装
框架不内置:
- `sys_*` 表。
- 认证、用户、角色、菜单、部门、字典、配置、文件、日志等系统业务。
- 系统 seed。
- 系统 migration。
- ERP 能力。
- 前端构建产物。
系统业务放在独立包 `iti-system`
MES 这类主业务项目按需依赖 `iti-flask + iti-system`
ERP Service 这类服务只依赖 `iti-flask`
## 开发
框架本地开发:
```bash
uv sync --extra dev
uv run --extra dev pytest
uv run --extra dev mypy
```
需要覆盖可选依赖时
业务项目通过 Git tag 依赖框架:
```bash
uv run --extra dev --extra mysql --extra image --extra excel pytest
```toml
dependencies = [
"iti-flask @ git+ssh://git@example.com/iTi-Flask.git@v0.1.1",
]
```
## 业务项目生成
可选依赖按需启用:
```bash
uvx copier copy ./copier-template ../my-business-app
uv sync --extra dev --extra mysql --extra image --extra excel
```
业务项目依赖 iTi-Flask 的 Git tag。
框架升级后,业务项目更新依赖 tag。
## 系统业务
## 应用工厂
需要用户、角色、菜单等能力时,业务项目额外依赖 `iti-system`,并在 `modules` 中注册
业务项目使用 `iti.applications.create_app()` 创建 Flask 应用:
```python
from iti_system import create_system_module
from iti.applications import create_app
modules = [
create_system_module(),
]
from config import config
from my_app.models import import_models
from my_app.modules.example.module import ExampleModule
app = create_app(
config_mapping=config,
model_imports=[import_models],
modules=[ExampleModule()],
)
```
`config_mapping` 用于传入业务项目自己的配置类。
`model_imports` 用于让 Alembic 自动发现业务模型。
`modules` 用于注册进程内业务模块。
## 业务项目生成
```bash
uvx copier copy ./copier-template ../my-business-app
```
`iti-system` 引入即全量引入。
不提供系统模块选择。
不拆分系统 migration。
生成后进入业务项目:
`iti-system` 自己提供系统 seed 和系统 migration 同步命令。
```bash
cd ../my-business-app
uv sync --extra dev
uv run python -m flask --app app.py db upgrade
uv run python -m flask --app app.py run --debug
```
## 文档
- [框架边界](docs/FRAMEWORK_BOUNDARY.md)
- [架构重构计划](docs/ARCHITECTURE_REFACTOR_PLAN.md)
- [文档索引](docs/README.md)
- [架构](docs/ARCHITECTURE.md)
- [配置](docs/CONFIGURATION.md)
- [模块协议](docs/MODULES.md)
- [数据库迁移](docs/MIGRATIONS.md)
- [种子数据](docs/SEEDS.md)
- [服务客户端](docs/SERVICE_CLIENT.md)
- [任务运行器](docs/TASKS.md)
- [数据库迁移](docs/MIGRATIONS.md)
- [种子数据](docs/SEEDS.md)
- [Copier 模板](docs/COPIER_TEMPLATE.md)

@ -44,25 +44,12 @@ uv run python -m flask --app app.py db upgrade
{% if include_system %}
本项目已引入 `iti-system`。
系统能力全量引入,不拆分 migration。
同步系统 migration
```bash
uv run python -m flask --app app.py iti-system migrations sync
uv run python -m flask --app app.py db upgrade
```
写入系统 seed
```bash
uv run python -m flask --app app.py iti-system seed system
```
初始化和维护命令以 `iTi-System` 文档为准。
{% else %}
本项目未引入 `iti-system`。
因此没有认证、用户、角色、菜单、部门、字典、配置、文件、日志等系统能力。
因此没有系统业务能力。
后续需要系统能力时,增加 `iti-system` 依赖,并在 `app.py` 注册:
后续需要系统业务时,增加 `iti-system` 依赖,并在 `app.py` 注册:
```python
from iti_system import create_system_module

@ -0,0 +1,109 @@
# 架构
iTi-Flask 是框架基座。
它提供后端项目的通用工程能力,不内置具体业务。
## 包定位
框架负责:
- 应用工厂。
- 配置加载。
- APIFlask 集成。
- SQLAlchemy 和 Flask-Migrate 初始化。
- JWT、缓存、限流、日志、错误处理。
- 模块注册协议。
- HTTP JSON 服务客户端。
- 单进程任务运行器。
- Copier 业务项目模板。
- 可选 SPA 静态目录承载。
- 默认 HTML 错误页。
框架不负责:
- 系统业务。
- 业务表和业务路由。
- 业务 seed。
- 业务前端构建产物。
需要系统业务时,业务项目额外依赖 `iTi-System`
## 应用创建流程
`create_app()` 会按顺序完成这些工作:
1. 解析配置。
2. 创建 `APIFlask` 应用。
3. 初始化日志、插件、HTTP、JSON、Moment。
4. 初始化数据库、JWT、迁移、限流、缓存、事件总线。
5. 导入业务模型。
6. 初始化服务客户端和任务运行器。
7. 注册模块的 `init_app``register_commands`
8. 注册可选 SPA 路由。
9. 注册模块路由、权限元数据和菜单元数据。
10. 初始化服务层。
## 扩展组件
常用扩展从 `iti.applications.extensions` 引入:
```python
from iti.applications.extensions import db, migrate, jwt, limiter
from iti.applications.extensions import cache_simple, cache_redis, eventbus
```
框架还提供:
- `iti.applications.common.utils.success`
- `iti.applications.common.utils.fail`
- `iti.applications.common.utils.page`
- `iti.applications.common.utils.pagination_builder`
后台 API 默认使用响应 envelope
```json
{"data": {}, "code": 200, "message": "成功"}
```
服务间 API 应使用真实 HTTP 状态码。
## 默认错误页
框架内置 `403`、`404`、`500` HTML 错误页。
请求更偏向 HTML 时返回模板页。
请求更偏向 JSON 时返回:
```json
{"data": null, "code": 404, "message": "Not Found"}
```
## SPA 静态目录
SPA 承载默认关闭。
业务项目开启后,框架从 `FRONTEND_PATH` 读取 `index.html` 和静态文件:
```python
FRONTEND_ENABLED = True
FRONTEND_PATH = "frontend/dist"
```
`FRONTEND_PATH` 可以是绝对路径。
相对路径按业务项目配置里的 `BASE_DIR` 解析。
## 扩展框架
业务项目通过模块扩展框架:
- 注册业务蓝图。
- 注册 CLI 命令。
- 声明权限元数据。
- 声明菜单 seed 元数据。
- 增加业务模型。
- 增加业务配置。
- 配置服务客户端。
- 注册任务。
框架问题在框架仓库修复,再发布新 tag。
业务项目不复制框架源码,也不覆盖框架 import path。

@ -1,162 +0,0 @@
# 架构重构决策日志
## 当前决策
iTi-Flask 收敛为框架底座。
系统业务拆到独立包 `iti-system`
ERP Service 作为独立业务项目,只依赖 `iti-flask`
当前整体形态:
- `iTi-Flask`:框架基座。
- `iTi-System`:可选系统业务包。
- `erp-service`ERP 中转服务。
- MES 主项目:按需依赖 `iti-flask + iti-system`
## iTi-Flask 边界
保留:
- 应用工厂。
- 配置加载。
- APIFlask 集成。
- SQLAlchemy / Flask-Migrate。
- JWT 基础集成。
- 缓存、限流、日志、错误处理。
- module 协议。
- service client。
- 单进程 task runner。
- 可选 SPA 静态目录承载。
- Copier 业务项目模板。
移出:
- `sys_*` 模型。
- 系统路由。
- 系统 service。
- 系统 seed。
- 系统 migration。
- 系统事件处理器。
- 权限校验装饰器。
- 系统日志入库扩展。
## iTi-System 边界
`iti-system` 提供系统域能力:
- 认证。
- 用户。
- 用户扩展属性。
- 角色。
- 菜单。
- 部门。
- 字典。
- 系统配置。
- 文件。
- 审计日志。
业务项目只决定是否引入 `iti-system`
引入即全量引入。
不提供 profile。
不提供系统模块选择。
不拆分系统 migration。
示例:
```python
from iti_system import create_system_module
modules = [
create_system_module(),
]
```
## 微服务边界
当前不是强微服务平台。
不做:
- 服务注册发现。
- Kubernetes service mesh。
- 分布式事务。
- Saga。
- gRPC。
- 消息总线全家桶。
- 自动弹性伸缩。
- 多租户网关。
保留轻量服务协作:
- HTTP JSON。
- service token。
- 超时。
- 保守重试。
- 可选熔断。
- trace id 透传。
## ERP Service 决策
ERP Service 不属于框架。
它是独立业务项目。
职责:
- 调用既有 Monitor ERP API。
- 调用既有 Monitor ERP ODBC 数据源。
- 承担必要的 ERP 数据同步任务。
- 对其它业务项目提供 HTTP JSON 中转能力。
它不依赖 `iti-system`
它不共享 MES 系统库。
## 数据库和迁移
每个业务项目只保留一条 Alembic migration 流。
iTi-Flask 不提供系统 migration。
需要系统能力时,由 `iti-system` 同步系统 migration
```bash
uv run python -m flask --app app.py iti-system migrations sync
uv run python -m flask --app app.py db upgrade
```
`iti-system` 系统迁移是全量系统表集合。
不按 auth、user、role 等能力拆分。
轻服务不需要系统表时,直接不依赖 `iti-system`
## Seed
iTi-Flask 不提供系统 seed。
`iti-system` 提供系统 seed
```bash
uv run python -m flask --app app.py iti-system seed system
```
业务 seed 由业务项目自己维护。
## 当前实施状态
已完成:
- iTi-Flask 移除系统域代码。
- iTi-Flask 移除系统 seed 和系统 migration 命令。
- iTi-Flask 保留默认 HTML 错误页。
- iTi-Flask 保留可配置 SPA 承载。
- iTi-System 独立成包。
- iTi-System 提供全量系统包注册入口。
- iTi-System 提供系统 seed 命令。
- iTi-System 提供系统 migration 同步命令。
- ERP Service 文档去掉系统 seed / migration 初始化步骤。
- 三个项目都改为 uv 虚拟环境工作流。
验证结果:
```text
iTi-Flask: 60 passed
iTi-System: 7 passed
erp-service: 4 passed
```

@ -0,0 +1,138 @@
# 配置
iTi-Flask 内置 `dev`、`test`、`prod` 三套配置。
默认环境是 `dev`
## 环境选择
```bash
FLASK_ENV=dev uv run python -m flask --app app.py run --debug
```
`create_app(config_name="test")` 可以直接指定环境。
业务项目通常传入自己的配置映射:
```python
from iti.config import DevConfig as BaseDevConfig
from iti.config import ProdConfig as BaseProdConfig
from iti.config import TestConfig as BaseTestConfig
class DevConfig(BaseDevConfig):
SQLALCHEMY_DATABASE_URI = "sqlite:///runtime/app_dev.db"
class TestConfig(BaseTestConfig):
SQLALCHEMY_DATABASE_URI = "sqlite:///:memory:"
class ProdConfig(BaseProdConfig):
pass
config = {
"dev": DevConfig,
"test": TestConfig,
"prod": ProdConfig,
"default": DevConfig,
}
```
## env 文件
框架会从当前工作目录加载第一个存在的 env 文件:
1. `.env.local`
2. `.env.<FLASK_ENV>`
3. `.env`
也可以用 `ITI_ENV_DIR` 指定查找目录:
```bash
ITI_ENV_DIR=/path/to/app FLASK_ENV=prod uv run python -m flask --app app.py run
```
## 常用配置项
| 配置项 | 说明 |
| --- | --- |
| `SECRET_KEY` | Flask 密钥 |
| `JWT_SECRET_KEY` | JWT 密钥 |
| `DATABASE_URL` | 数据库连接串 |
| `REDIS_URL` | 生产限流存储地址 |
| `FRONTEND_ENABLED` | 是否启用 SPA 承载 |
| `FRONTEND_PATH` | SPA 构建目录 |
| `SERVICES` | 服务客户端配置 |
| `TASKS_ENABLED` | 是否启动任务调度线程 |
## 数据库
默认使用 SQLite。
开发环境默认数据库:
```text
runtime/iti-flask_dev.db
```
测试环境使用内存数据库:
```text
sqlite:///:memory:
```
生产环境优先读取 `DATABASE_URL`
`postgres://` 会自动转换为 `postgresql://`
## 限流
默认启用 Flask-Limiter。
开发环境:
```python
RATELIMIT_ENABLED = True
RATELIMIT_STORAGE_URL = "memory://"
RATELIMIT_DEFAULT = "1000 per hour"
```
测试环境禁用限流。
生产环境默认从 `REDIS_URL` 读取存储地址:
```python
RATELIMIT_DEFAULT = "100 per hour"
```
路由级限流:
```python
from iti.applications.extensions import limiter
@bp.get("/reports")
@limiter.limit("10 per minute")
def list_reports():
return {"data": []}
```
## 缓存
框架初始化两个缓存配置:
- `CACHE_SIMPLE`
- `CACHE_REDIS`
本地默认启用 `SimpleCache`
Redis 缓存默认关闭。
## 文件存储
本地文件默认写入:
```text
runtime/uploads
```
业务项目可以覆盖 `FILE_STORAGE["LOCAL"]["base_path"]`

@ -0,0 +1,57 @@
# Copier 模板
`copier-template` 用于生成业务后端项目。
模板只生成项目骨架,不复制框架源码。
## 生成项目
在 iTi-Flask 仓库根目录执行:
```bash
uvx copier copy ./copier-template ../my-business-app
```
进入生成后的项目:
```bash
cd ../my-business-app
uv sync --extra dev
uv run python -m flask --app app.py db upgrade
uv run python -m flask --app app.py run --debug
```
## 模板参数
| 参数 | 说明 |
| --- | --- |
| `project_name` | 业务项目显示名称 |
| `project_slug` | 业务项目 Python 包名 |
| `framework_git` | iTi-Flask Git 地址 |
| `framework_tag` | iTi-Flask Git tag |
| `include_system` | 是否引入 iTi-System |
| `system_git` | iTi-System Git 地址 |
| `system_tag` | iTi-System Git tag |
## 生成内容
模板会生成:
- `app.py`
- `config.py`
- `pyproject.toml`
- `migrations/`
- 业务 Python 包。
- 示例模块。
- 示例模型。
- 示例测试。
## 扩展方式
业务项目扩展框架时,只改业务项目自己的文件:
- 在 `modules/` 下新增业务模块。
- 在 `models/` 下新增业务模型。
- 在 `config.py` 中覆盖配置。
- 在 `app.py` 中注册模块和模型导入函数。
框架升级通过更新 `pyproject.toml` 中的 Git tag 完成。

@ -1,214 +0,0 @@
# ERP Service 契约草案
ERP Service 是独立业务项目。
它不属于 iTi-Flask core。
它应按业务项目方式依赖 iTi-Flask并遵从框架的应用工厂、module、migration、seed 和配置规则。
ERP Service 实际项目应放在 iTi-Flask 仓库之外。
```text
/root/Projects/iTi/erp-service
```
在 ERP Service 这个业务项目内部Monitor 能力以 module 组织。
本仓库只保留契约文档,不内置 ERP Service 代码。
## 职责
ERP Service 负责隔离既有 Monitor ERP 系统。
第一版职责:
- 调用 Monitor ERP HTTP API。
- 通过 ODBC 读取 Monitor ERP 数据库。
- 提供轻量定时同步任务。
- 将 ERP 数据上报给业务项目。
- 对业务项目提供统一 HTTP JSON API。
业务项目不直接连接 Monitor API。
业务项目不直接连接 Monitor ODBC。
## 非目标
不做:
- 服务注册发现。
- Kubernetes service mesh。
- 分布式事务。
- Saga。
- gRPC。
- 消息总线全家桶。
- 自动弹性伸缩。
- 多租户网关。
第一版只做 HTTP JSON。
## 服务边界
ERP Service 自己持有:
- Monitor API 配置。
- Monitor ODBC 配置。
- ERP 字段映射。
- ERP 查询和转换逻辑。
- ERP 同步任务状态。
业务项目只关心:
- 请求哪个 ERP 能力。
- 传入业务参数。
- 得到标准 JSON 响应。
- 处理真实 HTTP 状态码。
## 认证
服务间调用使用 service token。
请求头:
```http
Authorization: Bearer <service-token>
X-Trace-Id: <trace-id>
```
`X-Trace-Id` 由调用方传入。
没有时调用方生成。
## 状态码
服务间 API 使用真实 HTTP 状态码。
建议:
- `200`:成功。
- `202`:任务已接受。
- `400`:请求参数错误。
- `401`:服务 token 无效。
- `404`:资源不存在。
- `409`:幂等键冲突或任务状态冲突。
- `422`ERP 返回数据无法转换。
- `502`Monitor API 或 ODBC 返回异常。
- `503`Monitor 不可用。
- `504`Monitor 超时。
不要把服务间失败包装成 HTTP 200。
## API 草案
健康检查:
```http
GET /health
```
返回:
```json
{"status": "ok"}
```
ERP API 代理能力:
```http
POST /monitor/api/call
```
请求:
```json
{
"name": "get_customer",
"params": {"customer_id": "C001"}
}
```
ODBC 查询能力:
```http
POST /monitor/odbc/query
```
请求:
```json
{
"name": "customer_by_id",
"params": {"customer_id": "C001"}
}
```
同步任务:
```http
POST /sync/jobs
GET /sync/jobs/{job_id}
```
创建请求:
```json
{
"kind": "customers",
"since": "2026-05-08T00:00:00+08:00",
"callback_url": "http://business.local/internal/erp/customers",
"idempotency_key": "customers-20260508"
}
```
创建返回:
```json
{
"job_id": "01HX...",
"status": "accepted"
}
```
## 调用约定
业务项目调用 ERP Service 时使用 `iti.service_client`
默认建议:
```python
SERVICES = {
"erp": {
"base_url": "http://erp-service:8000",
"token": "...",
"timeout": {
"connect": 1.0,
"read": 10.0,
"write": 5.0,
"pool": 1.0,
},
"retry": {
"attempts": 2,
"backoff": 0.2,
"statuses": [502, 503, 504],
},
"circuit_breaker": {
"enabled": True,
"fail_max": 5,
"reset_timeout": 30,
},
}
}
```
GET 查询可以默认重试。
POST 默认不重试。
需要重试 POST 时,必须提供 `idempotency_key`
## 任务边界
ERP Service 可以使用 iTi-Flask 的轻量 task runner。
限制:
- 只在一个专用进程启用。
- 任务状态第一版可以存在进程内。
- 需要跨重启恢复时,再引入数据库任务表。
这不是分布式任务平台。

@ -1,247 +0,0 @@
# iTi-Flask 框架边界
iTi-Flask 是给多个业务项目复用的轻量后端基座。
它不是业务应用。
它不是微服务平台。
它不是前端发布包。
它可以按配置承载业务项目自己的 SPA 静态目录。
框架包本身不携带前端构建产物。
## 包和模板
iTi-Flask 以 Python 包发布。
业务项目依赖 Git tag
```toml
dependencies = [
"iti-flask @ git+https://git.noahlan.cn/iti-framework/iTi-Flask.git@v0.1.1",
]
```
Copier 只负责生成项目骨架。
模板不会把框架源码复制到业务项目里。
## 核心职责
框架负责:
- 应用工厂。
- 配置加载。
- APIFlask 集成。
- SQLAlchemy、迁移、缓存、限流、JWT、日志、错误处理集成。
- 模块协议。
- 运行时插件加载。
- HTTP JSON 服务客户端。
- 单进程任务注册表和运行器。
- 可选 SPA 静态目录承载。
框架不负责:
- 系统域业务。
- `sys_*` 表。
- 系统域迁移。
- 系统域 seed。
- ERP 中转能力。
- 统一网关。
- 服务注册发现。
- 前端构建产物。
## 系统域
系统域不属于 iTi-Flask。
系统域放在独立包 `iti-system`
`iti-system` 可以提供:
- 认证。
- 用户。
- 角色。
- 菜单。
- 部门。
- 字典。
- 系统配置。
- 文件。
- 审计日志。
- 用户扩展属性。
业务项目是否引入系统域,由业务项目依赖和 `modules=[create_system_module()]` 注册决定。
`iti-system` 引入即全量引入。
不提供系统模块选择。
不拆分系统 migration。
不引入 `iti-system` 时,不会有系统路由、系统模型、系统 migration、系统 seed。
ERP Service 这类服务不依赖 `iti-system`
MES 这类主业务项目可以依赖 `iti-flask + iti-system`
A 工厂 MES 和 B 工厂 MES 可以各自独立部署,各自使用自己的数据库。
这不是多租户。
## 扩展规则
业务项目只能扩展框架。
不能修改或覆盖框架实现。
允许:
- 注册业务模块。
- 跨模块调用时只调用公开 facade 函数。
- 注册业务蓝图。
- 注册业务权限和菜单元数据。
- 在业务项目中增加业务模型。
- 配置服务和插件。
- 安装框架 optional extras。
- 按需安装 `iti-system`
禁止:
- 复制并修改框架模块。
- 导入其它模块的内部 model、repository 或私有 service 层。
- shadow 框架 import path。
- monkey patch 框架函数。
- 直接修改已安装的包源码。
框架问题必须在框架仓库修复,并通过新的 Git tag 发布。
## 模块和服务
模块是代码边界。
模块运行在同一个 Flask 进程内。
服务是部署边界。
服务通过 HTTP JSON 调用。
默认使用模块。
只有同时满足以下条件时,才拆成服务:
- 至少两个业务项目需要复用这个能力。
- 该能力有外部系统、独立数据源或独立发布诉求。
- 业务项目不应该理解该能力的内部细节。
ERP 是服务。
它应当作为 ERP Service 独立项目存在,不属于框架核心。
## 服务客户端边界
第一版服务客户端只支持 HTTP JSON。
提供:
- base URL 配置。
- service token 鉴权。
- 超时。
- 保守重试。
- 可选熔断。
- trace id 透传。
- 结构化调用日志。
- JSON 编码和解码。
- 测试用 mock transport。
不提供:
- 服务发现。
- 负载均衡。
- gRPC。
- streaming。
- async client。
- service mesh。
## 任务运行器边界
第一版任务运行器是单进程能力。
提供:
- 任务注册。
- 手动触发。
- interval 和简单 cron-like 调度。
- 单进程内防重复执行。
- 运行日志。
- 状态查询。
不提供:
- 分布式锁。
- 多实例 exactly-once。
- 默认 Celery 或 RQ。
多进程生产部署时,只在一个专用实例中启用 scheduler。
## API 状态码规则
面向后台前端的 API 可以保留现有响应 envelope
```json
{"code": 200, "message": "success", "data": {}}
```
服务间 API 必须使用真实 HTTP 状态码。
服务客户端依赖状态码做重试、熔断和监控判断。
## 数据库规则
业务项目只保留一条 migration 流。
迁移文件必须提交到 Git。
生产只从 `main` 执行迁移。
引入 `iti-system` 的业务项目,由 `iti-system` 同步系统迁移到业务项目 migration 流。
不引入 `iti-system` 的业务项目,不同步系统迁移。
生产应只执行一个命令:
```bash
flask db upgrade
```
## 分支规则
推荐分支模型:
- `main`:只用于生产发布。
- `dev`:集成分支。
- `user/<name>`:个人开发分支。
`dev` 发布前必须收敛成一个 migration head。
生产不能从个人分支发布。
## 种子数据规则
iTi-Flask 不提供系统 seed。
业务 seed 放在业务项目。
系统 seed 放在 `iti-system`
seed 代码必须:
- 只用 Python seed。
- 幂等。
- 按唯一键 upsert。
- 不删除用户数据。
- 不重置已有管理员密码。
- 输出变更摘要。
运行时数据库不是源码资产。
## 生成物
仓库不应跟踪:
- 运行时数据库。
- 前端构建产物。
- `__pycache__`
- 测试缓存。
- 覆盖率产物。
## SPA 承载
框架内置 SPA 承载能力,但默认关闭。
业务项目可以配置:
```python
FRONTEND_ENABLED = True
FRONTEND_PATH = "/path/to/business/dist"
```
`FRONTEND_PATH` 指向业务项目自己的前端构建目录。
相对路径按业务项目配置里的 `BASE_DIR` 解析。
框架不会内置 `static/dist`

@ -1,871 +0,0 @@
# HTTP 响应包装工具使用指南
## 📖 概述
本工具库提供统一的 API 响应格式包装函数和分页工具,基于 APIFlask 框架设计,简化 API 开发流程。
**核心特性:**
- ✅ 统一返回格式:`{data, code, message}`
- ✅ 智能分页支持:自动识别 SQLAlchemy Pagination 对象
- ✅ 灵活调用方式:支持多种参数传递方式
- ✅ 完整类型提示TypeScript 级别的类型安全
- ✅ 参考 APIFlask 标准:与框架保持一致
---
## 📦 安装位置
```
src/applications/common/utils/http.py
```
---
## 🎯 核心函数
### 1. `success()` - 成功响应
**签名:**
```python
def success(data: Any = None, message: str = '成功', code: int = 200) -> dict
```
**参数:**
- `data`:返回数据(任意类型)
- `message`:提示信息(默认 `'成功'`
- `code`:业务状态码(默认 `200`
**返回格式:**
```json
{
"data": <返回数据>,
"code": 200,
"message": "成功"
}
```
**示例:**
```python
from iti.applications.common.utils import success
from iti.applications.extensions.http import BaseResponse
@app.get('/api/users/<int:user_id>')
@app.output(BaseResponse)
def get_user(user_id):
user = User.query.get(user_id)
if not user:
return fail('用户不存在', code=404)
# 返回单个对象
return success({
'id': user.id,
'name': user.name,
'email': user.email
})
@app.get('/api/products')
@app.output(BaseResponse)
def get_products():
products = Product.query.limit(10).all()
# 返回列表
return success(
[{'id': p.id, 'name': p.name} for p in products],
message='获取产品列表成功'
)
```
---
### 2. `fail()` - 失败响应
**签名:**
```python
def fail(message: str = '操作失败', code: int = 500, data: Any = None) -> dict
```
**参数:**
- `message`:错误信息
- `code`:业务错误码(默认 `500`
- `data`:额外数据(如验证错误详情)
**特点:**
- ⚠️ **HTTP 状态码保持 200**,由前端根据 `code` 字段判断业务状态
- 💡 适用于统一错误处理,避免 HTTP 层面的错误拦截
**返回格式:**
```json
{
"data": null,
"code": 500,
"message": "操作失败"
}
```
**示例:**
```python
from iti.applications.common.utils import fail
@app.post('/api/users')
@app.output(BaseResponse)
def create_user():
username = request.json.get('username')
# 参数验证失败
if not username:
return fail('用户名不能为空', code=400)
# 资源不存在
if User.query.filter_by(username=username).first():
return fail('用户名已存在', code=409)
# 服务器错误
try:
user = User(username=username)
db.session.add(user)
db.session.commit()
except Exception as e:
return fail(f'创建失败: {str(e)}', code=500)
return success(user_to_dict(user), message='创建成功')
@app.post('/api/login')
@app.output(BaseResponse)
def login():
data = request.json
# 验证失败,返回详细错误
errors = validate_login(data)
if errors:
return fail(
message='验证失败',
code=422,
data=errors # {'username': ['必填项'], 'password': ['长度不足']}
)
# ... 登录逻辑
```
---
### 3. `page()` - 分页响应
**签名:**
```python
def page(
items: Union[list, Any],
pagination: Union[dict, Any, None] = None,
message: str = '成功',
code: int = 200
) -> dict
```
**支持三种调用方式:**
#### **方式 1传入 SQLAlchemy Pagination 对象(最简)**
```python
from iti.applications.common.utils import page
@app.get('/api/users')
@app.output(BaseResponse)
def get_users():
page_num = request.args.get('page', 1, type=int)
page_size = request.args.get('size', 10, type=int)
# SQLAlchemy 分页查询
db_pagination = User.query.paginate(page=page_num, per_page=page_size)
# ✅ 直接传入 Pagination 对象,自动解析
return page(db_pagination, message='获取用户列表成功')
```
**返回格式:**
```json
{
"data": {
"items": [
{"id": 1, "name": "Alice"},
{"id": 2, "name": "Bob"}
],
"page": {
"page": 1,
"size": 10,
"pages": 10,
"total": 100,
"current": "http://api.example.com/api/users?page=1&size=10",
"next": "http://api.example.com/api/users?page=2&size=10",
"prev": null,
"first": "http://api.example.com/api/users?page=1&size=10",
"last": "http://api.example.com/api/users?page=10&size=10"
}
},
"code": 200,
"message": "获取用户列表成功"
}
```
---
#### **方式 2传入数据列表 + Pagination 对象**
适用于需要先序列化数据的场景:
```python
from iti.applications.common.utils import page
from iti.applications.schemas.user import UserSchema
@app.get('/api/users')
@app.output(BaseResponse)
def get_users():
db_pagination = User.query.paginate(page=1, per_page=10)
# 先序列化数据(使用 Marshmallow Schema
users = UserSchema(many=True).dump(db_pagination.items)
# ✅ 传入序列化后的数据 + Pagination 对象
return page(users, db_pagination, message='获取用户列表成功')
```
---
#### **方式 3手动构建分页信息**
适用于非 SQLAlchemy 数据源(如 Redis、外部 API
```python
from iti.applications.common.utils import page, pagination_builder
@app.get('/api/stats')
@app.output(BaseResponse)
def get_stats():
# 从 Redis 获取数据
all_data = redis_client.lrange('stats', 0, -1)
page_num = request.args.get('page', 1, type=int)
page_size = request.args.get('size', 20, type=int)
# 手动分页
start = (page_num - 1) * page_size
end = start + page_size
items = all_data[start:end]
# ✅ 手动构建分页信息
pagination_info = pagination_builder(
None,
page=page_num,
size=page_size,
total=len(all_data)
)
return page(items, pagination_info, message='获取统计数据成功')
```
---
## 🔧 辅助函数
### `pagination_builder()` - 分页信息构建器
**签名:**
```python
def pagination_builder(
pagination: Any,
*,
page: Optional[int] = None,
size: Optional[int] = None,
total: Optional[int] = None,
pages: Optional[int] = None,
) -> dict
```
**参数说明:**
- `pagination`SQLAlchemy Pagination 对象 或 `None`
- `page`:当前页码(手动模式)
- `size`:每页数量(手动模式)
- `total`:总记录数(手动模式)
- `pages`:总页数(可选,自动计算)
**注意:**
- 第一个参数后使用 `*`,强制后续参数必须使用关键字传参
- 参考 APIFlask 的 `helpers.py` 实现
**示例 1自动模式SQLAlchemy Pagination**
```python
from iti.applications.common.utils import pagination_builder
@app.get('/api/products')
def get_products():
db_pagination = Product.query.paginate(page=1, per_page=10)
# ✅ 自动从 Pagination 对象提取信息
pagination_info = pagination_builder(db_pagination)
return {
'items': db_pagination.items,
'pagination': pagination_info
}
```
**示例 2手动模式自定义数据源**
```python
from iti.applications.common.utils import pagination_builder
@app.get('/api/external-data')
def get_external_data():
# 从外部 API 获取数据
response = requests.get('https://api.example.com/data')
total_count = response.headers.get('X-Total-Count', 0)
items = response.json()
# ✅ 手动构建分页信息
pagination_info = pagination_builder(
None, # 第一个参数传 None
page=1,
size=20,
total=int(total_count)
)
return page(items, pagination_info)
```
---
## 📊 Schema 定义
### `PaginationSchema` - 分页信息 Schema
```python
class PaginationSchema(Schema):
"""自定义分页信息 Schema"""
page = Integer() # 当前页码
size = Integer() # 每页数量(重命名自 per_page
pages = Integer() # 总页数
total = Integer() # 总记录数
current = URL() # 当前页URL
next = URL() # 下一页URL
prev = URL() # 上一页URL
first = URL() # 首页URL
last = URL() # 末页URL
```
**特点:**
- ✅ 与 APIFlask 原生 `PaginationSchema` 保持一致
- ✅ 仅将 `per_page` 重命名为 `size`
---
### `PageDataSchema` - 分页数据 Schema
```python
class PageDataSchema(Schema):
"""分页数据包装 Schema"""
items = List(Nested(Field())) # 数据列表
page = Nested(PaginationSchema) # 分页信息(字段名为 page
```
**使用示例:**
```python
from iti.applications.common.utils import PageDataSchema
# 在路由中使用(可选,主要用于文档生成)
@app.get('/api/users')
@app.doc(responses={200: PageDataSchema})
def get_users():
...
```
---
## 🎨 完整示例
### 示例 1用户管理 CRUD
```python
from flask import request
from iti.applications.common.utils import success, fail, page
from iti.applications.extensions.http import BaseResponse
from my_project.models.user import User
from my_project.schemas.user import UserSchema, UserCreateSchema
# ========== 列表(分页) ==========
@app.get('/api/users')
@app.output(BaseResponse)
def get_users():
"""获取用户列表"""
page_num = request.args.get('page', 1, type=int)
page_size = request.args.get('size', 10, type=int)
db_pagination = User.query.paginate(page=page_num, per_page=page_size)
return page(db_pagination, message='获取用户列表成功')
# ========== 详情 ==========
@app.get('/api/users/<int:user_id>')
@app.output(BaseResponse)
def get_user(user_id):
"""获取用户详情"""
user = User.query.get(user_id)
if not user:
return fail('用户不存在', code=404)
return success(UserSchema().dump(user))
# ========== 创建 ==========
@app.post('/api/users')
@app.input(UserCreateSchema)
@app.output(BaseResponse)
def create_user(data):
"""创建用户"""
# 检查用户名是否存在
if User.query.filter_by(username=data['username']).first():
return fail('用户名已存在', code=409)
try:
user = User(**data)
db.session.add(user)
db.session.commit()
return success(
UserSchema().dump(user),
message='创建成功',
code=201
)
except Exception as e:
db.session.rollback()
return fail(f'创建失败: {str(e)}', code=500)
# ========== 更新 ==========
@app.patch('/api/users/<int:user_id>')
@app.input(UserCreateSchema)
@app.output(BaseResponse)
def update_user(user_id, data):
"""更新用户"""
user = User.query.get(user_id)
if not user:
return fail('用户不存在', code=404)
try:
for key, value in data.items():
setattr(user, key, value)
db.session.commit()
return success(UserSchema().dump(user), message='更新成功')
except Exception as e:
db.session.rollback()
return fail(f'更新失败: {str(e)}', code=500)
# ========== 删除 ==========
@app.delete('/api/users/<int:user_id>')
@app.output(BaseResponse)
def delete_user(user_id):
"""删除用户"""
user = User.query.get(user_id)
if not user:
return fail('用户不存在', code=404)
try:
db.session.delete(user)
db.session.commit()
return success(None, message='删除成功')
except Exception as e:
db.session.rollback()
return fail(f'删除失败: {str(e)}', code=500)
```
---
### 示例 2复杂查询与筛选
```python
from sqlalchemy import and_, or_
from iti.applications.common.utils import page, pagination_builder
@app.get('/api/products')
@app.output(BaseResponse)
def get_products():
"""获取产品列表(支持筛选、搜索、排序)"""
# 获取查询参数
page_num = request.args.get('page', 1, type=int)
page_size = request.args.get('size', 20, type=int)
category = request.args.get('category')
search = request.args.get('search')
sort_by = request.args.get('sort', 'created_at')
order = request.args.get('order', 'desc')
# 构建查询
query = Product.query
# 筛选条件
if category:
query = query.filter(Product.category == category)
# 搜索条件
if search:
query = query.filter(
or_(
Product.name.contains(search),
Product.description.contains(search)
)
)
# 排序
if order == 'desc':
query = query.order_by(getattr(Product, sort_by).desc())
else:
query = query.order_by(getattr(Product, sort_by).asc())
# 分页
db_pagination = query.paginate(page=page_num, per_page=page_size)
# 序列化
products = ProductSchema(many=True).dump(db_pagination.items)
return page(products, db_pagination, message='获取产品列表成功')
```
---
### 示例 3聚合统计非 ORM 分页)
```python
from sqlalchemy import func
from iti.applications.common.utils import page, pagination_builder
@app.get('/api/stats/daily')
@app.output(BaseResponse)
def get_daily_stats():
"""获取每日统计数据"""
page_num = request.args.get('page', 1, type=int)
page_size = request.args.get('size', 30, type=int)
# 聚合查询(不使用 ORM 分页)
query = db.session.query(
func.date(Order.created_at).label('date'),
func.count(Order.id).label('order_count'),
func.sum(Order.amount).label('total_amount')
).group_by(func.date(Order.created_at))
# 获取总数
total = query.count()
# 手动分页
offset = (page_num - 1) * page_size
items = query.offset(offset).limit(page_size).all()
# 格式化数据
stats = [
{
'date': str(item.date),
'order_count': item.order_count,
'total_amount': float(item.total_amount or 0)
}
for item in items
]
# 手动构建分页信息
pagination_info = pagination_builder(
None,
page=page_num,
size=page_size,
total=total
)
return page(stats, pagination_info, message='获取统计数据成功')
```
---
## ⚙️ 配置说明
### BaseResponse Schema
`applications/extensions/http.py` 中定义:
```python
from apiflask import Schema
from apiflask.fields import Integer, String, Field
class BaseResponse(Schema):
"""统一响应格式 Schema"""
data = Field()
code = Integer()
message = String()
def init_http(app):
# 配置 APIFlask 使用自定义响应格式
app.config["BASE_RESPONSE_SCHEMA"] = BaseResponse
app.config["BASE_RESPONSE_DATA_KEY"] = "data"
```
---
## 🔍 URL 生成规则
分页 URL 自动生成逻辑:
1. **获取当前请求的 `base_url`**`http://api.example.com/api/users`
2. **复制查询参数**`request.args.copy()`
3. **更新分页参数**
- `page`: 页码
- `size`: 每页数量(注意:使用 `size` 而非 `per_page`
4. **构建完整 URL**`base_url + ? + urlencode(args)`
**示例:**
请求 `/api/users?category=admin&page=2&size=10` 时生成的 URL
```json
{
"current": "http://api.example.com/api/users?category=admin&page=2&size=10",
"next": "http://api.example.com/api/users?category=admin&page=3&size=10",
"prev": "http://api.example.com/api/users?category=admin&page=1&size=10",
"first": "http://api.example.com/api/users?category=admin&page=1&size=10",
"last": "http://api.example.com/api/users?category=admin&page=10&size=10"
}
```
---
## 📝 最佳实践
### 1. 统一错误码规范
建议定义错误码常量:
```python
# applications/common/constants.py
class ErrorCode:
"""业务错误码"""
SUCCESS = 200
BAD_REQUEST = 400
UNAUTHORIZED = 401
FORBIDDEN = 403
NOT_FOUND = 404
CONFLICT = 409
UNPROCESSABLE_ENTITY = 422
INTERNAL_SERVER_ERROR = 500
# 使用
from iti.applications.common.utils import fail
from iti.applications.common.constants import ErrorCode
return fail('用户不存在', code=ErrorCode.NOT_FOUND)
```
---
### 2. 结合 Marshmallow Schema
```python
from iti.applications.schemas.user import UserSchema
from iti.applications.common.utils import success
@app.get('/api/users/<int:user_id>')
@app.output(BaseResponse)
def get_user(user_id):
user = User.query.get_or_404(user_id)
# ✅ 使用 Schema 序列化
user_data = UserSchema().dump(user)
return success(user_data)
```
---
### 3. 分页参数验证
```python
from iti.applications.common.utils import fail, page
@app.get('/api/users')
@app.output(BaseResponse)
def get_users():
page_num = request.args.get('page', 1, type=int)
page_size = request.args.get('size', 10, type=int)
# 验证分页参数
if page_num < 1:
return fail('页码必须大于 0', code=400)
if page_size < 1 or page_size > 100:
return fail('每页数量必须在 1-100 之间', code=400)
db_pagination = User.query.paginate(page=page_num, per_page=page_size)
return page(db_pagination)
```
---
### 4. 异常统一处理
```python
from iti.applications.common.utils import fail
@app.errorhandler(404)
def handle_404(error):
return fail('资源不存在', code=404)
@app.errorhandler(500)
def handle_500(error):
return fail('服务器内部错误', code=500)
@app.errorhandler(Exception)
def handle_exception(error):
app.logger.error(f'未处理的异常: {error}')
return fail(str(error), code=500)
```
---
## 🆚 对比 APIFlask 原生实现
| 特性 | APIFlask 原生 | 本工具库 |
|------|--------------|---------|
| 分页参数名 | `per_page` | `size` ✅ |
| 返回格式 | 灵活 | 统一 `{data, code, message}` ✅ |
| 错误处理 | HTTP 状态码 | 业务 `code` 字段 ✅ |
| 智能识别 | 需手动处理 | 自动识别 Pagination 对象 ✅ |
| URL 生成 | 手动 | 自动生成 ✅ |
---
## 🧪 测试
### 运行测试
```bash
# 运行 HTTP 工具测试
uv run --extra dev pytest tests/test_http_utils.py -v
# 运行所有测试
uv run --extra dev pytest
# 测试覆盖率
uv run --extra dev pytest --cov=iti --cov-report=html
```
### 测试统计
- **测试用例数量**: 33 个
- **测试分类**:
- `success()` 函数测试: 6 个
- `fail()` 函数测试: 5 个
- `pagination_builder()` 测试: 6 个
- `page()` 函数测试: 7 个
- Schema 定义测试: 2 个
- Flask 集成测试: 4 个
- 边界情况测试: 5 个
### 测试覆盖范围
- ✅ 基础功能测试
- ✅ 参数验证测试
- ✅ 默认值测试
- ✅ 边界情况测试
- ✅ Flask 应用集成测试
- ✅ Schema 定义测试
- ✅ 请求上下文处理测试
### 添加自定义测试
`tests/test_http_utils.py` 中添加测试:
```python
import pytest
from iti.applications.common.utils import success
def test_my_custom_case():
"""测试自定义场景"""
result = success({'key': 'value'})
assert result['code'] == 200
```
---
## 🐛 常见问题
### Q1: 为什么错误也返回 HTTP 200
**A:** 这是一种常见的 API 设计模式,优点:
- 前端统一处理,不需要捕获 HTTP 异常
- 避免浏览器/代理对非 200 状态码的拦截
- 业务状态由 `code` 字段表示,更清晰
如需返回 HTTP 错误状态码,可以手动返回元组:
```python
return fail('未找到', code=404), 404 # HTTP 404
```
---
### Q2: 如何自定义分页 URL 生成逻辑?
**A:** 修改 `_generate_page_url()` 函数:
```python
def _generate_page_url(page_num, page_size):
if page_num is None:
return None
# 自定义逻辑
return f"https://custom-domain.com/api?p={page_num}&s={page_size}"
```
---
### Q3: 分页信息中的 URL 字段可以去掉吗?
**A:** 可以。修改 `pagination_builder()` 返回值:
```python
return {
'page': page,
'size': size,
'pages': pages,
'total': total,
# 注释掉 URL 字段
# 'current': current_url,
# 'next': next_url,
# ...
}
```
---
## 📚 参考资料
- [APIFlask 官方文档](https://apiflask.com/)
- [APIFlask helpers.py 源码](https://github.com/apiflask/apiflask/blob/main/src/apiflask/helpers.py)
- [Flask-SQLAlchemy Pagination](https://flask-sqlalchemy.palletsprojects.com/en/3.0.x/pagination/)
---
## 📅 更新日志
- **v1.0.0** (2024-10-14)
- 初始版本
- 实现 `success()`, `fail()`, `page()` 函数
- 实现 `pagination_builder()` 分页构建器
- 支持智能识别 SQLAlchemy Pagination 对象
- 自动生成分页 URL
- 参数重命名:`per_page` → `size`
---
**编写人员:** AI Assistant
**最后更新:** 2024-10-14
**版本:** 1.0.0

@ -1,305 +0,0 @@
# 限流器配置说明
## 📋 概述
项目使用 Flask-Limiter 实现 API 限流功能,支持基于 Flask 配置的动态设置。
## 🔧 配置项
### 基础配置
| 配置项 | 类型 | 默认值 | 说明 |
|--------|------|--------|------|
| `RATELIMIT_ENABLED` | bool | `True` | 是否启用限流 |
| `RATELIMIT_STORAGE_URL` | str | `"memory://"` | 存储后端 URI |
| `RATELIMIT_DEFAULT` | str | `"200 per hour"` | 默认限流规则 |
### 环境配置
#### 开发环境 (DevConfig)
```python
RATELIMIT_ENABLED = True
RATELIMIT_STORAGE_URL = "memory://"
RATELIMIT_DEFAULT = "1000 per hour" # 宽松限制
```
#### 测试环境 (TestConfig)
```python
RATELIMIT_ENABLED = False # 禁用限流
```
#### 生产环境 (ProdConfig)
```python
RATELIMIT_ENABLED = True
RATELIMIT_STORAGE_URL = os.getenv("REDIS_URL", "memory://")
RATELIMIT_DEFAULT = "100 per hour" # 严格限制
```
## 🚀 使用方式
### 1. 基本使用
```python
from flask import Flask, jsonify
from iti.applications.extensions.limit import limiter
app = Flask(__name__)
@app.route('/api/data')
@limiter.limit("10 per minute") # 每分钟10次
def get_data():
return jsonify({'data': 'some data'})
```
### 2. 使用默认限流
```python
@app.route('/api/public')
def public_api():
# 使用配置中的默认限流规则
return jsonify({'message': 'Public API'})
```
### 3. 豁免限流
```python
@app.route('/api/health')
@limiter.exempt # 豁免限流
def health_check():
return jsonify({'status': 'healthy'})
```
### 4. 自定义键函数
```python
@app.route('/api/user-data')
@limiter.limit("50 per hour", key_func=lambda: "user_123")
def user_data():
return jsonify({'user': 'user_123', 'data': 'user data'})
```
## 🔧 配置示例
### 使用 .env 文件
```env
# .env
RATELIMIT_ENABLED=true
RATELIMIT_STORAGE_URL=memory://
RATELIMIT_DEFAULT=200 per hour
# .env.prod
RATELIMIT_ENABLED=true
RATELIMIT_STORAGE_URL=redis://localhost:6379/0
RATELIMIT_DEFAULT=100 per hour
```
### 使用环境变量
```bash
# Windows
set RATELIMIT_ENABLED=true
set RATELIMIT_STORAGE_URL=redis://localhost:6379/0
set RATELIMIT_DEFAULT=100 per hour
# Linux/macOS
export RATELIMIT_ENABLED=true
export RATELIMIT_STORAGE_URL=redis://localhost:6379/0
export RATELIMIT_DEFAULT=100 per hour
```
## 📊 存储后端
### 内存存储 (开发环境)
```python
RATELIMIT_STORAGE_URL = "memory://"
```
- ✅ 简单快速
- ❌ 重启后数据丢失
- ❌ 不支持多进程
### Redis 存储 (生产环境)
```python
RATELIMIT_STORAGE_URL = "redis://localhost:6379/0"
```
- ✅ 持久化存储
- ✅ 支持多进程
- ✅ 高性能
- ⚠️ 需要 Redis 服务
### 其他存储选项
```python
# Memcached
RATELIMIT_STORAGE_URL = "memcached://localhost:11211"
# 文件存储
RATELIMIT_STORAGE_URL = "file:///tmp/limiter.db"
```
## 🎯 限流规则语法
### 时间单位
- `second` / `sec` - 秒
- `minute` / `min` - 分钟
- `hour` - 小时
- `day` - 天
- `month` - 月
- `year` - 年
### 示例规则
```python
"10 per second" # 每秒10次
"100 per minute" # 每分钟100次
"1000 per hour" # 每小时1000次
"10000 per day" # 每天10000次
```
### 复杂规则
```python
"10 per minute; 100 per hour" # 每分钟10次每小时100次
"5 per minute; 50 per hour; 500 per day" # 多级限流
```
## 🔍 调试和监控
### 检查限流状态
```python
@app.route('/api/limiter-status')
def limiter_status():
if limiter is None:
return jsonify({'enabled': False})
return jsonify({
'enabled': True,
'default_limits': limiter.default_limits,
'storage_uri': limiter.storage_uri,
'key_func': limiter.key_func.__name__
})
```
### 查看限流信息
```python
from flask_limiter.util import get_remote_address
# 获取当前用户的限流信息
limits = limiter.get_window_stats(get_remote_address())
print(f"剩余请求: {limits[1] - limits[0]}")
```
## 🛠️ 故障排查
### 问题 1: 限流不生效
**检查步骤:**
1. 确认 `RATELIMIT_ENABLED=True`
2. 检查存储后端是否可用
3. 验证限流规则语法
**调试方法:**
```python
print(f"限流启用: {app.config.get('RATELIMIT_ENABLED')}")
print(f"存储URI: {app.config.get('RATELIMIT_STORAGE_URL')}")
print(f"默认限制: {app.config.get('RATELIMIT_DEFAULT')}")
```
### 问题 2: Redis 连接失败
**检查步骤:**
1. 确认 Redis 服务运行
2. 检查连接字符串格式
3. 验证网络连接
**解决方案:**
```python
# 使用内存存储作为后备
RATELIMIT_STORAGE_URL = os.getenv("REDIS_URL", "memory://")
```
### 问题 3: 限流过于严格
**调整方法:**
```python
# 在配置中调整
RATELIMIT_DEFAULT = "1000 per hour" # 更宽松的限制
# 或在路由中覆盖
@limiter.limit("500 per hour")
def my_endpoint():
pass
```
## 📚 最佳实践
### 1. 环境配置
```python
# 开发环境 - 宽松限制
RATELIMIT_DEFAULT = "1000 per hour"
# 测试环境 - 禁用限流
RATELIMIT_ENABLED = False
# 生产环境 - 严格限制
RATELIMIT_DEFAULT = "100 per hour"
RATELIMIT_STORAGE_URL = "redis://localhost:6379/0"
```
### 2. 路由设计
```python
# 公共 API - 宽松限制
@app.route('/api/public')
@limiter.limit("100 per hour")
def public_api():
pass
# 私有 API - 严格限制
@app.route('/api/private')
@limiter.limit("10 per hour")
def private_api():
pass
# 健康检查 - 豁免限流
@app.route('/health')
@limiter.exempt
def health():
pass
```
### 3. 错误处理
```python
from flask_limiter.errors import RateLimitExceeded
@app.errorhandler(RateLimitExceeded)
def handle_rate_limit_exceeded(e):
return jsonify({
'error': 'Rate limit exceeded',
'message': 'Too many requests',
'retry_after': e.retry_after
}), 429
```
## 🎉 总结
现在您的项目:
1. ✅ **支持基于配置的限流** - 从 Flask 配置中读取设置
2. ✅ **环境特定配置** - 不同环境使用不同的限流策略
3. ✅ **灵活的存储后端** - 支持内存、Redis 等
4. ✅ **易于调试** - 提供状态检查和错误处理
5. ✅ **生产就绪** - 支持高并发和持久化存储
**开始使用:**
```python
from iti.applications.extensions.limit import limiter
@app.route('/api/data')
@limiter.limit("10 per minute")
def get_data():
return jsonify({'data': 'some data'})
```
就这么简单!🚀

@ -1,44 +1,37 @@
# 数据库迁移
## 规则
iTi-Flask 只初始化 Flask-Migrate。
业务表由业务项目自己维护 migration。
每个业务项目只保留一条 Alembic migration 流。
## 基本规则
`migrations/versions` 必须提交到 Git。
运行时数据库文件不提交。
- 每个业务项目只保留一条 Alembic migration 流。
- `migrations/versions` 必须提交到 Git。
- 运行时数据库文件不提交。
- 已发布的 migration 不回头修改。
- 生产只执行 `db upgrade`
生产只从 `main` 升级:
升级数据库
```bash
flask db upgrade
uv run python -m flask --app app.py db upgrade
```
## 分支模型
推荐分支模型:
- `main`:生产发布分支。
- `dev`:集成分支。
- `user/<name>`:个人开发分支。
生成 migration
`dev` 合并到 `main` 前必须只有一个 migration head。
## 文件名
```bash
uv run python -m flask --app app.py db migrate -m "alice add workorder priority"
```
业务 migration 文件名使用日期、时间、revision 和 message slug。
## 文件命名
`migrations/alembic.ini`
模板已配置 migration 文件名格式
```ini
file_template = %%(year)d%%(month).2d%%(day).2d_%%(hour).2d%%(minute).2d_%%(rev)s_%%(slug)s
```
message 第一个词是作者名。
后面自由描述。
```bash
uv run python -m flask --app app.py db migrate -m "alice add workorder priority"
```
message 第一个词写作者名,后面写变更说明。
生成示例:
@ -46,53 +39,37 @@ uv run python -m flask --app app.py db migrate -m "alice add workorder priority"
20260508_1430_9f8a7c6d2e1a_alice_add_workorder_priority.py
```
## 合并多个 Head
## 多个 head
个人分支合入 `dev` 前执行
合并分支后检查
```bash
git fetch
git merge origin/dev
uv run python -m flask --app app.py db heads
uv run python -m flask --app app.py db current
uv run python -m flask --app app.py db upgrade
```
如果出现多个 head
出现多个 head 时合并
```bash
uv run python -m flask --app app.py db merge heads -m "alice merge heads before release"
uv run python -m flask --app app.py db upgrade
```
`dev` 应保持一个 head。
发布前应保持一个 head。
## 手工数据库变更
## 模型导入
直接修改生产库结构不是正常流程。
业务项目通过 `model_imports` 让 Alembic 看到模型:
如果发生紧急手工变更,必须当天补 migration并把目标库恢复到一致的 Alembic 版本。
```python
from iti.applications import create_app
from my_app.models import import_models
已经合并并部署过的 migration 不允许回头修改。
## iTi-Flask 迁移
iTi-Flask 不提供业务表迁移和系统表迁移。
框架只初始化 Flask-Migrate。
业务项目自己的模型由业务项目生成 migration。
## 系统迁移
需要用户、角色、菜单等系统能力时,业务项目应依赖 `iti-system`
`iti-system` 提供系统 migration 同步命令:
```bash
uv run python -m flask --app app.py iti-system migrations sync
uv run python -m flask --app app.py db upgrade
app = create_app(model_imports=[import_models])
```
`iti-system` migration 是全量系统表集合。
不按 auth、user、role 等能力拆分。
实际项目如果只需要 ERP Service 这类轻服务,直接不依赖 `iti-system`
## 手工变更
不要直接修改生产库结构。
发生紧急手工变更后,应补 migration并让目标库回到一致的 Alembic 版本。

@ -1,62 +1,56 @@
# 模块协议
模块用于业务项目内的代码边界。
模块运行在同一个 Flask 进程内,不是独立服务。
模块运行在同一个 Flask 进程内。
它不是独立服务。
## 适用场景
## 适用范围
适合做模块:
适合做模块的能力:
- 业务项目内部的业务域。
- 需要独立路由、service、schema 的功能。
- 共享同一个部署单元和数据库的功能。
- 某个业务项目内部的业务域。
- 需要独立路由、service 和 model 目录的功能。
- 多人并行开发,但共享同一个部署单元和数据库。
不适合做模块:
不适合做模块的能力:
- 需要独立部署的能力。
- 有独立数据源的能力。
- 多个项目跨进程复用的能力。
- 需要被多个项目跨进程复用。
- 有独立数据源。
- 有独立发布节奏。
- 业务项目不应该理解其内部细节。
这类能力应通过 HTTP JSON 服务提供。
这类能力应拆成 HTTP JSON 服务。
ERP Service 属于服务候选。
## 注册方式
业务项目在 `app.py` 传入模块实例:
## 注册模块
```python
from iti.applications import create_app
from my_project.modules.example import ExampleModule
from my_app.modules.example.module import ExampleModule
app = create_app(modules=[ExampleModule()])
```
模块可以声明这些阶段
模块
```python
class ExampleModule:
name = "example"
def init_app(self, app):
...
pass
def register_commands(self, app):
...
pass
def register_routes(self, app):
...
pass
def register_permissions(self, app):
...
pass
def register_menu_seed(self, app):
...
pass
```
执行顺序固定
执行顺序:
1. `init_app`
2. `register_commands`
@ -64,9 +58,7 @@ class ExampleModule:
4. `register_permissions`
5. `register_menu_seed`
## 权限声明
模块通过注册表声明权限码:
## 权限元数据
```python
from iti.modules import ModulePermission, get_module_registry
@ -78,17 +70,15 @@ def register_permissions(self, app):
ModulePermission(
code="example:item:list",
name="示例列表",
description="查看示例模块数据",
description="查看示例数据",
)
)
```
权限码本身不单独落表
实际授权仍然来自菜单 `auth_code`
框架只收集权限元数据
授权写库由业务项目或系统业务包处理
## 菜单 Seed
模块可以声明菜单 seed
## 菜单元数据
```python
from iti.applications.common.enums import MenuTypeEnum
@ -111,10 +101,8 @@ def register_menu_seed(self, app):
)
```
菜单 seed 只是模块元数据。
iTi-Flask 只负责收集,不负责写入系统表。
如果业务项目引入 `iti-system`,可以由 `iti-system` seed 把模块菜单写入 `sys_menu`,并默认绑定到 `ADMIN`
框架只收集菜单元数据。
是否写入数据库由业务项目决定。
## 边界
@ -122,16 +110,12 @@ iTi-Flask 只负责收集,不负责写入系统表。
- 注册蓝图。
- 注册 CLI 命令。
- 声明权限。
- 声明菜单 seed。
- 声明权限和菜单元数据。
- 使用业务项目自己的 model。
模块不应
模块不应:
- 修改框架内部实现。
- 直接导入其它模块内部 model 或 service。
- 直接导入其它模块内部 model 或 service。
- 自建独立 migration 流。
- 在系统 seed 中写业务数据。
业务 model 仍然集中在业务项目 `models/` 下。
整个业务项目只保留一条 migration 流。
- 在框架包里写业务数据。

@ -1,20 +1,26 @@
# iTi-Flask 文档
## 架构与边界
iTi-Flask 文档只描述框架自身。
- [框架边界](FRAMEWORK_BOUNDARY.md)
- [架构重构计划](ARCHITECTURE_REFACTOR_PLAN.md)
- [架构](ARCHITECTURE.md)
- [配置](CONFIGURATION.md)
- [模块协议](MODULES.md)
- [ERP Service 契约草案](ERP_SERVICE.md)
## 工程规则
- [数据库迁移](MIGRATIONS.md)
- [种子数据](SEEDS.md)
- [服务客户端](SERVICE_CLIENT.md)
- [任务运行器](TASKS.md)
- [数据库迁移](MIGRATIONS.md)
- [种子数据](SEEDS.md)
- [Copier 模板](COPIER_TEMPLATE.md)
## 常用命令
```bash
uv sync --extra dev
uv run --extra dev pytest
uv run --extra dev mypy
```
## 既有工具文档
生成业务项目:
- [HTTP 响应工具](HTTP_RESPONSE_UTILS.md)
- [限流配置](LIMITER_CONFIG.md)
```bash
uvx copier copy ./copier-template ../my-business-app
```

@ -1,12 +1,7 @@
# 种子数据
iTi-Flask 不提供系统 seed。
seed 分三类:
- 框架 seed当前没有。
- 系统 seed`iti-system` 提供。
- 业务 seed由业务项目自己维护。
iTi-Flask 不提供框架 seed。
业务 seed 由业务项目维护。
## 规则
@ -15,42 +10,44 @@ seed 代码必须:
- 幂等。
- 可重复执行。
- 按唯一键 upsert。
- 不做破坏性操作。
- 不删除用户数据。
- 不替代 migration 修改表结构。
seed 代码不得
seed 适合写
- 删除用户数据。
- 重置已有管理员密码。
- 写入与当前包无关的数据。
- 替代 migration 修改表结构。
- 业务默认配置。
- 业务字典。
- 演示数据。
## 系统 Seed
seed 不适合写:
需要用户、角色、菜单等系统能力时,业务项目应依赖 `iti-system`
- 框架内部数据。
- 其它包的数据。
- 运行时数据库快照。
命令:
## 业务命令
```bash
uv run python -m flask --app app.py iti-system seed system
```
业务项目可以在模块中注册自己的 CLI 命令:
`iti-system` seed 只写系统域初始数据。
引入即全量写入。
```python
import click
## 业务 Seed
业务项目可以自行提供 seed 命令。
class ExampleModule:
name = "example"
业务 seed 可以写:
def register_commands(self, app):
@click.command("seed-example")
def seed_example():
click.echo("seeded")
- 业务字典。
- 业务默认配置。
- 业务演示数据。
app.cli.add_command(seed_example)
```
业务 seed 不应写
执行
- 框架内部数据。
- 其它服务的数据。
- ERP 外部系统数据。
```bash
uv run python -m flask --app app.py seed-example
```
运行时数据库文件不是 seed 文件,也不提交
需要系统业务 seed 时,查看 `iTi-System` 文档

@ -1,42 +1,36 @@
# 服务客户端
iTi-Flask 提供轻量 HTTP JSON 服务客户端。
iTi-Flask 提供同步 HTTP JSON 服务客户端。
它用于业务项目调用独立服务。
它用于调用 ERP 这类独立 Gateway 服务。
它不是 service mesh也不是服务发现系统。
## 能力
## 范围
支持:
第一版支持:
- HTTP JSON。
- 同步调用。
- base URL 配置。
- service token 鉴权。
- 超时。
- 保守重试。
- 按方法和状态码重试。
- 可选熔断。
- trace id 透传。
- `X-Trace-Id` 透传。
- 结构化调用日志。
- 测试用 mock transport。
不支持:
- async client。
- 服务发现。
- 负载均衡。
- gRPC。
- streaming。
- async client。
- OpenAPI client 生成。
## 配置
Flask 配置示例:
```python
SERVICES = {
"erp": {
"base_url": "http://iti-erp:8000",
"inventory": {
"base_url": "http://inventory.local",
"token": "change-me",
"timeout": {
"connect": 1.0,
@ -48,6 +42,7 @@ SERVICES = {
"attempts": 2,
"backoff": 0.2,
"statuses": [502, 503, 504],
"methods": ["GET", "HEAD", "OPTIONS"],
},
"circuit_breaker": {
"enabled": False,
@ -58,33 +53,46 @@ SERVICES = {
}
```
`base_url` 必填。
`token` 非空时,客户端会发送:
```http
Authorization: Bearer change-me
```
## 使用
```python
from iti.service_client import service_client
erp = service_client("erp")
inventory = service_client("inventory")
payload = erp.get("/erp/users/{id}", path={"id": user_id})
item = inventory.get("/items/{id}", path={"id": "A001"})
created = inventory.post("/items", json={"name": "demo"})
```
POST 默认不重试。
`GET`、`HEAD`、`OPTIONS` 默认可按配置重试。
`POST` 默认不重试。
需要强制重试:
```python
result = erp.post("/erp/sync/jobs", json={"kind": "users"})
inventory.post("/jobs", json={"kind": "sync"}, retry=True)
```
## 错误语义
## Trace ID
服务间 API 使用真实 HTTP 状态码。
客户端优先复用当前请求头里的 `X-Trace-Id`
没有请求上下文时自动生成。
客户端在以下情况抛出框架服务错误:
## 错误
- 缺少服务配置。
- 超时。
- 传输错误。
- 熔断打开。
- 非 2xx HTTP 响应。
- 期望 JSON 但响应不是合法 JSON。
客户端会抛出这些异常:
- `ServiceConfigError`
- `ServiceUnavailableError`
- `ServiceHTTPError`
后台 API 的 envelope 规则不适用于服务间 API。
非 2xx 响应会抛出 `ServiceHTTPError`
响应体为空时返回 `None`
`expect_json=False` 时返回原始 `httpx.Response`

@ -1,64 +1,73 @@
# 任务
# 任务运行器
iTi-Flask 提供单进程任务注册表和运行器。
它适合轻量定时任务和手动任务。
它用于轻量定时任务或手动触发任务。
它不是分布式任务平台。
## 范围
## 能力
支持:
- 任务注册。
- 手动触发。
- interval 调度。
- `interval` 调度。
- 简单 cron-like 调度。
- 单进程内防重复执行。
- 内存中的运行状态。
- 结构化日志。
- 内存运行记录。
不支持:
- 分布式锁。
- 多实例 exactly-once。
- 默认 Celery 或 RQ。
- 持久化队列存储。
- 持久化队列。
- Celery 或 RQ 集成。
多进程部署时,只在一个专用进程启用调度。
## 配置
默认不启动调度线程。
多进程生产部署时,只在一个专用进程中启用 scheduler。
```python
TASKS_ENABLED = True
```
## 使用
## 注册任务
```python
from iti.tasks import task_registry
def sync_users():
return {"synced": 10}
def rebuild_reports():
return {"rebuilt": 10}
task_registry.register(
name="erp.sync.users",
handler=sync_users,
name="reports.rebuild",
handler=rebuild_reports,
schedule="interval:600",
description="重建报表缓存",
)
```
手动触发
## 手动触发
```python
from iti.tasks import task_registry
run = task_registry.trigger("erp.sync.users")
run = task_registry.trigger("reports.rebuild")
print(run.status)
print(run.result)
```
## 调度格式
同一任务正在运行时,再次触发会返回 `skipped`
第一版支持的 schedule 格式:
## 调度格式
```text
interval:60
cron:*/10 * * * *
cron:* * * * *
```
内置 cron 支持刻意保持很小
复杂调度后续接专用 scheduler 集成
cron 解析只读取分钟字段
`cron:* * * * *` 等价于每 60 秒

@ -1,154 +0,0 @@
# 文件存储系统
## 概述
本存储系统采用策略模式设计,支持多种存储后端,包括本地存储和各大云服务商的对象存储服务。
## 支持的存储类型
- **本地存储 (local)** - 默认,无需额外依赖
- **阿里云OSS (aliyun_oss)** - 需要安装 `oss2`
- **腾讯云COS (tencent_cos)** - 需要安装 `cos-python-sdk-v5`
- **七牛云Kodo (qiniu_kodo)** - 需要安装 `qiniu`
- **华为云OBS (huawei_obs)** - 需要安装 `esdk-obs-python`
- **AWS S3 (aws_s3)** - 待实现
- **MinIO (minio)** - 待实现
## 安装依赖
根据需要安装对应的SDK
```bash
# 阿里云OSS
pip install oss2
# 腾讯云COS
pip install cos-python-sdk-v5
# 七牛云Kodo
pip install qiniu
# 华为云OBS
pip install esdk-obs-python
```
或一次性安装所有依赖:
```bash
pip install oss2 cos-python-sdk-v5 qiniu esdk-obs-python
```
## 配置说明
### 环境变量配置
`.env` 文件中配置对应的云存储凭证:
```bash
# 阿里云OSS
ALIYUN_OSS_ACCESS_KEY_ID=your_access_key_id
ALIYUN_OSS_ACCESS_KEY_SECRET=your_access_key_secret
ALIYUN_OSS_ENDPOINT=oss-cn-hangzhou.aliyuncs.com
ALIYUN_OSS_BUCKET=your_bucket_name
# 腾讯云COS
TENCENT_COS_SECRET_ID=your_secret_id
TENCENT_COS_SECRET_KEY=your_secret_key
TENCENT_COS_REGION=ap-guangzhou
TENCENT_COS_BUCKET=your_bucket_name
# 七牛云Kodo
QINIU_KODO_ACCESS_KEY=your_access_key
QINIU_KODO_SECRET_KEY=your_secret_key
QINIU_KODO_BUCKET=your_bucket_name
QINIU_KODO_DOMAIN=your_cdn_domain.com
# 华为云OBS
HUAWEI_OBS_ACCESS_KEY_ID=your_access_key_id
HUAWEI_OBS_SECRET_ACCESS_KEY=your_secret_access_key
HUAWEI_OBS_SERVER=obs.cn-north-4.myhuaweicloud.com
HUAWEI_OBS_BUCKET=your_bucket_name
```
### 应用配置
`config.py` 中修改默认存储类型:
```python
FILE_STORAGE = {
"DEFAULT_STORAGE_TYPE": "aliyun_oss", # 修改为你想要的默认存储
"LOCAL": {
"base_path": "/path/to/uploads",
},
"ALIYUN_OSS": {
# ...
},
}
```
## 使用方式
### 1. 全局默认存储
在配置文件中设置 `DEFAULT_STORAGE_TYPE`,所有未指定存储类型的文件将使用此存储。
### 2. 目录级存储
为特定目录设置 `default_storage_type`
```python
directory = SysFileDirectory(
name="用户头像",
path="/avatars",
default_storage_type="aliyun_oss" # 该目录下的文件使用阿里云OSS
)
```
### 3. 请求级存储
上传文件时通过参数指定:
```javascript
// 普通上传
formData.append('storageType', 'tencent_cos');
// TUS上传
uppy.setMeta({
storageType: 'qiniu_kodo'
});
```
## 存储优先级
请求参数 > 目录默认 > 全局默认
## 接口说明
所有存储适配器实现了统一的 `StorageInterface` 接口:
- `upload()` - 上传文件
- `append_chunk()` - 追加数据块用于TUS协议
- `download()` - 下载文件
- `delete()` - 删除文件
- `exists()` - 检查文件是否存在
- `get_url()` - 获取访问URL支持签名URL
## 注意事项
1. **TUS协议支持**
- 阿里云OSS、华为云OBS 支持原生追加写入
- 腾讯云COS、七牛云Kodo 使用读-改-写策略模拟追加(性能较低,不建议用于大文件)
2. **URL签名**
- 所有云存储都支持生成带签名的临时URL
- 设置 `expires=0` 可生成永久URL仅适用于公共读bucket
3. **秒传机制**
- 系统会检查文件哈希,相同存储类型下的重复文件自动复用
- 不同存储类型的相同文件会分别存储
4. **错误处理**
- SDK未安装时会抛出 `ImportError`
- 配置不完整时会抛出 `ValueError`
- 文件操作失败时会抛出相应异常

@ -1,296 +0,0 @@
# 测试文档
## 📊 测试概览
| 测试文件 | 测试数量 | 覆盖模块 | 状态 |
|---------|---------|---------|------|
| `test_config.py` | 7 | 配置管理 | ✅ 通过 |
| `test_limiter.py` | 6 | 限流器 | ✅ 通过 |
| `test_http_utils.py` | 33 | HTTP 响应工具 | ✅ 通过 |
| **总计** | **46** | - | **✅ 全部通过** |
---
## 🚀 运行测试
### 运行所有测试
```bash
uv run --extra dev pytest
```
### 运行特定测试文件
```bash
# 运行 HTTP 工具测试
uv run --extra dev pytest tests/test_http_utils.py
# 运行配置测试
uv run --extra dev pytest tests/test_config.py
# 运行限流器测试
uv run --extra dev pytest tests/test_limiter.py
```
### 运行特定测试类
```bash
# 运行 success() 函数测试
uv run --extra dev pytest tests/test_http_utils.py::TestSuccessFunction
# 运行分页构建器测试
uv run --extra dev pytest tests/test_http_utils.py::TestPaginationBuilder
```
### 运行特定测试用例
```bash
# 运行单个测试
uv run --extra dev pytest tests/test_http_utils.py::TestSuccessFunction::test_success_with_data
```
### 带详细输出
```bash
# 详细模式
uv run --extra dev pytest -v
# 超详细模式
uv run --extra dev pytest -vv
```
---
## 📈 测试覆盖率
### 生成覆盖率报告
```bash
# 运行测试并生成覆盖率报告
uv run --extra dev pytest --cov=iti --cov-report=html
```
### 查看覆盖率报告
覆盖率报告会生成在 `htmlcov/` 目录:
```bash
# Windows
start htmlcov/index.html
# macOS
open htmlcov/index.html
# Linux
xdg-open htmlcov/index.html
```
---
## 📝 测试文件说明
### `test_config.py` - 配置测试
**测试内容:**
- ✅ 开发环境配置
- ✅ 测试环境配置
- ✅ 生产环境配置
- ✅ 配置类定义
- ✅ 配置获取函数
- ✅ 应用创建
- ✅ 应用上下文
**测试类:**
- `TestConfig2`: 配置类测试
---
### `test_limiter.py` - 限流器测试
**测试内容:**
- ✅ 测试环境禁用限流
- ✅ 开发环境限流配置
- ✅ 生产环境限流配置
- ✅ 限流器初始化
- ✅ 自定义限流配置
- ✅ 禁用限流配置
---
### `test_http_utils.py` - HTTP 工具测试
**测试内容:**
- ✅ `success()` 函数6 个测试)
- ✅ `fail()` 函数5 个测试)
- ✅ `pagination_builder()` 函数6 个测试)
- ✅ `page()` 函数7 个测试)
- ✅ Schema 定义2 个测试)
- ✅ Flask 集成4 个测试)
- ✅ 边界情况5 个测试)
**测试类:**
- `TestSuccessFunction`: success() 函数测试
- `TestFailFunction`: fail() 函数测试
- `TestPaginationBuilder`: pagination_builder() 测试
- `TestPageFunction`: page() 函数测试
- `TestSchemaDefinitions`: Schema 定义测试
- `TestIntegrationWithFlaskApp`: Flask 集成测试
- `TestEdgeCases`: 边界情况测试
**详细测试列表:**
#### success() 函数测试
1. ✅ `test_success_default` - 默认参数
2. ✅ `test_success_with_data` - 带数据
3. ✅ `test_success_with_custom_msg` - 自定义消息
4. ✅ `test_success_with_custom_code` - 自定义状态码
5. ✅ `test_success_with_list` - 列表数据
6. ✅ `test_success_with_none_data` - None 数据
#### fail() 函数测试
1. ✅ `test_fail_default` - 默认参数
2. ✅ `test_fail_with_custom_msg` - 自定义消息
3. ✅ `test_fail_with_custom_code` - 自定义状态码
4. ✅ `test_fail_with_data` - 带额外数据
5. ✅ `test_fail_returns_dict` - 返回字典
#### pagination_builder() 测试
1. ✅ `test_manual_mode_basic` - 手动模式基础
2. ✅ `test_manual_mode_auto_calculate_pages` - 自动计算页数
3. ✅ `test_manual_mode_with_explicit_pages` - 显式指定页数
4. ✅ `test_manual_mode_zero_total` - 总数为 0
5. ✅ `test_manual_mode_urls_are_none_outside_request` - 请求上下文外 URL
6. ✅ `test_auto_mode_with_mock_pagination` - 自动模式
#### page() 函数测试
1. ✅ `test_page_with_list_and_dict` - 列表 + 字典
2. ✅ `test_page_with_custom_msg` - 自定义消息
3. ✅ `test_page_with_custom_code` - 自定义状态码
4. ✅ `test_page_with_mock_pagination_object` - Pagination 对象
5. ✅ `test_page_with_items_and_pagination_object` - 数据 + Pagination
6. ✅ `test_page_empty_items` - 空数据列表
7. ✅ `test_page_with_large_total` - 大数据量
#### Flask 集成测试
1. ✅ `test_success_in_route` - success() 在路由中
2. ✅ `test_fail_in_route` - fail() 在路由中
3. ✅ `test_page_in_route` - page() 在路由中
4. ✅ `test_index_with_schema` - Schema 验证
---
## ✍️ 编写测试
### 测试规范
1. **文件命名**: `test_*.py`
2. **类命名**: `Test*`(可选)
3. **函数命名**: `test_*`
4. **使用 pytest**: 使用 `pytest` 而非 `unittest`
5. **使用 fixtures**: 复用测试设置
### 示例:添加新测试
`tests/test_http_utils.py` 中添加:
```python
import pytest
from iti.applications.common.utils import success
class TestMyNewFeature:
"""测试新功能"""
def test_my_feature(self):
"""测试我的功能"""
result = success({'key': 'value'})
assert result['code'] == 200
assert result['data']['key'] == 'value'
```
### 使用 Fixtures
```python
@pytest.fixture
def app():
"""创建测试应用"""
from iti.applications import create_app
return create_app('test')
@pytest.fixture
def client(app):
"""创建测试客户端"""
return app.test_client()
def test_with_fixture(client):
"""使用 fixture 测试"""
response = client.get('/')
assert response.status_code == 200
```
---
## 🐛 调试测试
### 查看详细输出
```bash
# 显示 print() 输出
uv run --extra dev pytest -s
# 显示局部变量
uv run --extra dev pytest -l
# 进入调试器
uv run --extra dev pytest --pdb
```
### 仅运行失败的测试
```bash
# 首次运行记录结果
uv run --extra dev pytest
# 仅运行上次失败的测试
uv run --extra dev pytest --lf
# 先运行失败的,再运行其他的
uv run --extra dev pytest --ff
```
---
## 📊 持续集成
### GitHub Actions 示例
```yaml
name: Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install uv
run: curl -LsSf https://astral.sh/uv/install.sh | sh
- name: Run tests
run: uv run --extra dev pytest
- name: Generate coverage
run: uv run --extra dev pytest --cov=iti --cov-report=html
```
---
## 📚 参考资料
- [Pytest 官方文档](https://docs.pytest.org/)
- [Flask Testing 文档](https://flask.palletsprojects.com/en/latest/testing/)
- [Coverage.py 文档](https://coverage.readthedocs.io/)
---
**最后更新:** 2025-10-14
**维护者:** iTi-Flask Team
Loading…
Cancel
Save