# -*- coding: utf-8 -*- """订单链路实时对比:同一请求分别打线上 PHP 与本地 Java,逐字段 diff。 PHP 侧必须把参数直接拼进 URL(用 requests 的 params 传参会二次编码 s 参数, ThinkPHP 端会解析失败报 "controller not exists")。 用法:python compare_order_batch.py [用例名...] """ import json import os import re import sys from pathlib import Path import requests PHP_BASE = os.environ.get("YOSHOP_PHP_BASE", "https://shop.qinys.cn/index.php?s=") JAVA_BASE = "http://127.0.0.1:18080" TOKEN = "testsession123" SESSION = requests.Session() SESSION.trust_env = False CASES = [ ("delivery_list", "/store/order.delivery/list&page=1", "/store/order.delivery/list?page=1"), ("delivery_list_filter", "/store/order.delivery/list&page=1&deliveryMethod=10&searchType=10&searchValue=369583", "/store/order.delivery/list?page=1&deliveryMethod=10&searchType=10&searchValue=369583"), ("delivery_list_empty", "/store/order.delivery/list&page=1&searchValue=NOT-EXIST", "/store/order.delivery/list?page=1&searchValue=NOT-EXIST"), ("delivery_detail", "/store/order.delivery/detail&deliveryId=10001", "/store/order.delivery/detail?deliveryId=10001"), ("order_detail_10001", "/store/order/detail&orderId=10001", "/store/order/detail?orderId=10001"), ("order_detail_10002", "/store/order/detail&orderId=10002", "/store/order/detail?orderId=10002"), ("order_list_all", "/store/order/list&dataType=all&page=1", "/store/order/list?dataType=all&page=1"), ("refund_list", "/store/order.refund/list&page=1", "/store/order.refund/list?page=1"), ("refund_detail_missing", "/store/order.refund/detail&orderRefundId=1", "/store/order.refund/detail?orderRefundId=1"), ("traces_disabled", "/store/order.delivery/traces&deliveryId=10001", "/store/order.delivery/traces?deliveryId=10001"), ] def php_token() -> str: if os.environ.get("YOSHOP_TOKEN"): return os.environ["YOSHOP_TOKEN"] src = Path(r"F:\import goods\upload_goods.py").read_text(encoding="utf-8") m = re.search(r'TOKEN\s*=\s*"([^"]+)"', src) return m.group(1) if m else TOKEN def diff(a, b, path="$", out=None, limit=25): out = out if out is not None else [] if len(out) >= limit: return out if type(a) != type(b): if isinstance(a, (int, float)) and isinstance(b, (int, float)) \ and not isinstance(a, bool) and not isinstance(b, bool): if float(a) != float(b): out.append(f"{path}: php={a!r} java={b!r}") return out out.append(f"{path}: 类型不同 php={type(a).__name__} java={type(b).__name__} (php={a!r} java={b!r})") return out if isinstance(a, dict): for k in a: if k not in b: out.append(f"{path}.{k}: Java 缺失 (php={a[k]!r})") else: diff(a[k], b[k], f"{path}.{k}", out, limit) for k in b: if k not in a: out.append(f"{path}.{k}: Java 多出 (java={b[k]!r})") return out if isinstance(a, list): if len(a) != len(b): out.append(f"{path}: 长度不同 php={len(a)} java={len(b)}") for i in range(min(len(a), len(b))): diff(a[i], b[i], f"{path}[{i}]", out, limit) return out if a != b: out.append(f"{path}: php={a!r} java={b!r}") return out def main(): only = set(sys.argv[1:]) token = php_token() ok = fail = 0 for name, php_path, java_path in CASES: if only and name not in only: continue try: php = SESSION.get(PHP_BASE + php_path, headers={"Access-Token": token}, timeout=60).json() except Exception as e: print(f"[skip] {name} 拉取 PHP 失败: {e}") continue try: java = SESSION.get(JAVA_BASE + java_path, headers={"Access-Token": TOKEN}, timeout=60).json() except Exception as e: print(f"[FAIL] {name} 拉取 Java 失败: {e}") fail += 1 continue if php.get("status") != java.get("status"): print(f"[DIFF] {name} status php={php.get('status')} java={java.get('status')} " f"msg php={php.get('message')!r} java={java.get('message')!r}") fail += 1 continue if php.get("status") != 200: # 错误响应也比对 message / data(如物流查询未开启) diffs = diff(php, java) if diffs: fail += 1 print(f"[DIFF] {name} (错误响应)") for d in diffs: print(" ", d) else: ok += 1 print(f"[PASS] {name} (错误响应一致: {php.get('message')})") continue diffs = diff(php.get("data"), java.get("data")) if diffs: fail += 1 print(f"[DIFF] {name} ({len(diffs)}+ 处)") for d in diffs: print(" ", d) else: ok += 1 print(f"[PASS] {name}") print(f"\n通过 {ok} / 差异 {fail}") if __name__ == "__main__": main()