Эх сурвалжийг харах

docs(kb): 下线重构阶段 3、取消 UDF 注释补齐、kb/31 登记 13 个通用 UDF

tianyu.chu 2 долоо хоног өмнө
parent
commit
606a202bee

+ 17 - 3
kb/31-UDF手册.md

@@ -1,17 +1,31 @@
 # UDF 手册
 
-`dw_base/udf/` 下 UDF 的自查表:每次增删 / 重命名 / 改签名时同步更新本表。
+`dw_base/udf/` 下 UDF 的自查表:每次增删 / 重命名 / 改签名时同步更新本表。函数编号与 `dw_base/udf/common/spark_common_udf.py` 中的 `UDF-XX` 注释保持一致。
 
 ## 1. 通用 UDF 自查表
 
 | 函数编号 | 函数名 | 分类 | 入参 | 返回 | 描述 | 示例 |
 |----------|--------|------|------|------|------|------|
-| —        | —      | —    | —    | —    | —    | —    |
+| UDF-01 | `is_json` | JSON | `data` | `boolean` | 判断输入是否为合法 JSON 字符串 | `SELECT is_json('{"a":1}')` → `true` |
+| UDF-02 | `json_object_keys` | JSON | `json_str: string` | `array<string>` | 提取 JSON object 的 key 列表 | `SELECT json_object_keys('{"a":1,"b":2}')` → `['a','b']` |
+| UDF-21 | `array_intersect` | ARRAY | `arr1: array, arr2: array` | `array<string>` | 计算两个数组的交集 | `SELECT array_intersect(array('a','b'), array('b','c'))` → `['b']` |
+| UDF-22 | `array_slice` | ARRAY | `input_array: array, start: int, end: int` | `array<string>` | 按起止下标截取数组(Python 切片语义) | `SELECT array_slice(array('a','b','c','d'), 1, 3)` → `['b','c']` |
+| UDF-23 | `merge_list` | ARRAY | `arr_list: array<array>` | `array<string>` | 合并二维数组,过滤 None 与空字符串 | `SELECT merge_list(array(array('a',''), array('b',null)))` → `['a','b']` |
+| UDF-31 | `has_chinese` | STRING | `datum: string` | `boolean` | 判断字符串中是否包含中文字符 | `SELECT has_chinese('hello 你好')` → `true` |
+| UDF-32 | `similarity` | STRING | `left: string, right: string` | `float` | 计算两个字符串的快速相似度(difflib `quick_ratio`) | `SELECT similarity('apple','apples')` → `0.909` |
+| UDF-33 | `regexp_extract_all` | STRING | `col: string, ptn: string, g: int = 0` | `array<string>` | 提取正则表达式全部匹配结果,`g` 指定捕获组 | `SELECT regexp_extract_all('a1b2c3','\\d',0)` → `['1','2','3']` |
+| UDF-41 | `parse_datetime_to_timestamp` | NUMERIC / DATE / HASH | `date_time: string, in_milli_seconds: boolean = false, original_format: string = null` | `bigint` | 字符串日期转时间戳;支持 `YY.MM.DD` / `YYYY年M月D日` 启发式识别 | `SELECT parse_datetime_to_timestamp('2026-04-21')` |
+| UDF-42 | `get_md5` | NUMERIC / DATE / HASH | `*cols: string` | `string` | 多列按"长度前缀 + 值"拼接后取 MD5(防碰撞) | `SELECT get_md5('a','bc')` |
+| UDF-51 | `str_to_arr` | CROSS-TYPE | `json_str: string` | `array<string>` | JSON array 字符串转 list | `SELECT str_to_arr('["a","b"]')` → `['a','b']` |
+| UDF-52 | `str_to_json_arr` | CROSS-TYPE | `json_str: string` | `array<string>` | JSON array 字符串转"JSON 字符串数组"(每个元素再 `json.dumps`) | `SELECT str_to_json_arr('[{"a":1},{"b":2}]')` |
+| UDF-53 | `str_to_map_arr` | CROSS-TYPE | `json_str: string` | `array<map<string,string>>` | JSON array 字符串转 map 数组 | `SELECT str_to_map_arr('[{"a":1},{"b":2}]')` |
 
-> 本表行数应等于 `spark_common_udf.py` 中导出的 UDF 函数数(启动日志里 `注册 Python UDF` 的条数即真值)。
+> 本表行数应等于 `spark_common_udf.py` 中 `@udf` 注册函数数(启动日志里 `注册 Python UDF` 的条数即真值)。非 `@udf` 装饰的普通 `def` 是 Python 侧辅助函数,不进 SparkSQL 注册表,不登记。
 
 ## 2. 业务 UDF 自查表
 
+`dw_base/udf/business/` 下 UDF,当前暂无。新增业务 UDF 后在此登记。
+
 | 函数编号 | 函数名 | 分类 | 入参 | 返回 | 描述 | 示例 |
 |----------|--------|------|------|------|------|------|
 | —        | —      | —    | —    | —    | —    | —    |

+ 15 - 26
kb/90-重构路线.md

@@ -15,9 +15,10 @@
 | **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/` 数据质量 · `wiki/` Docmost · `pm/` 项目管理集成 | 阶段 2 / 4 |
-| **E** | 业务 SQL 从零开发 | `jobs/{raw,ods,dwd,dws,tdm,ads}/` · DS 工作流 | 阶段 3 |
 | **F** | 老代码删除 | `launch-pad/` 整删 · 其他已确认废弃 | 阶段 5 |
 
+> 新业务 SQL 从零开发(`jobs/{raw,ods,dwd,dws,tdm,ads}/` + DS 工作流)不属于重构 scope,不纳入聚簇。新业务 SQL 生产稳定一个完整周期后触发 F(`launch-pad/` 整删)。
+
 ### 依赖 DAG
 
 ```
@@ -44,12 +45,10 @@ A 其余项 ───────┐
 B3 风格修正 ────┤ 可独立推进,与上图并行
 D 基础设施 ─────┘
 
-       (A + B + C 基本就绪后)
+       (A + B + C 基本就绪 → 新业务 SQL 从零开发,不属于重构 scope)
+                 ↓
+              (新业务 SQL 生产稳定一个完整周期后)
-              ┌────┐
-              │ E  │  业务 SQL 从零开发
-              └─┬──┘
-                ↓
               ┌────┐
               │ F  │  老代码删除
               └────┘
@@ -60,15 +59,13 @@ D 基础设施 ─────┘
 - **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/` 在生产跑稳
+- **新业务 SQL 生产稳定 → F**:`launch-pad/` 删除前置条件是新 `jobs/` 在生产跑稳一个完整周期(新业务 SQL 开发本身不纳入重构 scope)
 
 **可并行的事**(无强依赖):
 
 - A 大部分子项(env.sh / workers.ini / alerter.ini / datax-speed.ini / datasource 多环境 / DataX 脚本路径解耦)彼此独立
 - B3 代码风格修正(`__contains__` / SQL 注入)与任何聚簇都不冲突
 - D 基础设施(tests 骨架 / dq / sync / pm)骨架已建,实现可滚动推进
-- **基础模块弄好后 E 就可并行开工**,不必等 F
 
 ### 本文各节与聚簇的对应
 
@@ -531,26 +528,22 @@ record_per_channel = 100000
 | `wiki/` | `dw_base/wiki/` | 骨架 | 待确认 Docmost API 鉴权 / webhook 支持后实现 | 阶段 4 之后 |
 | `tests/` | `tests/{unit,integration}/` | 骨架 | 阶段 4 首批 UDF 单测启动时同步补 `conftest.py` + fixtures | 阶段 4 |
 
-### 2.12 通用 UDF 注释完整化 + 自查表(B 延伸) [聚簇 B]
+### 2.12 通用 UDF 自查表(B 延伸) [聚簇 B]
 
-**现状**:`dw_base/udf/common/spark_common_udf.py` 500 行 40 函数由 6 份老文件合并而来(见 `kb/92` 2026-04-20 UDF 重组条目),函数签名齐全但注释粗细不一:部分函数只有函数名,部分 docstring 写了逻辑但没写入参约束 / 返回类型 / 异常路径 / SQL 调用示例。`common/` 目录 UDF 由 `bin/spark-sql-starter.py` 自动 `ADD FILE` 注册(启动日志 40 条 `注册 Python UDF xxx` 可证),理论上任意 SQL 都能用,但"能用"不等于"知道怎么用"
+**现状**:`dw_base/udf/common/spark_common_udf.py` 462 行,其中 `@udf` 装饰的注册函数 13 个(启动日志 `注册 Python UDF xxx` 条数即真值),另有若干普通 `def` 辅助函数不进 SparkSQL 注册表。函数注释已由开发者手动补完(编号 `UDF-XX` 顺序注入、分类分节 JSON / ARRAY / STRING / NUMERIC-DATE-HASH / CROSS-TYPE),本节不再规划"注释补齐"动作
 
-**问题**:
-- 注释不一致导致业务 SQL 开发时需要反复读源码猜参数语义,违背 common/ auto-load 的"开箱即用"初衷
-- 未来新增通用 UDF 如果没有登记表,规模大了之后"这个函数有没有 / 叫什么 / 谁加的"全靠 grep
+**问题**:未来新增通用 UDF 如果没有登记表,规模大了之后"这个函数有没有 / 叫什么 / 谁加的"全靠 grep。
 
 **目标态**:
 
-1. **注释完整化**:40 函数全部补齐 docstring,统一 5 段模板 —— 一句话摘要 / 入参(名 · 类型 · 约束 / 可空 / 单位) / 返回(类型 · 语义 · 空值场景) / 异常与边界 / SQL 调用示例。按分类分批推进:JSON 段 → Array 段 → String 段 → Numeric-Date-Hash 段 → Cross-type 段,5 批独立 commit。
-
-2. **UDF 自查表**:新建 `kb/31-UDF手册.md`(与 `30-开发规范.md` 同级独立文档;40 函数规模独立成文更稳,新增 UDF 需要稳定引用锚点)。表头 `函数编号 | 函数名 | 分类 | 入参 | 返回 | 描述 | 示例 | 补注释状态`,函数编号由 Codex 在 `spark_common_udf.py` 中以顺序注释注入(不依赖行号,避免改动污染他函数)。common 与 business 两类 UDF 都在本表登记,分两段(§1 通用 / §2 业务);初版由 Codex 补注释完成后登全量。
+- **UDF 自查表**:`kb/31-UDF手册.md`(与 `30-开发规范.md` 同级独立文档)。表头 `函数编号 | 函数名 | 分类 | 入参 | 返回 | 描述 | 示例`,函数编号沿用 `spark_common_udf.py` 中的 `UDF-XX` 顺序注释。common 与 business 两类 UDF 都在本表登记,分两段(§1 通用 / §2 业务)。初版 13 个通用 UDF 已登记(2026-04-21)。
 
 **回归检验**:
-- 任意 SQL 文件直接 `SELECT my_udf(col)` 能跑通(common auto-load 链路未变,保留现状
-- 自查表行数 = `spark_common_udf.py` 中 `@udf` / `def` 导出函数数(启动日志中 `注册 Python UDF` 的条数即真值)
+- 任意 SQL 文件直接 `SELECT my_udf(col)` 能跑通(common auto-load 链路未变)
+- 自查表 §1 行数 = `spark_common_udf.py` 中 `@udf` 注册函数数(启动日志中 `注册 Python UDF` 的条数即真值)
 
 **与其他条目的关系**:
-- 2026-04-20 UDF 模块重组(kb/92)已完成重组动作,本节是其延伸(补注释 + 登记表)
+- 2026-04-20 UDF 模块重组(kb/92)已完成重组动作,本节是其延伸(登记表)
 - 不动 auto-load 机制(`bin/spark-sql-starter.py` + `dw_base/__init__.py:29 COMMON_SPARK_UDF_FILE` 常量),只补文档
 
 ## 三、`__init__.py` 瘦身(高优先级) [聚簇 B(B1)]
@@ -783,13 +776,9 @@ else:
 | `wiki/` 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 在生产稳定一个完整周期
+见 `kb/92-重构进度.md` 阶段 5 checklist。前置:新业务 SQL 在生产稳定运行一个完整周期(新业务 SQL 开发不属于重构 scope,详见 kb/92 变更记录)。
 
 ### 当前推进建议
 
@@ -806,4 +795,4 @@ else:
 - A2 spark-defaults.conf ← B1
 - C `bin/` 两命令 / `datax-gc-generator` 重写 ← B2 + A datax
 - D `ops/` 下两个工具 ← B2
-- E 业务 SQL ← A + B + C 基本就绪
+- F `launch-pad/` 整删 ← 新业务 SQL 生产稳定一个完整周期(新业务开发本身不属于重构 scope)

+ 4 - 15
kb/92-重构进度.md

@@ -12,10 +12,11 @@
 | 阶段 0:知识库梳理 | ✅ 已完成 | —  | 2026-04-14 |
 | 阶段 1:P0 骨架重命名 | 🟡 推进中 | 2026-04-15 | — |
 | 阶段 2:P1 硬编码外置 | 🟡 部分提前完成(B4 骨架 / bin/publish.sh / excel_to_hive 删) | 2026-04-20 | — |
-| 阶段 3:业务 SQL 从零开发 | ⬜ 未开始 | — | — |
 | 阶段 4:测试体系 + 废弃代码清理 | 🟡 tests 骨架已建 / requirements 精简已完成 | 2026-04-15 | — |
 | 阶段 5:老项目残留删除 | ⬜ 未开始 | — | — |
 
+> 原"阶段 3:业务 SQL 从零开发"不属于重构 scope(2026-04-21 移除,详见变更记录),已从本 checklist 删除。业务 SQL 开发走独立路径,不在本进度追踪内;阶段 5 的前置条件相应调整为"新业务 SQL 生产稳定运行"。
+
 当前所处阶段:**主线阶段 1(P0 骨架)推进中;阶段 2 / 4 部分先导项随聚簇 B4 提前完成**。
 
 ---
@@ -92,19 +93,6 @@
 - [ ] **DataX 速率配置外移**:`conf/datax-speed.ini` 定义分时速率档;`dw_base/datax/job_config_generator.py:60-67` 硬编码替换为读 conf(见 `90-重构路线.md` §2.9)
 - [ ] 新建 `manual/imports/` + `manual/exports/` 目录(按日期 `{yyyymmdd}/` 组织一次性任务)
 
-## 阶段 3:业务 SQL 从零开发
-
-**目标**:按新分层架构填充 `jobs/` 下的真实业务 SQL,与老 `launch-pad/` 零关联。
-
-- [ ] 梳理首批待入仓业务表(按 `11-数据资产.md`)
-- [ ] `jobs/raw/` 首批 DataX ini(按业务域分目录)
-- [ ] `jobs/ods/` 首批贴源 SQL
-- [ ] `jobs/dwd/` 首批明细 SQL
-- [ ] `jobs/dws/` 首批汇总 SQL
-- [ ] `jobs/tdm/` 首批主题模型 SQL
-- [ ] `jobs/ads/` 首批应用层 SQL + 导出 ini
-- [ ] 在 DolphinScheduler 中配置工作流,贯通新链路
-
 ## 阶段 4:测试体系 + 废弃代码清理
 
 - [ ] 建立 `tests/` 目录骨架(见 `90-重构路线.md` §6)
@@ -120,7 +108,7 @@
 
 ## 阶段 5:老项目残留删除
 
-**前置条件**:阶段 3 的业务 SQL 在生产稳定运行至少一个完整周期,且 DS 工作流已完全切换到 `jobs/`。
+**前置条件**:新业务 SQL(不属于本重构 scope)在生产稳定运行至少一个完整周期,且 DS 工作流已完全切换到 `jobs/`。
 
 - [ ] 确认 DS 工作流已无对 `launch-pad/` 的引用
 - [ ] 删除 `launch-pad/` 整个目录
@@ -189,3 +177,4 @@
 | 2026-04-21 | **`dw_base/common/__init__.py` 撤销删除(反转先前空壳判断)**:早先 `ls` 只看到 `__init__.py` 0 字节就判定"空壳"、按 CLAUDE.md "空 __init__.py + 无 README 下次清理直接删"规则挂入 A.1 执行清单第 9 项。`grep` 查证发现 `dw_base/common/` 下 `config_constants.py` / `container.py` / `template_constants.py` 分别被 `bin/spark-sql-starter.py` / `bin/datax-job-config-generator.py` / `dw_base/utils/log_utils.py` / `dw_base/datax/plugins/reader/mysql_reader.py` import,`__init__.py` 0 字节是 Python 正常的 package 标识(`dw_base/__init__.py` 含 bootstrap 逻辑,保持 regular package 一致性优于切 namespace package)。结论:不删。教训:空壳判断必须 `ls` 整个目录 + grep 所有子模块引用,不能只看 `__init__.py` 字节数 | — |
 | 2026-04-21 | **删除 `bin/flume-control.sh`(194 行;事实不可用)**:脚本顶部 shebang 损坏(`ho#!/bin/bash`)+ 依赖已在 2026-04-20 删除的 `bin/wechat-work-alert.sh` + L64 `conf/flume/*.properties` 与 L162 `conf/flume/config/*.properties` 路径自相矛盾,实际已跑不起来。决定:整文件删 + Kafka→HDFS 接入通道的设计理念归档到 kb/90 §5.2 历史档案,按需重建时以该档为参考(不沿用老 `SKB_LITTLE_CUTE` / 手机号硬编码告警,重建时按 `conf/alerter.ini` 外配走) | — |
 | 2026-04-21 | **SQL 风格基线尝试后撤回**:本轮前半段把 `sql_style.xml` 从 `conf/` 挪到项目根,并在 kb/30 §3.2.1 / §3.2.2 立了强基线(关键字/类型 UPPER、SELECT/FROM/ORDER/GROUP 一项一行、JOIN/ON 缩进、CASE/CTE/UNION/OVER/INSERT OVERWRITE/分号九条换行与缩进样例)。实测 IDEA formatter 支持面不足(KEYWORD_CASE 仅作用于新输入不改存量、SELECT 前置逗号长期不支持、ORDER/GROUP 一项一行的 option 名 JetBrains 非公开、UNION 前后空行 formatter 不管、CASE THEN/END 独立行无选项控制),强约束无法靠 formatter 落地。本轮后半段全部回退:删 `sql_style.xml`、kb/30 §3.2.1 / §3.2.2 整节删除、原 §3.2.3 不对齐 AS 重编号为 §3.2.1。团队 SQL 格式化改由各自 IDEA 默认 + 项目 SQL 方言统一设为 Spark 承担,冲突走 review | — |
+| 2026-04-21 | **下线"阶段 3:业务 SQL 从零开发" + 取消 UDF 注释补齐 + kb/31 首批登记 13 个通用 UDF**:(a) 业务 SQL 从零开发属于新开发、不属于重构 scope:kb/92 总览表删阶段 3 行、阶段 3 整节删除、阶段 5 前置条件从"阶段 3 稳定"改为"新业务 SQL 稳定";kb/90 §〇 聚簇表删 E 行、DAG 图删 E 节点、关键依赖边 "A+B+C→E" 与 "E→F" 合并为 "新业务 SQL 生产稳定→F"、§八 聚簇 E 整节删除、当前推进建议"等待前置"里 E 行改为 F 行。(b) 通用 UDF 注释已由开发者手动补完(`spark_common_udf.py` 13 个 `@udf` 函数均带 `UDF-XX` 顺序编号 + 分节注释),kb/90 §2.12 删"5 段模板 + 5 批 commit"规划、"40 函数"更正为"13 个注册 UDF"、标题从"注释完整化 + 自查表"改为"自查表"。(c) `kb/31-UDF手册.md` §1 通用 UDF 表从空壳填入 13 行(UDF-01/02/21/22/23/31/32/33/41/42/51/52/53),分类按代码中分节注释(JSON / ARRAY / STRING / NUMERIC-DATE-HASH / CROSS-TYPE),函数编号按代码中 `UDF-XX` 注释;§2 业务 UDF 保持占位;非 `@udf` 普通 `def`(18 个辅助 / 工具函数)不登记 | — |