p2_audit_mapper_ns.py
4.38 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
#!/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<T>` 之类的标记接口扫进来(老代码注释写着会出错)。
## 判读
* 「越界 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())