132 lines
7.8 KiB
Markdown
132 lines
7.8 KiB
Markdown
# 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 导入路径,不承载生产
|
||
代码。项目内部代码和新增实现必须使用模块正式路径。
|