# -*- coding: utf-8 -*- """扫描萤火商城 PHP 源码,导出接口清单(控制器+方法)为 CSV 和 Markdown""" import os, re, csv ROOT = r"F:\shop\shop\app" OUT = str(ROOT) MODULES = ["admin", "api", "store", "timer"] MAGIC = re.compile(r"^__") def snake(name: str) -> str: return re.sub(r"(?<=[a-z0-9])([A-Z])", r"_\1", name).lower() rows = [] for module in MODULES: base = os.path.join(ROOT, module, "controller") for dirpath, _, files in os.walk(base): for fn in sorted(files): if not fn.endswith(".php"): continue path = os.path.join(dirpath, fn) rel = os.path.relpath(path, base).replace("\\", "/") text = open(path, encoding="utf-8", errors="replace").read() # 类名 m = re.search(r"class\s+(\w+)", text) if not m: continue cls = m.group(1) # 控制器 URL(TP6 多级控制器用点号连接,全部转下划线) # 例:goods/Goods.php → "goods";goods/Comment.php → "goods.comment" parts = rel.split("/") parts[-1] = parts[-1][:-4] ctrl_url = ".".join(snake(p) for p in parts) # 逐方法解析 for m in re.finditer(r"(/\*\*(.*?)\*/)?\s*(?:protected|public|private)?\s*function\s+(\w+)\s*\(([^)]*)\)", text): comment, body_args, name = m.group(2), m.group(4), m.group(3) if MAGIC.match(name): continue # 只统计 public(ThinkPHP 路由可达) before = text[: m.start()] if "protected" in m.group(0) or "private" in m.group(0): continue brief = "" if comment: for line in comment.splitlines(): line = line.strip().lstrip("*").strip() if line and not line.startswith("@"): brief = line break action = snake(name) url = f"/{module}/" + (ctrl_url + "/" if ctrl_url else "") + action line_no = text[: m.start()].count("\n") + 1 rows.append({ "module": module, "controller": cls, "controller_file": rel, "controller_url": ctrl_url, "action": name, "url": url, "args": " ".join(body_args.split()), "comment": brief, "line": line_no, }) rows.sort(key=lambda r: (MODULES.index(r["module"]), r["controller_file"], r["line"])) os.makedirs(OUT, exist_ok=True) csv_path = os.path.join(OUT, "API清单.csv") with open(csv_path, "w", newline="", encoding="utf-8-sig") as f: w = csv.DictWriter(f, fieldnames=list(rows[0].keys())) w.writeheader() w.writerows(rows) md_path = os.path.join(OUT, "API清单.md") with open(md_path, "w", encoding="utf-8") as f: f.write("# 萤火商城接口清单(Java 重写对齐基线)\n\n") f.write("> 自动扫描自 `F:\\shop\\shop`(ThinkPHP 6 多应用路由:`/模块/控制器/方法`," "多级控制器用点号连接)。响应统一为 `{\"status\":200,\"message\":\"success\",\"data\":{...}}`。\n\n") total = len(rows) f.write(f"**接口总数:{total}**\n\n") for module in MODULES: sub = [r for r in rows if r["module"] == module] f.write(f"\n## 模块 {module}({len(sub)} 个接口)\n\n") cur = None for r in sub: key = r["controller_file"] if key != cur: cur = key f.write(f"\n### {r['controller']}(`app/{module}/controller/{key}`)\n\n") f.write("| 方法 | URL | 参数 | 说明 |\n|---|---|---|---|\n") args = r["args"].replace("|", "\\|") or "—" comment = (r["comment"] or "").replace("|", "\\|") or "—" f.write(f"| {r['action']} | `{r['url']}` | {args} | {comment} |\n") print(f"接口总数: {len(rows)}") from collections import Counter from _root import ROOT print(Counter(r["module"] for r in rows)) print("CSV:", csv_path) print("MD :", md_path)