14 KiB
ODS 层任务详解
本文档说明飞球 ETL 系统中 ODS(操作数据存储)层的所有任务。 ODS 层负责从上游 SaaS API 抽取原始业务数据并落地到 PostgreSQL(
billiards_odsschema),保留源 payload 便于回溯。
概述
ODS 层采用声明式配置驱动的通用任务模式:由 BaseOdsTask + OdsTaskSpec 配置驱动,通过 ODS_TASK_CLASSES 字典动态注册,共 23 个任务。
所有 ODS 任务写入 billiards_ods.* 表,原始 API 响应以 JSON 格式存入 payload 列,元数据列(fetched_at、source_file、content_hash 等)自动填充。
历史说明:早期版本曾有 14 个独立 ODS 任务(ORDERS、PAYMENTS、MEMBERS 等),写入不存在的
billiards.*schema。 这些任务已于 2026-02-14 废弃删除,全部由下述通用 ODS 任务替代。
任务总览
| 任务代码 | 动态类名 | API 端点 | 目标 ODS 表 | 说明 |
|---|---|---|---|---|
ODS_ASSISTANT_ACCOUNT |
OdsAssistantAccountsTask |
/PersonnelManagement/SearchAssistantInfo |
assistant_accounts_master |
助教账号档案 |
ODS_SETTLEMENT_RECORDS |
OdsOrderSettleTask |
/Site/GetAllOrderSettleList |
settlement_records |
结账记录 |
ODS_TABLE_USE |
OdsTableUseTask |
/Site/GetSiteTableOrderDetails |
table_fee_transactions |
台费计费流水 |
ODS_ASSISTANT_LEDGER |
OdsAssistantLedgerTask |
/AssistantPerformance/GetOrderAssistantDetails |
assistant_service_records |
助教服务流水 |
ODS_ASSISTANT_ABOLISH |
OdsAssistantAbolishTask |
/AssistantPerformance/GetAbolitionAssistant |
assistant_cancellation_records |
助教废除记录 |
ODS_STORE_GOODS_SALES |
OdsGoodsLedgerTask |
/TenantGoods/GetGoodsSalesList |
store_goods_sales_records |
门店商品销售流水 |
ODS_PAYMENT |
OdsPaymentTask |
/PayLog/GetPayLogListPage |
payment_transactions |
支付流水 |
ODS_REFUND |
OdsRefundTask |
/Order/GetRefundPayLogList |
refund_transactions |
退款流水 |
ODS_PLATFORM_COUPON |
OdsCouponVerifyTask |
/Promotion/GetOfflineCouponConsumePageList |
platform_coupon_redemption_records |
平台/团购券核销 |
ODS_MEMBER |
OdsMemberTask |
/MemberProfile/GetTenantMemberList |
member_profiles |
会员档案 |
ODS_MEMBER_CARD |
OdsMemberCardTask |
/MemberProfile/GetTenantMemberCardList |
member_stored_value_cards |
会员储值卡 |
ODS_MEMBER_BALANCE |
OdsMemberBalanceTask |
/MemberProfile/GetMemberCardBalanceChange |
member_balance_changes |
会员余额变动 |
ODS_RECHARGE_SETTLE |
OdsRechargeSettleTask |
/Site/GetRechargeSettleList |
recharge_settlements |
充值结算 |
ODS_GROUP_PACKAGE |
OdsPackageTask |
/PackageCoupon/QueryPackageCouponList |
group_buy_packages |
团购套餐定义 |
ODS_GROUP_BUY_REDEMPTION |
OdsGroupBuyRedemptionTask |
/Site/GetSiteTableUseDetails |
group_buy_redemption_records |
团购套餐核销 |
ODS_INVENTORY_STOCK |
OdsInventoryStockTask |
/TenantGoods/GetGoodsStockReport |
goods_stock_summary |
库存汇总 |
ODS_INVENTORY_CHANGE |
OdsInventoryChangeTask |
/GoodsStockManage/QueryGoodsOutboundReceipt |
goods_stock_movements |
库存变化记录 |
ODS_TABLES |
OdsTablesTask |
/Table/GetSiteTables |
site_tables_master |
台桌维表 |
ODS_GOODS_CATEGORY |
OdsGoodsCategoryTask |
/TenantGoodsCategory/QueryPrimarySecondaryCategory |
stock_goods_category_tree |
库存商品分类树 |
ODS_STORE_GOODS |
OdsStoreGoodsTask |
/TenantGoods/GetGoodsInventoryList |
store_goods_master |
门店商品档案 |
ODS_TABLE_FEE_DISCOUNT |
OdsTableDiscountTask |
/Site/GetTaiFeeAdjustList |
table_fee_discount_records |
台费折扣/调账 |
ODS_TENANT_GOODS |
OdsTenantGoodsTask |
/TenantGoods/QueryTenantGoods |
tenant_goods_master |
租户商品档案 |
ODS_SETTLEMENT_TICKET |
OdsSettlementTicketTask |
/Order/GetOrderSettleTicketNew |
settlement_ticket_details |
结账小票详情 |
所有目标表均位于
billiards_odsschema 下。
通用 ODS 任务架构(BaseOdsTask + OdsTaskSpec 模式)
通用 ODS 任务采用声明式配置驱动:开发者只需定义一个 OdsTaskSpec 数据类实例,由 BaseOdsTask 提供统一的 execute() 流程,再通过 _build_task_class() 工厂函数动态生成 Python 类,最终在 ODS_TASK_CLASSES 字典中注册。
核心优势:
- 零代码新增任务:只需添加一条
OdsTaskSpec配置即可接入新的 API 端点 - Schema-aware 写入:运行时从
information_schema读取目标表结构,自动匹配列名和类型,无需手写字段映射 - 统一去重与冲突处理:通过
content_hash和ON CONFLICT策略保证幂等性
OdsTaskSpec 配置结构
OdsTaskSpec 是一个不可变数据类(@dataclass(frozen=True)),定义了单个 ODS 任务的全部配置。
核心字段
| 字段 | 类型 | 说明 |
|---|---|---|
code |
str |
任务代码,如 ODS_PAYMENT,用于注册和调度 |
class_name |
str |
动态生成的 Python 类名,如 OdsPaymentTask |
table_name |
str |
目标 ODS 表全限定名,如 billiards_ods.payment_transactions |
endpoint |
str |
上游 API 端点路径,如 /PayLog/GetPayLogListPage |
description |
str |
任务描述(中文),用于日志和文档 |
分页与数据提取字段
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
data_path |
Tuple[str, ...] |
("data",) |
API 响应中数据的 JSON 路径,逐层深入 |
list_key |
str | None |
None |
数据列表在 data_path 下的键名(如 "settleList"),为 None 时直接取 data_path 下的列表 |
主键与列定义字段
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
pk_columns |
Tuple[ColumnSpec, ...] |
() |
业务主键列定义,用于冲突检测(通常为 id) |
extra_columns |
Tuple[ColumnSpec, ...] |
() |
额外列定义,用于从嵌套 JSON 中提取特定字段 |
conflict_columns_override |
Tuple[str, ...] | None |
None |
覆盖默认冲突列(默认使用表的 PRIMARY KEY) |
时间窗口字段
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
requires_window |
bool |
True |
是否需要时间窗口参数(事实表为 True,维度快照表为 False) |
time_fields |
Tuple[str, str] | None |
("startTime", "endTime") |
API 请求中时间窗口参数的键名对 |
include_site_id |
bool |
True |
是否在请求中传 siteId 参数 |
extra_params |
Dict[str, Any] |
{} |
额外的固定请求参数 |
快照与软删除字段
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
snapshot_full_table |
bool |
False |
全表快照模式:API 返回全量数据,不在返回集中的记录标记为已删除 |
snapshot_window_columns |
Tuple[str, ...] | None |
None |
窗口快照模式:指定用于限定软删除范围的时间列 |
快照模式说明:当
snapshot_full_table=True或snapshot_window_columns非空时,任务会在每个分段结束后调用_mark_missing_as_deleted(),将 API 未返回但数据库中存在的记录的is_delete标记为1。此行为还需配合运行时配置run.snapshot_missing_delete=True才会生效。
元数据控制字段
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
include_source_file |
bool |
True |
是否写入 source_file 列 |
include_source_endpoint |
bool |
True |
是否写入 source_endpoint 列 |
include_fetched_at |
bool |
True |
是否写入 fetched_at 列 |
include_record_index |
bool |
False |
是否写入 record_index 列 |
include_site_column |
bool |
True |
是否写入 site_id / store_id 列 |
ColumnSpec 列映射定义
ColumnSpec 是不可变数据类,定义单个列的映射规则:
| 字段 | 类型 | 说明 |
|---|---|---|
column |
str |
目标数据库列名 |
sources |
Tuple[str, ...] |
源 JSON 字段名列表,按优先级回退(支持点号路径) |
required |
bool |
是否为必填字段(缺失时跳过整条记录) |
default |
Any |
默认值(源字段全部为空时使用) |
transform |
Callable | None |
类型转换函数 |
代码中提供了三个快捷构造函数:
| 函数 | 用途 | transform |
|---|---|---|
_int_col(name, *sources) |
整数列 | TypeParser.parse_int |
_decimal_col(name, *sources) |
金额列(保留 2 位小数) | TypeParser.parse_decimal(v, 2) |
_bool_col(name, *sources) |
布尔列 | 自定义 _to_bool |
BaseOdsTask 通用 execute 流程
所有通用 ODS 任务共享 BaseOdsTask.execute() 方法,流程如下:
execute(cursor_data)
│
├── 1. 解析时间窗口(_resolve_window)
│ ├── 优先级:用户手动覆盖 > 游标 + MAX(fetched_at) 兜底 > 默认窗口
│ └── 若游标推进但表未实际入库,回退到 MAX(fetched_at) 作为起点
│
├── 2. 窗口分段(build_window_segments)
│ └── 按闲忙时段或配置拆分为多个子窗口
│
├── 3. 准备运行参数
│ ├── 读取 store_id、page_size
│ ├── 解析快照模式配置
│ ├── 获取表主键列(_get_table_pk_columns)
│ └── 检查表是否有 is_delete 列
│
├── 4. 逐段执行(for seg_start, seg_end in segments)
│ ├── 4a. 构建 API 请求参数(_build_params)
│ ├── 4b. 分页抓取(api.iter_paginated)→ _insert_records_schema_aware 写入
│ ├── 4c. 软删除标记(若快照模式启用)
│ └── 4d. 提交事务(db.commit)
│
├── 5. 汇总结果
│ └── 返回 {status, counts, window, segments, request_params}
│
└── 异常处理
└── db.rollback + 记录错误日志 + 重新抛出
Schema-aware 写入(_insert_records_schema_aware)
核心写入方法,运行时动态适配表结构:
-
读取表结构:从
information_schema.columns获取目标表的所有列名、数据类型 -
读取主键:从
information_schema.table_constraints获取 PRIMARY KEY 列 -
记录合并:
_merge_record_layers()将嵌套 JSON 展平为单层字典 -
is_delete 标准化:统一为
0/1 -
content_hash 计算:对记录内容计算 SHA-256 哈希
-
content_hash 去重:与数据库中同一业务主键的最新
content_hash比对,相同则跳过 -
值映射:逐列匹配,特殊列(
payload、source_file、fetched_at、content_hash)自动填充 -
冲突处理:根据
run.ods_conflict_mode配置:模式 SQL 行为 说明 updateON CONFLICT ... DO UPDATE SET ... WHERE IS DISTINCT FROM全字段对比,仅在有变化时更新 backfillON CONFLICT ... DO UPDATE SET COALESCE(existing, new) WHERE ... IS NULL仅回填 NULL 列 nothingON CONFLICT ... DO NOTHING跳过已存在记录 -
批量写入:使用
psycopg2.extras.execute_values分块写入,通过RETURNING (xmax = 0)区分插入和更新
软删除标记(_mark_missing_as_deleted)
当快照模式启用时,任务在每个分段结束后执行软删除:
- 全表快照(
snapshot_full_table=True):将数据库中所有is_delete != 1且不在本次 API 返回集中的记录标记为is_delete=1 - 窗口快照(
snapshot_window_columns非空):仅在指定时间列的窗口范围内执行软删除
content_hash 去重机制
content_hash 是通用 ODS 任务的核心去重手段:
- 计算:排除元数据字段后,对剩余字段按 key 排序后 JSON 序列化,计算 SHA-256 哈希
- 比对:从数据库中按业务主键取最新一条记录的
content_hash - 跳过:若新记录的
content_hash与数据库中最新记录相同,则跳过写入
仅在目标表包含
content_hash列且有fetched_at列时生效。
各任务详细配置
| 任务代码 | 需要窗口 | 快照模式 | 特殊说明 |
|---|---|---|---|
ODS_ASSISTANT_ACCOUNT |
是 | 全表快照 | 助教账号档案,全量抓取后标记离职/删除 |
ODS_SETTLEMENT_RECORDS |
是 | — | 结账记录,按时间窗口增量抓取 |
ODS_TABLE_USE |
否 | 窗口(create_time) |
台费计费流水 |
ODS_ASSISTANT_LEDGER |
是 | 窗口(create_time) |
助教服务流水 |
ODS_ASSISTANT_ABOLISH |
是 | — | 助教废除记录 |
ODS_STORE_GOODS_SALES |
否 | 窗口(create_time) |
门店商品销售流水 |
ODS_PAYMENT |
否 | — | 支付流水 |
ODS_REFUND |
否 | 窗口(pay_time) |
退款流水 |
ODS_PLATFORM_COUPON |
否 | 窗口(consume_time) |
平台/团购券核销 |
ODS_MEMBER |
否 | — | 会员档案 |
ODS_MEMBER_CARD |
否 | 全表快照 | 会员储值卡 |
ODS_MEMBER_BALANCE |
否 | 窗口(create_time) |
会员余额变动 |
ODS_RECHARGE_SETTLE |
是 | — | 充值结算 |
ODS_GROUP_PACKAGE |
否 | 全表快照 | 团购套餐定义 |
ODS_GROUP_BUY_REDEMPTION |
否 | 窗口(create_time) |
团购套餐核销 |
ODS_INVENTORY_STOCK |
否 | — | 库存汇总 |
ODS_INVENTORY_CHANGE |
是 | — | 库存变化记录 |
ODS_TABLES |
否 | — | 台桌维表 |
ODS_GOODS_CATEGORY |
否 | — | 库存商品分类树 |
ODS_STORE_GOODS |
否 | 全表快照 | 门店商品档案 |
ODS_TABLE_FEE_DISCOUNT |
否 | 窗口(create_time) |
台费折扣/调账 |
ODS_TENANT_GOODS |
否 | 全表快照 | 租户商品档案 |
ODS_SETTLEMENT_TICKET |
否 | — | 结账小票详情(专用实现,见下文) |
特殊任务:
ODS_SETTLEMENT_TICKET虽然在ODS_TASK_SPECS中声明,但其ODS_TASK_CLASSES条目被OdsSettlementTicketTask专用实现覆盖。该任务不走标准分页抓取流程,而是先从payment_transactions表或支付 API 收集orderSettleId,再逐个调用小票接口获取详情。