compare_live.py 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. # -*- coding: utf-8 -*-
  2. """实时对比:同时请求线上 PHP 与本地 Java,逐字段 diff。
  3. 与 compare_batch.py 的区别:不依赖 golden 快照,每次都拉线上最新数据,
  4. 适合带参数/随数据变化的接口(商品基础信息、订单发货、设置等)。
  5. 用法:python compare_live.py [用例名...]
  6. """
  7. import sys
  8. import re
  9. import json
  10. import os
  11. import requests
  12. from pathlib import Path
  13. PHP_BASE = os.environ.get("YOSHOP_PHP_BASE", "https://shop.qinys.cn/index.php?s=")
  14. JAVA_BASE = "http://127.0.0.1:18080"
  15. TOKEN = "testsession123"
  16. SESSION = requests.Session()
  17. SESSION.trust_env = False
  18. # (用例名, Java 路径, PHP 路径, 参数)
  19. CASES = [
  20. ("goods_basic_10004", "/store/goods/basic", "/store/goods/basic", {"goodsId": 10004}),
  21. ("goods_basic_10005", "/store/goods/basic", "/store/goods/basic", {"goodsId": 10005}),
  22. ("goods_list_by_ids", "/store/goods/listByIds", "/store/goods/listByIds", {"goodsIds[]": [10004, 10002]}),
  23. ("goods_spec_list", "/store/goods.spec/list", "/store/goods.spec/list", {"goodsId": 10004}),
  24. ("goods_detail_multi", "/store/goods/detail", "/store/goods/detail", {"goodsId": 10004}),
  25. ("goods_detail_multi2", "/store/goods/detail", "/store/goods/detail", {"goodsId": 10009}),
  26. ("goods_detail_single", "/store/goods/detail", "/store/goods/detail", {"goodsId": 10005}),
  27. ]
  28. def php_token() -> str:
  29. """复用上传商品脚本里的真实后台 token(PHP 黄金样本需要线上登录态)"""
  30. src = Path(r"F:\import goods\upload_goods.py").read_text(encoding="utf-8")
  31. m = re.search(r'TOKEN\s*=\s*"([^"]+)"', src)
  32. return m.group(1) if m else TOKEN
  33. def diff(a, b, path="$", out=None, limit=15):
  34. out = out if out is not None else []
  35. if len(out) >= limit:
  36. return out
  37. if type(a) != type(b):
  38. if isinstance(a, (int, float)) and isinstance(b, (int, float)) \
  39. and not isinstance(a, bool) and not isinstance(b, bool):
  40. if float(a) != float(b):
  41. out.append(f"{path}: php={a!r} java={b!r}")
  42. return out
  43. out.append(f"{path}: 类型不同 php={type(a).__name__} java={type(b).__name__} (php={a!r} java={b!r})")
  44. return out
  45. if isinstance(a, dict):
  46. for k in a:
  47. if k not in b:
  48. out.append(f"{path}.{k}: Java 缺失 (php={a[k]!r})")
  49. else:
  50. diff(a[k], b[k], f"{path}.{k}", out, limit)
  51. for k in b:
  52. if k not in a:
  53. out.append(f"{path}.{k}: Java 多出 (java={b[k]!r})")
  54. return out
  55. if isinstance(a, list):
  56. if len(a) != len(b):
  57. out.append(f"{path}: 长度不同 php={len(a)} java={len(b)}")
  58. for i in range(min(len(a), len(b))):
  59. diff(a[i], b[i], f"{path}[{i}]", out, limit)
  60. return out
  61. if a != b:
  62. out.append(f"{path}: php={a!r} java={b!r}")
  63. return out
  64. def fetch_php(path, params, token):
  65. r = SESSION.get(PHP_BASE + path, headers={"Access-Token": token}, params=params, timeout=60)
  66. return r.json()
  67. def fetch_java(path, params):
  68. # 数组参数名已带 [](与前端 axios 序列化一致),直接透传
  69. r = SESSION.get(JAVA_BASE + path, headers={"Access-Token": TOKEN}, params=params or {}, timeout=60)
  70. return r.json()
  71. def main():
  72. only = set(sys.argv[1:])
  73. token = php_token()
  74. ok = fail = 0
  75. for name, java_path, php_path, params in CASES:
  76. if only and name not in only:
  77. continue
  78. try:
  79. php = fetch_php(php_path, params, token)
  80. except Exception as e:
  81. print(f"[skip] {name} 拉取 PHP 失败: {e}")
  82. continue
  83. try:
  84. java = fetch_java(java_path, params)
  85. except Exception as e:
  86. print(f"[FAIL] {name} 拉取 Java 失败: {e}")
  87. fail += 1
  88. continue
  89. if php.get("status") != 200:
  90. print(f"[skip] {name} PHP 返回 {php.get('status')} {php.get('message')}")
  91. continue
  92. if java.get("status") != 200:
  93. print(f"[FAIL] {name} Java 返回 {java.get('status')} {java.get('message')}")
  94. fail += 1
  95. continue
  96. diffs = diff(php.get("data"), java.get("data"))
  97. if diffs:
  98. fail += 1
  99. print(f"[DIFF] {name} ({len(diffs)}+ 处)")
  100. for d in diffs:
  101. print(" ", d)
  102. else:
  103. ok += 1
  104. print(f"[PASS] {name}")
  105. print(f"\n通过 {ok} / 差异 {fail}")
  106. if __name__ == "__main__":
  107. main()