Files
gyxx-flow/docs/architecture.md
T

132 lines
7.8 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.
# GYXX Flow 架构
## 目标
GYXX Flow 是一个 Python 3.12 模块化单体。项目在同一部署单元中提供工作流目录、LangGraph 编排、Python 定时调度、外部系统适配和运行审计,同时保持业务模块之间互不依赖。
核心约束:
- 每个定时任务都是一个独立工作流,并编译为 LangGraph `StateGraph`
- 业务模块只能依赖共享契约,不能导入其他业务模块的内部实现。
- PostgreSQL 使用运行时注入的云端连接;Hermes 使用本机回环地址;飞书保持现有身份和调用方式。
- 手动执行默认 dry-run,真实外部副作用必须显式使用 `--execute`
- 代码、配置和运行数据分离;生产数据根目录必须位于项目目录之外。
## 项目边界
```text
gyxx-flow/
├─ src/gyxx_flow/
│ ├─ core/ 配置、上下文、产物、记录和锁
│ ├─ workflow/ LangGraph 构建、步骤协议和执行引擎
│ ├─ adapters/ 进程、浏览器和外部服务适配
│ ├─ modules/ 四个业务模块,生产代码直接位于模块目录
│ ├─ source_sync/ 可选的业务源码漂移检查
│ └─ scheduler_service.py 跨平台 Python 常驻调度器
├─ config/ 工作流、时间规则和运行绑定
├─ deploy/ PostgreSQL 与进程守护定义
├─ docs/ 架构、部署和运维主文档
└─ tests/ 开发和验收测试,不进入生产运行包
```
`var/` 只是开发环境的默认运行目录,不是源码。生产环境通过 `GYXX_DATA_ROOT` 使用独立持久化目录。
## 运行链路
```mermaid
flowchart TD
A["CLI / Python Scheduler"] --> B["WorkflowCatalog"]
B --> C["Workflow Registry + Factory"]
C --> D["LangGraph StateGraph"]
D --> E1["content_marketing"]
D --> E2["product_commerce"]
D --> E3["shop_intelligence"]
D --> E4["supply_chain"]
E1 --> F["Shared Adapters"]
E2 --> F
E3 --> F
E4 --> F
F --> G1["Cloud PostgreSQL"]
F --> G2["Local Hermes collector / analyzer"]
F --> G3["Existing Feishu identity"]
F --> G4["Per-script CDP and browser state"]
D --> H["Journal / Locks / EffectLedger"]
D --> I["Layered DataLayout"]
```
## 工作流与调度
`config/workflows.json` 只保存由 Python 调度器托管的工作流,记录稳定 ID、所属模块、入口、显式步骤、依赖和失败策略。每个目录项必须在 `config/schedules.json` 中有且只有一条时间规则。模块工厂负责注入超时、重试、资源锁和副作用策略。
`config/commands.json` 保存可手动执行的脚本白名单,也承载补采、重试、映射刷新和受保护写操作。命令不是新的定时工作流;`--date` 可渲染命令声明的 `{business_date}` 默认参数,`--arg` 用于追加脚本参数。
`config/schedules.json` 只保存时间规则。`scheduler_service.py` 负责 `Asia/Shanghai` 时区计算、业务日期偏移、时间槽防重、同工作流不重叠、有限错过触发补偿和优雅停止。
服务器只守护一个 `gyxx schedule run` 进程。systemd 或兼容的 NSSM 只负责进程生命周期,不保存业务时间规则;不得再向 Windows Task Scheduler 或多条 cron 复制工作流时间。
## 外部系统
- PostgreSQL:云端模式不提供地址、数据库、用户或密码的源码默认值;由 `GYXX_POSTGRES_DSN` 在运行时注入,并拒绝回环数据库地址。
- Hermes:保留本机 `data-analyzer``data-collector` 两个角色,以及各自 API 和 gateway;Hermes 只承担需要大模型的分析,不作为飞书消息投递身份。
- 飞书:表格写入和业务消息统一使用 `lark-cli --profile hermes-analyzer --as user`。消息发送必须校验真实 `message_id` 回执;卡片图片预上传因上游接口仅支持 tenant token,保留同一 profile 的 bot 媒体上传例外,但最终卡片仍由 user 身份投递。
- 浏览器:每个脚本从 `config/runtime-bindings.json` 获得唯一 CDP 端口及独立 Profile、Cookie、storage state 路径,不共享可写 Profile;多个脚本若属于同一登录身份,则通过 `state/accounts/<account_id>/` 的账号 vault 合并 Cookie,仍保持 Profile 隔离。像商品经营日报这类按品牌动态路由的包装脚本,会在运行时选择对应账号 vault,但仍为每个品牌保留独立 Profile。
- 账号保活:`accounts.<account_id>.keepalive` 由唯一的 `gyxx schedule run` 常驻进程错峰执行;只有安全页面确认未跳转登录页且必需 Cookie 仍有效时,才原子发布新 vault 并同步成员脚本。京东 `jd-shop``jd-self-operated``jd-market-rank` 以及天猫 `tmall-shop``tmall-ozko` 都使用独立 vault,避免同域不同店铺相互覆盖。
### Scrapling 采集边界
浏览器和公开 HTTP 采集统一使用 `scrapling[fetchers]==0.4.13`。生产代码不得直接
导入或启动 Playwright、Patchright、Selenium;静态接口使用 `Fetcher`,普通动态
页面使用 `DynamicSession`,需要隐身能力的页面使用 `StealthySession`。Scrapling 的
动态抓取器内部仍分别使用 Playwright/Patchright,因此它们会作为传递依赖出现,但
不是项目业务 API。官方选型说明见
<https://scrapling.readthedocs.io/en/latest/fetching/choosing.html>。
`gyxx_flow.adapters.scrapling.ScraplingBrowser` 是统一浏览器边界,负责:
- 默认只执行一次,避免上传、申诉等有副作用动作被框架静默重放;
- 从绑定专属 Cookie/storage-state 文件恢复状态,并在关闭前原子保存;
- 用持久 Profile 启动自有浏览器,或借用外部 CDP 中唯一已有 context
- 借用 CDP 时只关闭本次页面和连接,不关闭远端 browser/context
- 将 Scrapling 回调中被记录后吞掉的异常重新抛给工作流;
- 在业务代码不导入底层引擎的前提下统一识别浏览器超时。
Scrapling 0.4.13 当前要求 `curl-cffi==0.16.1b1`,项目显式锁定该版本。部署安装锁定
依赖后运行 `uv run scrapling install` 准备浏览器运行时;定时采集任务本身不得执行
安装。`tests/test_no_native_browser_automation.py` 负责阻止原生浏览器调用回流,唯一
排除项是不可执行的上游 Scrapling 源码快照
`vendors/dy-data-flow/dynamic_session_src.py`
## 数据布局
所有运行路径由 `GYXX_DATA_ROOT` 推导:
```text
data/raw 原始响应、JSON、CSV、Excel、下载文件和截图
data/normalized 清洗和标准化数据
data/curated 聚合事实和业务结果
data/exports Markdown、Excel 等交付文件
data/evidence 对账、验收和追溯证据
state 调度、图状态、锁、账本和浏览器状态
logs 调度器与工作流日志
tmp 可清理临时文件
```
原始数据采用追加式保存。迁移部署时停止调度器,完整复制数据根目录并重新注入 `GYXX_DATA_ROOT`,不修改代码中的路径。
## 扩展方式
新增定时任务时:
1. 在所属 `modules/<module>/` 中实现可独立测试的业务入口。
2. 在工作流目录声明 LangGraph 步骤和依赖,并同步增加唯一时间规则。
3. 为工作流使用的脚本登记公开命令和独立浏览器绑定。
4. 通过共享适配器访问数据库、Hermes、飞书和数据目录。
5. 增加 dry-run、失败边界、业务日期和模块回归测试。
新增补采或维护能力时,只在命令目录注册,不增加工作流或调度规则;只有形成新的独立定时任务后才升级为工作流。
新增模块不应要求修改其他业务模块;新增步骤不应要求修改调度器或执行引擎。
四个模块下的 `runtime/__init__.py` 仅用于兼容历史 Python 导入路径,不承载生产
代码。项目内部代码和新增实现必须使用模块正式路径。