#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ 存量 mapper XML 的 namespace 审计 —— 用来自证 @MapperScan 的包清单是完备的。 ## 为什么要审计 `ParkingServerApplication` 上的第 2 条 `@MapperScan` 写死了 7 个包: com.zteits.{bill,business,charge,order,park,pay,sys}.mapper 这条清单不能靠印象,只能靠**把全部存量 XML 的 namespace 枚举出来**:少一个包, 那个包下的 Mapper 接口就不会注册,运行时报「无此 Bean」;多一个包, 可能把 tk.mybatis 的 `MyMapper` 之类的标记接口扫进来(老代码注释写着会出错)。 ## 判读 * 「越界 namespace」= 不在 7 个包下的,必须逐个看(要么补进扫描清单,要么确认是死 XML) * 「无 Java 接口的 namespace」= XML 有、接口没了。这类是老仓库自带的死 XML, 不会导致启动失败(MyBatis 只有在你真正调用该 statement 时才会报错), 但要列出来留痕,别在排查「XML 没加载」时被它们带偏。 用法:python3 -I tools/p2_audit_mapper_ns.py """ import os import re import sys from collections import Counter CLOUD = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # 与 ParkingServerApplication 的 @MapperScan 保持一致 SCAN_PACKAGES = [ "com.zteits.bill.mapper", "com.zteits.business.mapper", "com.zteits.charge.mapper", "com.zteits.order.mapper", "com.zteits.park.mapper", "com.zteits.pay.mapper", "com.zteits.sys.mapper", ] # 存量业务模块(mapper XML 只可能在这几个里) MODULES = [ "parking-module-park", "parking-module-order", "parking-module-pay", "parking-module-bill", "parking-module-charge", "parking-module-business", "parking-module-system", ] NS_RE = re.compile(r'namespace\s*=\s*"([^"]+)"') SKIP_DIR_NAMES = {"target", "out"} def main(): xml_hits = [] # (namespace, xml路径) for mod in MODULES: base = os.path.join(CLOUD, mod, "src/main/java") res = os.path.join(CLOUD, mod, "src/main/resources") for root0 in (base, res): if not os.path.isdir(root0): continue for root, dirs, files in os.walk(root0): dirs[:] = [d for d in dirs if d not in SKIP_DIR_NAMES] for f in files: if not f.endswith(".xml"): continue p = os.path.join(root, f) with open(p, encoding="utf-8", errors="surrogateescape") as fh: head = fh.read(8192) m = NS_RE.search(head) if m: xml_hits.append((m.group(1), p)) # 现存 Java 接口(FQN 集合) java_types = set() for mod in MODULES + ["parking-module-api"]: base = os.path.join(CLOUD, mod, "src/main/java") if not os.path.isdir(base): continue for root, dirs, files in os.walk(base): dirs[:] = [d for d in dirs if d not in SKIP_DIR_NAMES] for f in files: if f.endswith(".java"): java_types.add(f[:-5]) print(f"扫到带 namespace 的 XML {len(xml_hits)} 个;Java 类型 {len(java_types)} 个\n") outside, missing, by_pkg = [], [], Counter() for ns, p in xml_hits: pkg = ns.rsplit(".", 1)[0] by_pkg[pkg] += 1 if not any(ns.startswith(sp + ".") for sp in SCAN_PACKAGES): outside.append((ns, p)) simple = ns.rsplit(".", 1)[-1] if simple not in java_types: missing.append((ns, p)) print("== namespace 所属包分布 ==") for pkg, n in by_pkg.most_common(): flag = "" if any(pkg == sp or pkg.startswith(sp + ".") for sp in SCAN_PACKAGES) else " <== 越界" print(f" {n:>4} {pkg}{flag}") if outside: print(f"\n!! 越界 namespace {len(outside)} 个(不在 7 个 @MapperScan 包下):") for ns, p in outside: print(f" {ns}\n {os.path.relpath(p, CLOUD)}") else: print("\n✔ 全部 namespace 都落在 7 个 @MapperScan 包下") if missing: print(f"\n· 有 XML 无 Java 接口 {len(missing)} 个(老仓库自带的死 XML,不会影响启动):") for ns, p in missing: print(f" {ns}\n {os.path.relpath(p, CLOUD)}") return 0 if not outside else 1 if __name__ == "__main__": sys.exit(main())