# -*- coding: utf-8 -*- """第 8 批接口双端对比(商品评价 / 客户端设置 / 订单导出 / 统计 / initialize)。 PHP 侧用本地站点(php -S 127.0.0.1:8899,与 Java 共用 192.168.1.177 的 shop 库), 因此两端的 base_url 只有端口不同;对比前把 host 归一化,避免误报。 用法:python compare_final_batch.py [用例名...] 前置: 1. F:/shop/shop 启动 PHP 8.2 本地站点(8899); 2. python tools/seed_comment_golden.py 造评价数据; 3. Java 服务运行在 18080。 """ import json import re import sys import requests PHP_BASE = "http://127.0.0.1:8899/index.php?s=" JAVA_BASE = "http://127.0.0.1:18080" TOKEN = "testsession123" SESSION = requests.Session() SESSION.trust_env = False CASES = [ ("comment_list", "/store/goods.comment/list", "/store/goods.comment/list"), ("comment_list_score10", "/store/goods.comment/list&score=10", "/store/goods.comment/list?score=10"), ("comment_list_score30", "/store/goods.comment/list&score=30", "/store/goods.comment/list?score=30"), ("comment_list_status1", "/store/goods.comment/list&status=1", "/store/goods.comment/list?status=1"), ("comment_list_status0", "/store/goods.comment/list&status=0", "/store/goods.comment/list?status=0"), ("comment_list_goods", "/store/goods.comment/list&goodsName=%E7%99%BE%E9%A6%99%E6%9E%9C", "/store/goods.comment/list?goodsName=%E7%99%BE%E9%A6%99%E6%9E%9C"), ("comment_list_goods_no", "/store/goods.comment/list&goodsName=AI", "/store/goods.comment/list?goodsName=AI"), ("comment_list_order_no", "/store/goods.comment/list&orderNo=369583", "/store/goods.comment/list?orderNo=369583"), ("comment_list_user", "/store/goods.comment/list&userId=10001", "/store/goods.comment/list?userId=10001"), ("comment_list_user_missing", "/store/goods.comment/list&userId=99999", "/store/goods.comment/list?userId=99999"), ("comment_list_page2", "/store/goods.comment/list&page=2", "/store/goods.comment/list?page=2"), ("comment_detail_10001", "/store/goods.comment/detail&commentId=10001", "/store/goods.comment/detail?commentId=10001"), ("comment_detail_10002", "/store/goods.comment/detail&commentId=10002", "/store/goods.comment/detail?commentId=10002"), ("comment_detail_10003", "/store/goods.comment/detail&commentId=10003", "/store/goods.comment/detail?commentId=10003"), ("comment_detail_missing", "/store/goods.comment/detail&commentId=99999", "/store/goods.comment/detail?commentId=99999"), ("h5_detail", "/store/client.h5.setting/detail&key=basic", "/store/client.h5.setting/detail?key=basic"), ("h5_detail_unknown", "/store/client.h5.setting/detail&key=unknown", "/store/client.h5.setting/detail?key=unknown"), ("wxapp_detail", "/store/client.wxapp.setting/detail&key=basic", "/store/client.wxapp.setting/detail?key=basic"), ("wxapp_detail_unknown", "/store/client.wxapp.setting/detail&key=unknown", "/store/client.wxapp.setting/detail?key=unknown"), ("export_list", "/store/order.export/list", "/store/order.export/list"), ("statistics_data", "/store/statistics.data/data", "/store/statistics.data/data"), ("statistics_survey", "/store/statistics.data/survey", "/store/statistics.data/survey"), ("statistics_survey_range", "/store/statistics.data/survey&startDate=2026-10-01&endDate=2026-10-17", "/store/statistics.data/survey?startDate=2026-10-01&endDate=2026-10-17"), ("statistics_survey_empty_range", "/store/statistics.data/survey&startDate=2020-01-01&endDate=2020-01-31", "/store/statistics.data/survey?startDate=2020-01-01&endDate=2020-01-31"), ] HOSTS = ("127.0.0.1:8899", "127.0.0.1:18080", "localhost:8899", "localhost:18080") def normalize(obj): """把两端不同的 host:port 归一化,避免 base_url/下载地址误报""" if isinstance(obj, dict): return {k: normalize(v) for k, v in obj.items()} if isinstance(obj, list): return [normalize(v) for v in obj] if isinstance(obj, str): out = obj for host in HOSTS: out = out.replace(host, "{HOST}") return out return obj 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 raw(path, java=False): base = JAVA_BASE if java else PHP_BASE url = base + path if java: url = JAVA_BASE + path return SESSION.get(url, headers={"Access-Token": TOKEN}, timeout=120) def main(): only = set(sys.argv[1:]) 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=120).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=120).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 diffs = diff(normalize(php.get("data")), normalize(java.get("data"))) if php.get("message") != java.get("message"): diffs.append(f"$.message: php={php.get('message')!r} java={java.get('message')!r}") if diffs: fail += 1 print(f"[DIFF] {name} ({len(diffs)}+ 处)") for d in diffs: print(" ", d) else: ok += 1 print(f"[PASS] {name}") # initialize 接口:PHP 与 Java 都应是 200 + 空响应体 for path in ("/store/controller/initialize", "/store/statistics.data/initialize"): name = "initialize:" + path if only and name not in only: continue p = SESSION.get(PHP_BASE + path, headers={"Access-Token": TOKEN}, timeout=60) j = SESSION.get(JAVA_BASE + path, headers={"Access-Token": TOKEN}, timeout=60) if p.status_code == j.status_code == 200 and not p.text.strip() and not j.text.strip(): ok += 1 print(f"[PASS] {name}") else: fail += 1 print(f"[DIFF] {name} php=({p.status_code},{p.text[:80]!r}) java=({j.status_code},{j.text[:80]!r})") print(f"\n通过 {ok} / 差异 {fail}") if __name__ == "__main__": main()