_lsy-order-refunds-probe.py 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  1. # -*- coding: utf-8 -*-
  2. """只读探针:订单抽屉那条新查询,在**真库**上到底会不会返回行。
  3. 它跑的是与新端点**同一条 SQL**(同一个 WHERE / ORDER BY),但**量具不是线上那支** ——
  4. 真正的端点是 MyBatis 发出去的、还要过租户拦截器与 @PreAuthorize。本脚本只能证明
  5. 「按 out_trade_no 能查到这几行、字段名与形态长这样」,证明不了端点本身。
  6. (这条区分的来历见 memory `probe-instrument-is-not-the-production-instrument`。)
  7. 用法: python logs/_lsy-order-refunds-probe.py <out_trade_no> [...]
  8. 只读:只发 SELECT。控制台 GBK ⇒ stdout 只打 ASCII,正文写 UTF-8 文件。
  9. """
  10. import io
  11. import os
  12. import re
  13. import sys
  14. import psycopg2
  15. ROOT = r"D:\project2\payment-platform"
  16. YML = os.path.join(ROOT, "java", "src", "main", "resources", "application.yml")
  17. OUT = os.path.join(ROOT, "logs", "_lsy-order-refunds-probe.txt")
  18. buf = []
  19. def say(s=""):
  20. buf.append(str(s))
  21. print(str(s).encode("ascii", "replace").decode())
  22. def db():
  23. y = io.open(YML, encoding="utf-8", errors="replace").read()
  24. url = re.search(r"(jdbc:postgresql://[^\s\"']+)", y).group(1)
  25. tail = y[y.index(url):]
  26. user = re.search(r"username:\s*(\S+)", tail).group(1)
  27. pwd = re.search(r"password:\s*(\S+)", tail).group(1)
  28. m = re.search(r"//([^:/]+):(\d+)/([^?\s]+)", url)
  29. return psycopg2.connect(host=m.group(1), port=int(m.group(2)), dbname=m.group(3),
  30. user=user, password=pwd, connect_timeout=8)
  31. orders = [a for a in sys.argv[1:] if not a.startswith("--")]
  32. if not orders:
  33. raise SystemExit("用法: python logs/_lsy-order-refunds-probe.py <out_trade_no> [...]")
  34. conn = db()
  35. cur = conn.cursor()
  36. # 后端实体字段 → 全局 SNAKE_CASE 之后前端要读的键(这是本探针要复核的那件事)
  37. COLS = ("out_refund_no", "refund_fee", "status", "refund_no", "finish_time")
  38. for o in orders:
  39. say("=" * 72)
  40. say("out_trade_no = %s" % o)
  41. cur.execute("""SELECT status, total_amount, refunded_amount
  42. FROM pay_lsy_order WHERE out_trade_no = %s""", (o,))
  43. row = cur.fetchone()
  44. say(" pay_lsy_order: %s" % (row,))
  45. if not row:
  46. say(" ! 库里没有这笔订单")
  47. continue
  48. # ↓↓↓ 与新端点 refundsOf() 同一条查询 ↓↓↓
  49. cur.execute("""SELECT out_refund_no, refund_fee, status, refund_no, finish_time,
  50. refund_trace_no, created_time
  51. FROM pay_lsy_refund WHERE out_trade_no = %s
  52. ORDER BY created_time DESC""", (o,))
  53. rows = cur.fetchall()
  54. say(" 新端点那条查询返回 %d 行" % len(rows))
  55. for r in rows:
  56. say(" " + " | ".join("%s=%s" % (c, v) for c, v in zip(
  57. ("out_refund_no", "refund_fee", "status", "refund_no", "finish_time",
  58. "refund_trace_no", "created_time"), r)))
  59. # ★ 下面这几行量的是**表里的列**,不是**响应里的键** —— 两回事,别把标题写成后者。
  60. # 列存在 ⇒ 该字段在实体上有映射(`LsyRefundEntity` 逐字段读过;`status` 来自父类
  61. # `PaymentBaseEntity`),SNAKE_CASE 之后键名与列名一致,故键也在。但这条推理链的
  62. # 最后一跳**本脚本没跑**(它不发 HTTP、不过 Jackson),真正的端到端读数得由接口给。
  63. cur.execute("""SELECT column_name FROM information_schema.columns
  64. WHERE table_name = 'pay_lsy_refund'""")
  65. have = set(x[0] for x in cur.fetchall())
  66. for c in COLS:
  67. say(" 表里的列 `%s` %s(响应键名同一套 SNAKE_CASE;端到端未由本脚本验证)"
  68. % (c, "存在" if c in have else "**不存在**"))
  69. # 回调轨迹那条:IN (订单 trace ∪ 每笔退款的 trace)。只数行,证明退款回调行确实存在。
  70. cur.execute("""SELECT pay_trace_no FROM pay_lsy_order WHERE out_trade_no = %s""", (o,))
  71. pt = cur.fetchone()[0]
  72. cur.execute("""SELECT refund_trace_no FROM pay_lsy_refund WHERE out_trade_no = %s""", (o,))
  73. traces = [pt] + [t[0] for t in cur.fetchall() if t[0]]
  74. say(" 回调轨迹的 trace 集合 = %d 个" % len(traces))
  75. cur.execute("""SELECT to_char(created_time,'HH24:MI:SS.MS'), trade_type, verify_status,
  76. process_status, pay_trace_no
  77. FROM pay_lsy_notify_log WHERE pay_trace_no = ANY(%s)
  78. ORDER BY created_time DESC""", (traces,))
  79. logs = cur.fetchall()
  80. say(" 回调轨迹命中 %d 行(改前只按订单 trace 查,命中 %d 行)"
  81. % (len(logs), sum(1 for l in logs if l[4] == pt)))
  82. for l in logs:
  83. say(" %s" % (l,))
  84. conn.close()
  85. io.open(OUT, "w", encoding="utf-8").write("\n".join(buf) + "\n")
  86. say("")
  87. say("正文已写入 %s" % os.path.relpath(OUT, ROOT))