feat: 整站全量采集与订单写入接口契约补全

- GET 全量扫描 279 个接口并留档(HTTP200=90, 业务成功=76)
- POST 安全探测 24 个只读/计算接口(白名单制, 支付/短信/删除类零触碰)
- 订单写入接口契约经 chunk 源码静态分析补全:
  orders CRUD / 创建订单{contract_id,drawing_data,pic_url} / windoor_default 保存
- 确认付费墙语义 code=10086 及到期/角色权限响应
- doc/API.md 新增整站采集结果章节, 更新订单模块契约表
This commit is contained in:
HHY
2026-09-20 07:19:10 -07:00
parent 0d94fa2f29
commit 7554f2e3c7
293 changed files with 2511 additions and 6 deletions
+63
View File
@@ -0,0 +1,63 @@
#!/usr/bin/env python3
"""POST 安全探测:仅探测搜索/计算/详情语义的接口(空最小请求体)。
绝不触碰:支付、短信、账号绑定、删除/更新类、文件上传类接口。"""
import json, os, re, time, urllib.request, urllib.error
BASE = "https://zzc-serv.thinkerx.com/api/"
TOKEN = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczpcL1wvenpjLXNlcnYudGhpbmtlcnguY29tXC9hcGlcL3dlY2hhdC1sb2dpbi13aXRoLXBob25lIiwiaWF0IjoxNzg5OTA5NTIwLCJleHAiOjE3ODk5OTU5MjAsIm5iZiI6MTc4OTkwOTUyMCwianRpIjoiTGVyV0J4VTZONTVOMlNwQSIsInN1YiI6MzU2Mjg1NiwicHJ2IjoiNTRjMjZlNjE2MjNhYTQzMzZkZTQ1YTcxNGIwYzdhYWIxZTJhYzA3NSJ9.MXBrpBh3gseq8I91EQklxsgLfHafUKwNxc24JdcUkcc"
OUT = r"D:\Code\huamenchuang\data\full-site\post"
os.makedirs(OUT, exist_ok=True)
# 只读/计算语义白名单(详情查询、列表搜索、纯计算、受付费墙保护)
SAFE_POST = [
"orders/", "sunny_house_order/", "after_sales_orders/", "windoor_default",
"order-by-pid", "get_order_process", "record_by_contract_id",
"contract_bars_with_stock", "contract_share", "contract_share/",
"cut_solution", "offer-price", "offer-price/",
"optimize1d-ai", "optimize1d-test", "three-d-data",
"product_by_warehouse", "product-order-format", "product-order-format/",
"order_format_by_warehouse", "is-repeat-order", "cj-orders-status",
"xf_contract_workshop", "ai_product",
]
def safe_name(ep):
return re.sub(r'[^A-Za-z0-9_-]', '_', ep).strip('_') or 'root'
manifest = []
for ep in SAFE_POST:
body = json.dumps({"page": 1}).encode()
req = urllib.request.Request(BASE + ep, method="POST", headers={
"Authorization": f"Bearer {TOKEN}",
"X-Requested-With": "XMLHttpRequest",
"Accept": "application/json",
"Content-Type": "application/json",
}, data=body)
try:
with urllib.request.urlopen(req, timeout=20) as r:
status, text = r.status, r.read().decode("utf-8", "replace")
except urllib.error.HTTPError as e:
status, text = e.code, e.read().decode("utf-8", "replace")[:4000]
except Exception as e:
status, text = -1, str(e)[:200]
entry = {"endpoint": ep, "method": "POST", "status": status, "size": len(text)}
try:
j = json.loads(text)
if isinstance(j, dict):
entry["code"] = j.get("code")
entry["message"] = (j.get("message") or "")[:50]
entry["data_type"] = type(j.get("data")).__name__
# 记录校验错误里的字段清单(Laravel validation)
if j.get("errors"):
entry["required_fields"] = list(j["errors"].keys())
if j.get("data") and isinstance(j.get("data"), list):
entry["data_len"] = len(j["data"])
except Exception:
pass
json.dump({"endpoint": ep, "status": status, "body": text[:100000]},
open(f"{OUT}\\{safe_name(ep)}.json", "w", encoding="utf-8"), ensure_ascii=False)
manifest.append(entry)
print(f"{ep:38s} HTTP {status} code={entry.get('code')} {entry.get('message','')}", flush=True)
time.sleep(0.3)
json.dump(manifest, open(r"D:\Code\huamenchuang\data\full-site\post-manifest.json", "w", encoding="utf-8"), ensure_ascii=False, indent=1)
print(f"\n完成: {len(SAFE_POST)} 个 POST 探测")