|
|
@@ -3,7 +3,86 @@
|
|
|
> 基于老项目 `tendata-warehouse-release` 的代码分析,为新项目 `poyee-data-warehouse` 规划的重构路线。
|
|
|
> 本文档说"为什么改、怎么改";配套的 `92-重构进度.md` 说"改到哪一步了"。
|
|
|
|
|
|
-## 一、模块重命名(高优先级)
|
|
|
+## 〇、全景与依赖 DAG
|
|
|
+
|
|
|
+线性 P0-P3 排序不适合实际推进 —— 配置外移、模块重组、基础设施可以并行,而业务 SQL / 老代码删除强依赖它们。本节把全部任务按**聚簇**组织,按**依赖边**而非阶段号推进。
|
|
|
+
|
|
|
+### 聚簇划分
|
|
|
+
|
|
|
+| 聚簇 | 名称 | 范围 | 对应旧阶段 |
|
|
|
+|------|------|------|-----------|
|
|
|
+| **A** | 配置外移 / 硬编码清理 | `conf/env.sh` / `workers.ini` / `alerter.ini` / `spark-defaults.conf` / `datax-speed.ini` / datasource 多环境 / DataX 路径解耦 | 阶段 2 主体 |
|
|
|
+| **B** | `dw_base/` 重组 | B1 `__init__.py` 瘦身 · B2 `common/utils/io/ops` 四模块边界定稿 · B3 代码风格修正(`__contains__` / SQL 注入 / Shell-Python 重复) · B4 新占位模块 registry | 阶段 2 / 4 混合 |
|
|
|
+| **C** | `bin/` 入口收口 | `datax-import` / `datax-export` 两命令收口 · `datax-gc-generator` 从零重写 · `csv-to-hdfs-starter` 实现 · `publish.sh` 已入 `bin/` | 阶段 1 尾 + 阶段 2 |
|
|
|
+| **D** | 基础设施 | `tests/` 测试体系 · 告警模块重写 · 日志模块统一 · `dq/` 数据质量 · `sync/` Docmost · `pm/` 项目管理集成 | 阶段 2 / 4 |
|
|
|
+| **E** | 业务 SQL 从零开发 | `jobs/{raw,ods,dwd,dws,tdm,ads}/` · DS 工作流 | 阶段 3 |
|
|
|
+| **F** | 老代码删除 | `launch-pad/` 整删 · 其他已确认废弃 | 阶段 5 |
|
|
|
+
|
|
|
+### 依赖 DAG
|
|
|
+
|
|
|
+```
|
|
|
+ ┌───────────────┐
|
|
|
+ │ B1 __init__ │
|
|
|
+ │ 瘦身 │
|
|
|
+ └───────┬───────┘
|
|
|
+ ↓
|
|
|
+ ┌────────────┴────────────┐
|
|
|
+ ↓ ↓
|
|
|
+┌──────────┐ ┌──────────────┐
|
|
|
+│ A2 spark │ │ B2 四模块 │
|
|
|
+│ defaults │ │ 边界定稿 │
|
|
|
+└──────────┘ └──────┬───────┘
|
|
|
+ ↓
|
|
|
+ ┌─────────┴──────────┐
|
|
|
+ ↓ ↓
|
|
|
+ ┌──────────┐ ┌──────────┐
|
|
|
+ │ B4 新占位 │ │ C bin │
|
|
|
+ │ (骨架) │ │ 收口 │
|
|
|
+ └──────────┘ └──────────┘
|
|
|
+
|
|
|
+A 其余项 ───────┐
|
|
|
+B3 风格修正 ────┤ 可独立推进,与上图并行
|
|
|
+D 基础设施 ─────┘
|
|
|
+
|
|
|
+ (A + B + C 基本就绪后)
|
|
|
+ ↓
|
|
|
+ ┌────┐
|
|
|
+ │ E │ 业务 SQL 从零开发
|
|
|
+ └─┬──┘
|
|
|
+ ↓
|
|
|
+ ┌────┐
|
|
|
+ │ F │ 老代码删除
|
|
|
+ └────┘
|
|
|
+```
|
|
|
+
|
|
|
+**关键依赖边**(强依赖,不能翻转):
|
|
|
+
|
|
|
+- **B1 → A2**:`spark-defaults.conf` 路径解析依赖瘦身后的 `PROJECT_ROOT_PATH`,反过来做会踩返工
|
|
|
+- **B2 → B4**:四模块(`common/utils/io/ops`)边界不定,新占位模块放哪里都是赌博
|
|
|
+- **B2 → C**:`bin/` 两命令的底层实现要放 `dw_base/datax/entry.py`,属于四模块定稿后的延伸
|
|
|
+- **A + B + C → E**:业务 SQL 开工需要 conf 外配就绪 + dw_base 结构稳定 + bin 入口可用
|
|
|
+- **E → F**:`launch-pad/` 删除前置条件是新 `jobs/` 在生产跑稳
|
|
|
+
|
|
|
+**可并行的事**(无强依赖):
|
|
|
+
|
|
|
+- A 大部分子项(env.sh / workers.ini / alerter.ini / datax-speed.ini / datasource 多环境 / DataX 脚本路径解耦)彼此独立
|
|
|
+- B3 代码风格修正(`__contains__` / SQL 注入)与任何聚簇都不冲突
|
|
|
+- D 基础设施(tests 骨架 / dq / sync / pm)骨架已建,实现可滚动推进
|
|
|
+- **基础模块弄好后 E 就可并行开工**,不必等 F
|
|
|
+
|
|
|
+### 本文各节与聚簇的对应
|
|
|
+
|
|
|
+- §一 模块重命名 → **跨 B/F**(历史项,大部分已完成)
|
|
|
+- §二 消除硬编码 → **聚簇 A + 部分 C**
|
|
|
+- §三 `__init__.py` 瘦身 → **聚簇 B(B1)**
|
|
|
+- §四 代码风格修正 → **聚簇 B(B3)**
|
|
|
+- §五 清理废弃代码 → **聚簇 F**(目前已清零,留档案)
|
|
|
+- §六 测试体系 → **聚簇 D**
|
|
|
+- §七 其他建议 → **聚簇 D + A 杂项**
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 一、模块重命名(高优先级) [聚簇 B / F]
|
|
|
|
|
|
### 1.1 `tendata` → `dw_base`
|
|
|
|
|
|
@@ -31,7 +110,7 @@
|
|
|
- `zip -qr launch-pad.zip launch-pad` 类命令(如有)→ 改为 `jobs`
|
|
|
- DolphinScheduler 工作流里老路径的引用 → 新项目上线时一并替换
|
|
|
|
|
|
-## 二、消除硬编码(高优先级)
|
|
|
+## 二、消除硬编码(高优先级) [聚簇 A + 部分 C]
|
|
|
|
|
|
### 2.1 当前硬编码清单
|
|
|
|
|
|
@@ -41,7 +120,7 @@
|
|
|
| `PYTHON3_PATH="/usr/bin/python3"` | `bin/common/init.sh` | 移入 `conf/env.sh` |
|
|
|
| `RELEASE_USER="alvis"` | `bin/common/init.sh` | 改为 `RELEASE_USER="bigdata"` 并移入 `conf/env.sh` |
|
|
|
| `RELEASE_ROOT_DIR="/home/alvis/release"` | `init.sh`、`__init__.py` | 改为 `/home/bigdata/release` 并移入 `conf/env.sh` |
|
|
|
-| 项目部署目录 `poyee-data-warehouse/` | `publish.sh` | 新项目发布目录为 `/home/bigdata/release/poyee-data-warehouse/` |
|
|
|
+| 项目部署目录 `poyee-data-warehouse/` | `bin/publish.sh`(2026-04-20 从根目录挪入 `bin/`) | 新项目发布目录为 `/home/bigdata/release/poyee-data-warehouse/` |
|
|
|
| `DATAX_WORKERS=(m3 d1 d2 d3 d4)` + `DATAX_WORKERS_WEIGHTS` 权重 map | `init.sh:18-31`(含展开 `DATAX_WORKERS_QUEUE` 的循环) | workers 列表 + 权重 map **整体**移入 `conf/workers.ini`(ini 格式),`init.sh` 仅保留读取 + 展开逻辑 |
|
|
|
| `HADOOP_CONF_DIR='/etc/hadoop/conf'` | `__init__.py` | 使用系统环境变量 |
|
|
|
| `LOG_ROOT_DIR="/opt/data/log"` + whoami 分流 | `init.sh`、`__init__.py` | 删除 whoami 分支,单值改为 `${HOME}/log` 并迁入 `conf/env.sh`,见 §7.2.1 |
|
|
|
@@ -425,7 +504,42 @@ record_per_channel = 100000
|
|
|
- 支持按 ini 名(业务表)在 conf 里覆盖特定任务的速率
|
|
|
- 支持命令行 `-speed fast` / `-speed slow` 手动切档(突发高峰 / 限流时用)
|
|
|
|
|
|
-## 三、`__init__.py` 瘦身(高优先级)
|
|
|
+### 2.10 `dw_base/` 四模块边界(common / utils / io / ops) [聚簇 B(B2)]
|
|
|
+
|
|
|
+**背景**:老项目 `dw_base/` 下子目录职责混乱 —— `utils/` 里塞过 Excel/Hive 读写、小文件合并;`database/` 又和 `datax/datasources/` 重叠;缺少 I/O 边界和"数据湖运维"这类长期职责的收纳点。重组把全部非业务代码(非 `spark/` / `udf/` / `datax/` 等明确功能域)按**边界属性**归入四个顶层模块,规则如下:
|
|
|
+
|
|
|
+| 模块 | 边界属性 | 典型内容 | 反例(不该放在此处) |
|
|
|
+|------|---------|---------|---------------------|
|
|
|
+| **`common/`** | 常量 / 全局上下文 / 无行为的元数据 | 颜色常量、枚举、模板路径常量、项目级单例 context | 任何"动词"型函数 —— 那是 `utils/` |
|
|
|
+| **`utils/`** | 纯函数,无外部副作用,可纯 Python 单测 | 日期格式、字符串切分、配置解析、数据结构转换 | 读/写 DB / 文件 / HDFS —— 那是 `io/` |
|
|
|
+| **`io/`** | 与外部系统通信的**边界** | DB connector 薄封装、CSV/JSON/Excel 读写、HDFS 文件读写 | 数据湖维护动作(合并小文件、清理分区)—— 那是 `ops/` |
|
|
|
+| **`ops/`** | 数据湖维护动作,会改变湖里的物理状态 | 小文件合并、分区清理、统计信息刷新、存储压缩 | 一次性的业务查询脚本 —— 那走 `jobs/` 或 `manual/` |
|
|
|
+
|
|
|
+**落地规则**:
|
|
|
+
|
|
|
+1. **写新代码前先对号入座**:如果一个函数既读文件又做纯计算,按**最强副作用**归类(读文件 → `io/`,即便函数里 90% 是计算)
|
|
|
+2. **`io/` 只做薄封装**:不把业务 schema 嵌入 `io/`(老 `mongodb_utils.py` 把公司名查询嵌进去就是反例),业务 schema 留在调用方
|
|
|
+3. **`ops/` 里的函数预期被多处复用**:只被一张表调用一次的"清分区"走 `manual/adhoc/`,不进 `ops/`
|
|
|
+4. **跨模块依赖方向**:`ops/` 可依赖 `io/` + `utils/` + `common/`;`io/` 可依赖 `utils/` + `common/`;`utils/` 只依赖 `common/`;`common/` 不依赖任何项目内模块。**禁止反向依赖**
|
|
|
+
|
|
|
+**B2 先于 B4 / C**:新占位模块(io/ops/pm/dq/sync)和 `bin/` 收口都假设四模块边界已定;边界不定就先别新建骨架,否则返工成本高。
|
|
|
+
|
|
|
+### 2.11 新占位模块 registry(B4) [聚簇 B(B4)]
|
|
|
+
|
|
|
+2026-04-20 本批建立的占位模块(空骨架 + README,实现待后续阶段),登记在此便于跨会话追踪:
|
|
|
+
|
|
|
+| 模块 | 路径 | 状态 | 何时实现 | 对应阶段 |
|
|
|
+|------|------|------|----------|---------|
|
|
|
+| `io/` | `dw_base/io/{db,file,hdfs}/` | 骨架 | B2 四模块边界定稿后,搬入 `mysql_utils` + 文件读写 | 阶段 2 |
|
|
|
+| `ops/` | `dw_base/ops/` | 骨架 | 阶段 4 随"重新实现小文件合并 / 分区保留工具"落地 | 阶段 4 |
|
|
|
+| `pm/` | `dw_base/pm/` | 骨架 | 待确认 TAPD vs Jira 后实现(commit → 任务 ID 联动) | 阶段 4 之后 |
|
|
|
+| `dq/` | `dw_base/dq/` | 骨架 | 待确认告警落点 + 首批规则后实现 | 阶段 4 |
|
|
|
+| `sync/` | `dw_base/sync/` | 骨架 | 待确认 Docmost API 鉴权 / webhook 支持后实现 | 阶段 4 之后 |
|
|
|
+| `tests/` | `tests/{unit,integration}/` | 骨架 | 阶段 4 首批 UDF 单测启动时同步补 `conftest.py` + fixtures | 阶段 4 |
|
|
|
+
|
|
|
+**标准**:每个占位模块必须带 README,4 节(职责 / 接口 / 依赖 / 状态)—— 规则见 `kb/30-开发规范.md §4.5`。空 `__init__.py` + 无 README 直接删除,不保留"暂留"。
|
|
|
+
|
|
|
+## 三、`__init__.py` 瘦身(高优先级) [聚簇 B(B1)]
|
|
|
|
|
|
**现状:** `tendata/__init__.py` 约 120 行,import 即执行以下操作:
|
|
|
- 环境变量设置
|
|
|
@@ -451,7 +565,7 @@ PROJECT_NAME = ...
|
|
|
# dw_base/core/spark_env.py —— findspark 初始化(按需 import)
|
|
|
```
|
|
|
|
|
|
-## 四、代码风格修正(中优先级)
|
|
|
+## 四、代码风格修正(中优先级) [聚簇 B(B3)]
|
|
|
|
|
|
### 4.1 `__contains__` 反模式
|
|
|
|
|
|
@@ -482,7 +596,7 @@ sql = "... WHERE TABLE_SCHEMA='%s' ..." % (database, table_name)
|
|
|
|
|
|
**建议:** 改用参数化查询。
|
|
|
|
|
|
-## 五、清理废弃代码(中优先级)
|
|
|
+## 五、清理废弃代码(中优先级) [聚簇 F]
|
|
|
|
|
|
截至 2026-04-20 本节已无待清理项。后续若发现新的废弃代码,在下方表格追加登记;已完成项保留在"历史档案"表中留档。
|
|
|
|
|
|
@@ -502,13 +616,14 @@ sql = "... WHERE TABLE_SCHEMA='%s' ..." % (database, table_name)
|
|
|
|
|
|
> 代码里残留的 `conf/datax/config/` replace 死逻辑 + `conf/datax/generated` 默认值,属于 §2.x 路径硬编码清理范畴(改名为 `conf/datax-json/` + 删 replace),不在本节。
|
|
|
|
|
|
-## 六、测试体系搭建(中优先级)
|
|
|
+## 六、测试体系搭建(中优先级) [聚簇 D]
|
|
|
|
|
|
### 6.1 现状
|
|
|
|
|
|
- 仅 UDF 有少量 pytest 测试
|
|
|
- 核心模块(SparkSQL、DataX 配置生成)无测试
|
|
|
- 无 CI/CD 集成
|
|
|
+- 2026-04-20 已建 `tests/{unit,integration}/` 骨架 + README(`.gitkeep` 占位,无实测用例),见 §2.11 占位模块 registry
|
|
|
|
|
|
### 6.2 建议的测试结构
|
|
|
|
|
|
@@ -536,7 +651,7 @@ tests/
|
|
|
- **Spark 集成测试**:使用 `local[*]` + 内存 Hive(`enableHiveSupport()` 需要 Hive MetaStore,可用嵌入式 Derby)
|
|
|
- **数据质量**:在 DolphinScheduler 工作流中加入校验节点
|
|
|
|
|
|
-## 七、其他建议
|
|
|
+## 七、其他建议 [聚簇 D + A 杂项]
|
|
|
|
|
|
### 7.1 依赖管理(已精简)
|
|
|
|
|
|
@@ -626,20 +741,81 @@ else:
|
|
|
|
|
|
**后续处理**:若 HMS 未挂 Ranger Hive,调研补挂成本 + 评估现有 HDFS 兜底是否足够(大部分数仓读写场景下足够,因为 PySpark 任务绝大多数以受控 Unix 账号提交、权限粒度粗即可;若要满足敏感列屏蔽类需求则必须补挂)。
|
|
|
|
|
|
-## 八、重构优先级排序
|
|
|
-
|
|
|
-| 阶段 | 任务 | 优先级 |
|
|
|
-|------|------|--------|
|
|
|
-| P0 | 模块重命名 tendata→dw_base、launch-pad→jobs | 高 |
|
|
|
-| P0 | 清理所有业务代码(launch-pad 中保留的样本) | 高 |
|
|
|
-| P1 | 硬编码提取到 conf/ | 高 |
|
|
|
-| P1 | `__init__.py` 瘦身,拆分初始化逻辑 | 高 |
|
|
|
-| P1 | 敏感信息(Webhook token 等)移出代码 | 高 |
|
|
|
-| P2 | `__contains__` → `in` 全局替换 | 中 |
|
|
|
-| P2 | 删除废弃空模块和注释代码 | 中 |
|
|
|
-| P2 | 搭建 tests/ 基础框架 + UDF 单测 | 中 |
|
|
|
-| P2 | 精简 requirements.txt | 中 |
|
|
|
-| P3 | 日志模块统一 | 低 |
|
|
|
-| P3 | SQL 注入修复 | 低 |
|
|
|
-| P3 | 部署脚本改进 | 低 |
|
|
|
-| P3 | Spark/HMS 侧 Ranger Hive 策略验证(见 §7.5) | 低 |
|
|
|
+## 八、聚簇推进视图
|
|
|
+
|
|
|
+替换原 P0-P3 线性优先级表。按聚簇 A-F 组织(定义见 §〇),同一聚簇内部不强排序,跨聚簇只标强依赖。
|
|
|
+
|
|
|
+### A 配置外移 / 硬编码清理
|
|
|
+
|
|
|
+| 子项 | 状态 | 依赖 | 参见 |
|
|
|
+|------|------|------|------|
|
|
|
+| `conf/env.sh`(LOG_ROOT_DIR / RELEASE_USER / RELEASE_ROOT_DIR / PYTHON3_PATH / DATAX_HOME) | 待启动 | — | §2.1 / §7.2.1 |
|
|
|
+| `conf/workers.ini`(DataX Workers + 权重 map 外移) | 待启动 | — | §2.1 |
|
|
|
+| `conf/alerter.ini`(告警 Webhook,入库) | 待启动 | 旧告警代码删除(已 2026-04-20 完成) | §2.1 |
|
|
|
+| `conf/spark-defaults.conf`(Spark 全局默认参数,Spark 原生 flat 格式) | 待启动 | **B1 `__init__.py` 瘦身** | §2.3 |
|
|
|
+| `conf/datax-speed.ini`(DataX 分时速率) | 待启动 | — | §2.9 |
|
|
|
+| `datasource/{db_type}/{env}/{instance}.ini` 多环境分层 | 待启动 | — | §2.5 |
|
|
|
+| DataX 脚本去前缀剥离 + 加 `-env` 参数 | 待启动 | datasource 多环境 | §2.5 |
|
|
|
+| JSON 输出目录改名 `conf/datax-json/` | 待启动 | — | §2.1 |
|
|
|
+
|
|
|
+### B `dw_base/` 重组
|
|
|
+
|
|
|
+| 子项 | 状态 | 依赖 | 参见 |
|
|
|
+|------|------|------|------|
|
|
|
+| **B1** `__init__.py` 瘦身 + `PROJECT_ROOT_PATH` 稳定下来 | 待启动 | — | §三 |
|
|
|
+| **B2** `common/utils/io/ops` 四模块边界定稿 | 待启动 | — | §2.10 |
|
|
|
+| **B3** `__contains__` → `in` 全局替换 | 待启动 | — | §4.1 |
|
|
|
+| **B3** Shell/Python 环境检测去重(`bin/common/init.sh` ↔ `dw_base/__init__.py`) | 待启动 | B1 | §4.2 |
|
|
|
+| **B3** `mysql_utils.py` SQL 注入修复 | 待启动 | —(该文件在 §2.7 `datax-gc-generator` 重写时一并重造,可能被废) | §4.3 |
|
|
|
+| **B4** 新占位模块 `io/` / `ops/` / `pm/` / `dq/` / `sync/` 骨架 + README | ✅ 2026-04-20 已建 | B2 边界规则先立 §2.10 | §2.11 |
|
|
|
+
|
|
|
+### C `bin/` 入口收口
|
|
|
+
|
|
|
+| 子项 | 状态 | 依赖 | 参见 |
|
|
|
+|------|------|------|------|
|
|
|
+| `bin/publish.sh`(从项目根挪入) | ✅ 2026-04-20 完成 | — | §2.1 行 |
|
|
|
+| `bin/excel_to_hive.py` 删除(有需求重做) | ✅ 2026-04-20 完成 | — | changelog |
|
|
|
+| `bin/csv-to-hdfs-starter.py` 实现 | 待启动 | A 大部分就绪 | kb/00 §9 模板 |
|
|
|
+| `bin/datax-import` + `bin/datax-export` 两命令收口 | 待启动 | **B2** 四模块边界 / A datax 路径解耦 | §2.6 |
|
|
|
+| `bin/datax-gc-generator.py` 从零重写 | 待启动 | datasource 多环境就绪 | §2.7 |
|
|
|
+
|
|
|
+### D 基础设施
|
|
|
+
|
|
|
+| 子项 | 状态 | 依赖 | 参见 |
|
|
|
+|------|------|------|------|
|
|
|
+| `tests/{unit,integration}/` 骨架 + README | ✅ 2026-04-20 完成 | — | §2.11 / §六 |
|
|
|
+| `tests/unit/udf/test_spark_common_udf.py` 首批单测 | 待启动 | tests 骨架 + `conftest.py` | §六 |
|
|
|
+| DataX 配置生成单测 | 待启动 | — | §六 |
|
|
|
+| 告警模块重写(弃钉钉 → `conf/alerter.ini`) | 待启动 | `conf/alerter.ini` | §2.1 / 阶段 4 |
|
|
|
+| 日志模块统一(Python `logging` 双输出 / `log_path` 工具) | 待启动 | `conf/env.sh` LOG_ROOT_DIR | §7.2 / §7.2.1 |
|
|
|
+| Hive HDFS 小文件合并工具重新实现 | 待启动 | **B2** → `ops/` | 阶段 4 |
|
|
|
+| 分区保留工具重新实现(元表驱动 + 参数化) | 待启动 | **B2** → `ops/` | 阶段 4 |
|
|
|
+| `dq/` 数据质量规则首批 + runner | 待启动 | `conf/alerter.ini` 就绪 | §2.11 |
|
|
|
+| `pm/` TAPD / Jira 集成(commit → 任务 ID) | 待启动 | 确认平台选型 | §2.11 |
|
|
|
+| `sync/` Docmost → `kb/inbox/` | 待启动 | Docmost API 鉴权确认 | §2.11 |
|
|
|
+| Ranger Hive 策略验证(集群侧) | 待启动 | — | §7.5 |
|
|
|
+
|
|
|
+### E 业务 SQL 从零开发(阶段 3)
|
|
|
+
|
|
|
+见 `kb/92-重构进度.md` 阶段 3 checklist。前置:A + B + C 基本就绪。
|
|
|
+
|
|
|
+### F 老代码删除(阶段 5)
|
|
|
+
|
|
|
+见 `kb/92-重构进度.md` 阶段 5 checklist。前置:E 在生产稳定一个完整周期。
|
|
|
+
|
|
|
+### 当前推进建议
|
|
|
+
|
|
|
+**本阶段可并行开工**(无前置阻塞):
|
|
|
+
|
|
|
+1. A 大部分子项(env.sh / workers.ini / alerter.ini / datax-speed.ini)
|
|
|
+2. B1 `__init__.py` 瘦身(解锁 A2 spark-defaults.conf)
|
|
|
+3. B2 四模块边界定稿(只需写决策,不改代码)
|
|
|
+4. B3 代码风格修正(`__contains__` 全替换)
|
|
|
+5. D 首批 UDF 单测(tests 骨架已建)
|
|
|
+
|
|
|
+**等待前置**:
|
|
|
+
|
|
|
+- A2 spark-defaults.conf ← B1
|
|
|
+- C `bin/` 两命令 / `datax-gc-generator` 重写 ← B2 + A datax
|
|
|
+- D `ops/` 下两个工具 ← B2
|
|
|
+- E 业务 SQL ← A + B + C 基本就绪
|