# 飞书库存同步项目重构方案 ## 1. 重构目标 当前项目的核心能力是从 PostgreSQL 查询库存、挂单量、销售和商品信息,按品牌筛选数据,并同步写入对应的飞书多维表格。 本次重构的目标是将原先集中在单个脚本中的逻辑拆分为更清晰、可维护、可扩展的工程结构: - 将配置读取、数据库查询、飞书 API、业务同步、命令行入口拆分到独立模块。 - 将飞书字段名称、SQL 字段来源、字段类型、数字格式统一维护在映射表中。 - 保留原有脚本入口,降低使用成本,避免改变已有执行习惯。 - 提高后续新增品牌、新增字段、新增同步动作时的可读性和安全性。 ## 2. 当前目录结构 ```text inventory_analytics/ ├── .env ├── .gitignore ├── list_feishu_base_tables.py ├── REFACTOR_PLAN.md └── inventory_sync/ ├── __init__.py ├── cli.py ├── feishu.py ├── postgres.py ├── service.py ├── settings.py ├── specs.py └── sql.py ``` ## 3. 模块职责 ### 3.1 入口脚本 文件:`list_feishu_base_tables.py` 职责: - 作为项目的兼容入口。 - 将执行逻辑转交给 `inventory_sync.cli.main()`。 - 保持原命令可继续使用。 示例: ```bash python3 list_feishu_base_tables.py --action sync-brand ``` ### 3.2 命令行模块 文件:`inventory_sync/cli.py` 职责: - 解析 `--action` 参数。 - 初始化配置和飞书客户端。 - 根据 action 调用对应服务。 - 统一输出 JSON 格式结果。 当前支持的 action: - `feishu`:读取飞书 Base 下的表信息。 - `postgres`:执行 PostgreSQL 查询并输出结果。 - `sync-brand`:按照 `BRAND_SYNC_TARGETS` 配置筛选品牌数据并同步到对应飞书表。 - `all`:依次执行飞书读取、数据库查询和品牌同步。 ### 3.3 配置模块 文件:`inventory_sync/settings.py` 职责: - 自动查找并加载 `.env` 文件。 - 从 `.env` 或当前 shell 环境变量中读取配置。 - 校验必填配置。 - 构建统一的 `AppConfig` 配置对象。 当前必填配置: ```text FEISHU_APP_ID FEISHU_APP_SECRET 或 FEISHU_APP_SECRE FEISHU_BASE_URL ANALYTICS_DB_HOST ANALYTICS_DB_PORT ANALYTICS_DB_USER ANALYTICS_DB_PASSWORD ANALYTICS_DB_NAME ``` 可选配置: ```text FEISHU_APP_TOKEN ``` ### 3.4 字段映射模块 文件:`inventory_sync/specs.py` 职责: - 定义品牌到目标飞书表的同步配置。 - 定义主字段名称和来源字段。 - 定义 SQL 查询结果字段到飞书表字段的映射关系。 - 定义飞书字段类型和数字格式。 - 定义历史字段名到新字段名的重命名关系。 当前同步目标: ```python BRAND_SYNC_TARGETS = [ BrandSyncTarget(brand_name="羽心堂", table_name="羽心堂"), ] PRIMARY_FIELD_NAME = "货品编码" PRIMARY_FIELD_SOURCE = "统一规格编码" ``` 当前字段映射: | 飞书字段名 | SQL 来源字段 | 飞书字段类型 | 显示格式 | | --- | --- | --- | --- | | 货品编码 | 统一规格编码 | 文本主字段 | 文本 | | 品牌 | 品牌名称 | 文本 | 文本 | | 分类 | 一级分类 | 文本 | 文本 | | 产品类别 | 货品简称 | 文本 | 文本 | | 货品名称 | 商品名称 | 文本 | 文本 | | 条形码 | 条形码 | 文本 | 文本 | | 零售价 | 零售价 | 数字 | 保留 2 位小数 | | 店铺名称 | 店铺名称 | 文本 | 文本 | | 店铺可用库存 | 店铺挂单量 | 数字 | 不保留小数 | | 总库存 | 总库存 | 数字 | 不保留小数 | | 本月销售数量 | 本月销售数量 | 数字 | 不保留小数 | | 上月销售数量 | 上月销售数量 | 数字 | 不保留小数 | | 上上月销售数量 | 上上月销售数量 | 数字 | 不保留小数 | ### 3.5 SQL 模块 文件:`inventory_sync/sql.py` 职责: - 集中维护 PostgreSQL 查询 SQL。 - 避免业务代码中混入大段 SQL。 - 后续如需调整库存、销量、商品维度逻辑,只需要修改该文件。 当前 SQL 逻辑包括: - 取每个统一规格编码、仓库的最新库存快照。 - 汇总统一规格编码维度的总库存。 - 取每个统一规格编码、店铺的最新挂单量快照。 - 汇总本月、上月、上上月销售数量。 - 关联商品维表和店铺维表。 - 输出用于飞书同步的明细结果。 ### 3.6 PostgreSQL 模块 文件:`inventory_sync/postgres.py` 职责: - 使用 `.env` 中的数据库配置连接 PostgreSQL。 - 执行 `inventory_sync/sql.py` 中定义的 SQL。 - 将查询结果解析为结构化字典列表。 - 对外返回统一结构,供 CLI 或同步服务使用。 ### 3.7 飞书 API 模块 文件:`inventory_sync/feishu.py` 职责: - 获取飞书 `tenant_access_token`。 - 解析飞书 Base 地址和 `app_token`。 - 查询飞书 Base 下的表列表。 - 查询、创建、更新飞书字段。 - 根据字段映射创建或修正目标表字段。 - 分页读取记录。 - 批量删除记录。 - 批量创建记录。 该模块只关心飞书接口本身,不关心具体业务品牌和 SQL 字段。 ### 3.8 业务服务模块 文件:`inventory_sync/service.py` 职责: - 编排完整业务流程。 - 根据配置查询 PostgreSQL。 - 按 `BRAND_SYNC_TARGETS` 逐个筛选品牌数据。 - 根据 `specs.py` 中的映射表构造飞书记录。 - 确认目标飞书表字段存在且类型正确。 - 删除目标表已有记录。 - 批量写入最新记录。 当前同步策略是全量覆盖: ```text 查询 PostgreSQL -> 遍历品牌同步配置 -> 筛选目标品牌 -> 确认飞书字段 -> 删除目标表已有记录 -> 批量插入新记录 ``` ## 4. 执行方式 ### 4.1 查看飞书表 ```bash python3 list_feishu_base_tables.py --action feishu ``` ### 4.2 查询 PostgreSQL 数据 ```bash python3 list_feishu_base_tables.py --action postgres ``` ### 4.3 同步“羽心堂”数据到飞书表 ```bash python3 list_feishu_base_tables.py --action sync-brand ``` ### 4.4 执行完整流程 ```bash python3 list_feishu_base_tables.py --action all ``` ## 5. 字段调整方式 如果只需要调整飞书表头名称、字段来源或字段类型,优先修改 `inventory_sync/specs.py`。 例如,将飞书表中的“店铺可用库存”改名为“可售库存”,只需要调整: ```python FieldSpec("店铺可用库存", "店铺挂单量", 2, "0", 0) ``` 改为: ```python FieldSpec("可售库存", "店铺挂单量", 2, "0", 0) ``` 如果 SQL 输出字段名发生变化,需要同步调整 `source_name`。 例如 SQL 输出从 `店铺挂单量` 改为 `店铺库存`,则需要调整: ```python FieldSpec("店铺可用库存", "店铺库存", 2, "0", 0) ``` ## 6. 新增字段流程 新增字段建议按以下顺序处理: 1. 在 `inventory_sync/sql.py` 中确认 SQL 已输出该字段。 2. 在 `inventory_sync/specs.py` 的 `FIELD_SPECS` 中增加映射。 3. 如果是数字字段,设置 `field_type=2`,并配置 `formatter` 和 `scale`。 4. 执行 `python3 list_feishu_base_tables.py --action sync-brand`。 5. 检查飞书表头和数据是否符合预期。 示例: ```python FieldSpec("新字段飞书名称", "SQL查询结果字段名", 1) FieldSpec("新数字字段", "SQL查询结果数字字段名", 2, "0", 0) FieldSpec("新金额字段", "SQL查询结果金额字段名", 2, "0.00", 2) ``` ## 7. 新增品牌同步方式 当前版本已经使用 `BRAND_SYNC_TARGETS` 管理品牌和目标飞书表的对应关系,配置位置在 `inventory_sync/specs.py`: ```python BRAND_SYNC_TARGETS = [ BrandSyncTarget(brand_name="羽心堂", table_name="羽心堂"), ] ``` 后续新增品牌时,只需要在列表中增加一行,并确保飞书 Base 中存在同名或指定名称的目标表: ```python BRAND_SYNC_TARGETS = [ BrandSyncTarget(brand_name="羽心堂", table_name="羽心堂"), BrandSyncTarget(brand_name="其他品牌", table_name="其他品牌"), ] ``` 执行 `python3 list_feishu_base_tables.py --action sync-brand` 时,程序会先查询一次 PostgreSQL,再按该列表逐个品牌同步。 ## 8. 当前设计取舍 ### 8.1 保留原脚本入口 保留 `list_feishu_base_tables.py` 是为了不改变已有使用方式。后续如果项目继续扩大,可以新增更标准的入口,例如: ```bash python3 -m inventory_sync.cli --action sync-brand ``` ### 8.2 使用全量覆盖同步 当前采用删除旧记录后重新插入的方式,优点是逻辑简单、结果确定,不需要处理复杂的增量比对。 需要注意的是,如果目标飞书表中存在人工维护的额外行,全量覆盖会删除这些记录。后续如需保留人工数据,应改为基于主键“货品编码 + 店铺名称”做 upsert。 ### 8.3 字段映射集中维护 所有“SQL 字段名 -> 飞书表头名”的关系集中放在 `specs.py`,这样可以避免同步逻辑中到处散落字段名,降低后续调整表头时漏改的风险。 ## 9. 后续优化建议 - 增加日志模块,将当前 print 输出改为结构化日志。 - 增加 dry-run 模式,只预览将要删除和写入的数据,不真正修改飞书。 - 增加 upsert 模式,避免每次全量删除重建。 - 增加单元测试,重点覆盖字段映射、数字转换、配置校验。 - 增加同步前数据质量检查,例如主字段为空、重复主键、数字无法转换。 - 支持为不同品牌配置不同字段映射,适配不同品牌表结构差异。 - 将 SQL 拆分为独立 `.sql` 文件,便于数据同学维护。 ## 10. 验证命令 重构后建议至少执行以下命令验证: ```bash PYTHONPYCACHEPREFIX=/tmp/pycache python3 -m py_compile list_feishu_base_tables.py inventory_sync/*.py python3 list_feishu_base_tables.py --action feishu python3 list_feishu_base_tables.py --action postgres python3 list_feishu_base_tables.py --action sync-brand ``` 如果 `sync-brand` 返回类似以下字段,说明同步流程执行成功: ```json { "target_count": 1, "source_row_count": 305, "targets": [ { "brand_name": "羽心堂", "target_table_name": "羽心堂", "filtered_row_count": 200, "deleted_record_count": 198, "created_record_count": 200 } ] } ```