"""yun8 运输流程造数 CLI。 旧 yun8_sync/main.py 的等价入口,复用 tests/apis/yun8 的 API 层与编排层: python -m tests.apis.yun8.cli --stage all --repeat 10 --delay 5 注意: - --dry-run 仅跳过回单/ETC 的 SQL 写入;HTTP 步骤全部真实执行, driver 阶段的 reset SQL 也不受 dry-run 影响(与旧工具行为一致)。 - 数据库连接读取 tests/config/test_config.yaml 的 database 段。 阶段选择: all 完整流程 (默认): 司机端 → 核算 → 付款 → 回单SQL → 上报 driver 仅司机端 (步骤 1-5) accounting 仅运费核算 (步骤 6-9) payment 仅审核付款 (步骤 10-16) receipt 仅回单文件+SQL+上报 (步骤 14-17) """ import argparse import sys import time import traceback from datetime import datetime from pathlib import Path import yaml from tests.core.config_loader import load_config from tests.core.db_helper import Db from tests.apis.yun8.pipeline import Yun8Pipeline STAGE_HELP = """阶段选择: all 完整流程 (默认): 司机端 → 核算 → 付款 → 回单SQL → 上报 driver 仅司机端 (步骤 1-5) accounting 仅运费核算 (步骤 6-9) payment 仅审核付款 (步骤 10-16) receipt 仅回单文件+SQL+上报 (步骤 14-17) """ def _load_yun8_section(path: str | Path | None) -> dict: """加载 yun8_api 配置段。 - path 为空: 读 tests/config/test_config.yaml 的 yun8_api 段 - path 指向的 YAML 含 yun8_api 键: 直接取该段 - 否则按旧 yun8_sync/config.yaml 结构兼容解析(database 段忽略) """ if path is None: return load_config()["yun8_api"] with open(path, "r", encoding="utf-8") as fh: raw = yaml.safe_load(fh) if "yun8_api" in raw: return raw["yun8_api"] # 旧版结构兼容: api/auth/driver_params/... 平铺 api = raw.get("api", {}) section = { "base_url": api.get("base_url", ""), "verify_ssl": api.get("verify_ssl", False), "timeout_seconds": api.get("timeout_seconds", 60), "common_headers": api.get("common_headers", {}), "driver_headers": api.get("driver", {}), "admin_headers": api.get("admin", {}), "tokens": dict(raw.get("auth", {})), } for key in ("driver_params", "accounting", "payment", "etc_invoice", "sql", "dianyuntong"): if key in raw: section[key] = raw[key] return section def run_one_cycle(section: dict, stage: str, driver_token: str, admin_token: str, dry_run: bool) -> bool: """执行一次完整流水线,返回 True 表示成功(异常直接向上抛)。""" db = Db() pipe = Yun8Pipeline(section, db) try: pipe.set_auth(driver_token, admin_token) if stage in ("all", "driver"): pipe.run_driver_flow() # reset_order_detail 模式:仅跑司机端,跳过后续全部步骤 dp = section.get("driver_params", {}) if dp.get("reset_order_detail", False): print(f"\n [stop] reset_order_detail=true,跳过步骤 06-21") return True if stage in ("all", "accounting"): pipe.run_accounting_flow() if stage in ("all", "payment"): pipe.run_audit_payment_flow() ac = section.get("accounting", {}) if ac.get("execute_after_audit", True): if stage in ("all", "receipt"): pipe.run_receipt_file_flow(dry_run=dry_run) # ETC 开票 SQL 写入 (步骤 22-23) pipe.run_etc_invoice_flow(dry_run=dry_run) else: if stage in ("all", "receipt"): print(f"\n [stop] execute_after_audit=false,跳过 receipt 阶段") return True finally: pipe.close() db.close() def main(): parser = argparse.ArgumentParser( description="yun8 运输流程完整自动化", formatter_class=argparse.RawDescriptionHelpFormatter, epilog=STAGE_HELP, ) parser.add_argument("--config", default=None, help="配置文件路径 (默认: tests/config/test_config.yaml 的 yun8_api 段)") parser.add_argument("--auth-driver", default="", help="司机端 Bearer Token (含 'Bearer ' 前缀)") parser.add_argument("--auth-admin", default="", help="管理端 Bearer Token (含 'Bearer ' 前缀)") parser.add_argument("--stage", default="all", choices=["all", "driver", "accounting", "payment", "receipt"], help="运行阶段 (默认: all)") parser.add_argument("--dry-run", action="store_true", help="仅预览 SQL,不实际写入数据库 (注意: HTTP 步骤仍真实执行)") parser.add_argument("--repeat", type=int, default=1, help="循环执行次数 (默认: 1)") parser.add_argument("--delay", type=int, default=0, help="每次循环之间的等待秒数 (默认: 0)") args = parser.parse_args() # 加载配置 section = _load_yun8_section(args.config) # 读取 Token(命令行优先) tokens = section.get("tokens", {}) driver_token = args.auth_driver or tokens.get("driver_token", "") admin_token = args.auth_admin or tokens.get("admin_token", "") print("=" * 60) print(f" yun8 运输流程自动化") print(f" config: {args.config or 'tests/config/test_config.yaml (yun8_api)'}") print(f" stage: {args.stage}") print(f" dry-run: {args.dry_run}") print(f" repeat: {args.repeat}") print(f" delay: {args.delay}s") print(f" 开始时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") print("=" * 60) ok_count = 0 fail_count = 0 cycle_start = time.time() for i in range(1, args.repeat + 1): if args.repeat > 1: print(f"\n{'#' * 60}") print(f"# 第 {i}/{args.repeat} 次循环") print(f"# 时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") print(f"{'#' * 60}") try: run_one_cycle(section, args.stage, driver_token, admin_token, args.dry_run) ok_count += 1 if args.repeat > 1: print(f"\n--- 第 {i}/{args.repeat} 次循环: 成功 ---") except KeyboardInterrupt: print(f"\n 用户中断,已执行 {i-1} 次") fail_count += (args.repeat - i + 1) break except Exception as exc: fail_count += 1 print(f"\n--- 第 {i}/{args.repeat} 次循环: 失败 ---") print(f" 错误: {exc}") traceback.print_exc() # 失败后继续下一次(不中断循环) # 循环间隔 if i < args.repeat and args.delay > 0: print(f" 等待 {args.delay}s 后进入下一次循环...") time.sleep(args.delay) # 汇总 elapsed = time.time() - cycle_start print(f"\n{'=' * 60}") print(f" 全部循环完成") print(f" 成功: {ok_count} 失败: {fail_count} 总计: {args.repeat}") print(f" 耗时: {elapsed:.0f}s") print(f" 结束时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") print(f"{'=' * 60}") if fail_count > 0: sys.exit(1) if __name__ == "__main__": main()