Initial commit: reorganized source tree

- backend/: 13 Maven modules (cw-elevator-application, cloudwalk-cloud, intelligent-cwoscomponent, ninca-crk, etc.)
- frontend/: 4 Vue projects (elevator-front, cwos-portal, alarm-front, front_acs) + decompiled + scripts
- scripts/: build, test-env, tools (Docker Compose, service templates, API parity)
- docs/: AGENTS.md, superpowers specs, architecture docs
- .gitignore: standard Java/Maven exclusions

Moved from legacy maven-*/ root layout to backend/ organized structure.
This commit is contained in:
hpd840321
2026-05-09 09:00:12 +08:00
commit 7b2bd307f1
7260 changed files with 612980 additions and 0 deletions
@@ -0,0 +1,340 @@
#!/usr/bin/env python3
"""
Full reactor audit: every *.java -> top-level FQN -> javap -p vs deployment jars/classes.
Usage:
python3 tools/deploy_javap_audit.py [--workers N] [--write-json PATH] [--write-markdown PATH]
"""
from __future__ import annotations
import argparse
import json
import os
import subprocess
import sys
import zipfile
from concurrent.futures import ThreadPoolExecutor, as_completed
from pathlib import Path
from typing import Any, Dict, List, Optional, Set, Tuple
REACTOR = Path(__file__).resolve().parents[1]
DEPLOY_ROOT = REACTOR.parents[1] / "部署包" / "ninca_common_component_organization_01-ninca_common_component_organization"
DEPLOY_UNPACK = DEPLOY_ROOT / "ninca-common-component-organization-V2.9.2_20210730"
DEPLOY_LIB = DEPLOY_UNPACK / "BOOT-INF" / "lib"
DEPLOY_CLASSES = DEPLOY_UNPACK / "BOOT-INF" / "classes"
DEPLOY_JARS = [
"cwos-component-organization-interface-v2.9.2_xinghewan.jar",
"cwos-component-organization-data-v2.9.2_xinghewan.jar",
"cwos-component-organization-service-v2.9.2_xinghewan.jar",
"cwos-component-organization-web-v2.9.2_xinghewan.jar",
]
def deploy_classpath() -> str:
jars = [str(DEPLOY_LIB / j) for j in DEPLOY_JARS]
return os.pathsep.join([str(DEPLOY_CLASSES)] + jars)
def local_classpath() -> str:
parts: List[str] = []
for mod in [
"cwos-component-organization-interface",
"cwos-component-organization-data",
"cwos-component-organization-service",
"cwos-component-organization-web",
"cwos-component-organization-starter",
]:
tc = REACTOR / mod / "target" / "classes"
if tc.is_dir():
parts.append(str(tc))
return os.pathsep.join(parts)
def collect_java_files(main_only: bool = True) -> List[Path]:
out: List[Path] = []
for root, _, files in os.walk(REACTOR):
r = root.replace("\\", "/")
if "/target/" in r:
continue
if main_only and "/src/main/java" not in r:
continue
for f in files:
if f.endswith(".java"):
out.append(Path(root) / f)
out.sort()
return out
def fqn_from_path(java_path: Path) -> Optional[str]:
try:
parts = java_path.resolve().relative_to(REACTOR).parts
except ValueError:
return None
anchor = None
for i in range(len(parts) - 1):
if parts[i : i + 3] == ("src", "main", "java"):
anchor = i + 3
break
if anchor is None:
return None
rel = parts[anchor:]
if not rel:
return None
stem = Path(rel[-1]).stem
pkg_parts = rel[:-1]
if stem == "package-info":
return ".".join(pkg_parts) + ".package-info"
return ".".join(pkg_parts) + "." + stem
def javap(cp: str, fqn: str) -> Tuple[int, str]:
try:
p = subprocess.run(
["javap", "-p", "-classpath", cp, fqn],
capture_output=True,
text=True,
timeout=30,
)
err = (p.stderr or "").strip()
out = (p.stdout or "").strip()
if p.returncode != 0:
return p.returncode, err or out
return 0, out
except Exception as e:
return 99, str(e)
def normalize_javap(text: str) -> str:
lines = []
for line in text.replace("\r\n", "\n").split("\n"):
lines.append(line.rstrip())
return "\n".join(lines).strip() + "\n"
def outer_fqn_from_class_path(entry: str) -> Optional[str]:
"""BOOT-INF path cn/foo/Bar.class or cn/foo/Bar$1.class -> cn.foo.Bar"""
if not entry.endswith(".class") or "META-INF" in entry:
return None
if not entry.endswith(".class"):
return None
rel = entry.replace("/", ".")[: -len(".class")]
if "$" in rel:
rel = rel.split("$", 1)[0]
return rel
def deploy_outer_classes() -> Set[str]:
"""Union of outer-type FQNs present in deploy jars + BOOT-INF/classes."""
out: Set[str] = set()
if DEPLOY_CLASSES.is_dir():
for p in DEPLOY_CLASSES.rglob("*.class"):
rel = p.relative_to(DEPLOY_CLASSES).as_posix()
o = outer_fqn_from_class_path(rel)
if o:
out.add(o)
for jname in DEPLOY_JARS:
jpath = DEPLOY_LIB / jname
if not jpath.is_file():
continue
with zipfile.ZipFile(jpath, "r") as z:
for name in z.namelist():
o = outer_fqn_from_class_path(name)
if o:
out.add(o)
return out
def audit_one(
args: Tuple[str, str, str, str]
) -> Dict[str, Any]:
rel_file, fqn, d_cp, l_cp = args
rc_d, out_d = javap(d_cp, fqn)
rc_l, out_l = javap(l_cp, fqn)
row: Dict[str, Any] = {"file": rel_file, "fqn": fqn}
if rc_l != 0:
row["status"] = "MISSING_LOCAL_JAVAP"
row["note"] = (out_l or "")[:800]
return row
if rc_d != 0:
row["status"] = "MISSING_DEPLOY_JAVAP"
row["note"] = (out_d or "")[:800]
return row
nd, nl = normalize_javap(out_d), normalize_javap(out_l)
if nd == nl:
row["status"] = "IDENTICAL"
row["note"] = ""
else:
row["status"] = "DIFFERENT"
row["note"] = f"lines deploy={len(nd.splitlines())} local={len(nl.splitlines())}"
return row
def main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument("--workers", type=int, default=12)
ap.add_argument(
"--all-sources",
action="store_true",
help="Include src/test and other trees (deploy JAR will miss most tests).",
)
ap.add_argument("--write-json", type=Path, default=None)
ap.add_argument("--write-markdown", type=Path, default=None)
args = ap.parse_args()
if not DEPLOY_LIB.is_dir():
print("Deploy lib not found:", DEPLOY_LIB, file=sys.stderr)
return 2
d_cp = deploy_classpath()
l_cp = local_classpath()
java_list = collect_java_files(main_only=not args.all_sources)
tasks: List[Tuple[str, str, str, str]] = []
skipped = 0
for jp in java_list:
fqn = fqn_from_path(jp)
rel = str(jp.relative_to(REACTOR))
if not fqn:
skipped += 1
continue
tasks.append((rel, fqn, d_cp, l_cp))
rows: List[Dict[str, Any]] = []
for jp in java_list:
fqn = fqn_from_path(jp)
rel = str(jp.relative_to(REACTOR))
if not fqn:
rows.append(
{
"file": rel,
"fqn": None,
"status": "SKIP_PATH",
"note": "could not derive FQN",
}
)
with ThreadPoolExecutor(max_workers=max(1, args.workers)) as ex:
futs = {ex.submit(audit_one, t): t for t in tasks}
for fut in as_completed(futs):
rows.append(fut.result())
# preserve stable sort by file path
rows_sorted = sorted(rows, key=lambda r: (r.get("file") or ""))
summary_counts: Dict[str, int] = {}
for r in rows_sorted:
s = r.get("status") or "?"
summary_counts[s] = summary_counts.get(s, 0) + 1
source_fqns = {r["fqn"] for r in rows_sorted if r.get("fqn")}
deploy_outer = deploy_outer_classes()
only_deploy = sorted(deploy_outer - source_fqns)
summary = {
"reactor": str(REACTOR),
"deploy_unpack": str(DEPLOY_UNPACK),
"scope_main_java_only": not args.all_sources,
"java_files_total": len(java_list),
"skipped_non_java_path": skipped,
"status_counts": summary_counts,
"deploy_only_outer_types": len(only_deploy),
}
print(json.dumps(summary, indent=2, ensure_ascii=False))
if args.write_json:
args.write_json.parent.mkdir(parents=True, exist_ok=True)
args.write_json.write_text(
json.dumps(
{"summary": summary, "rows": rows_sorted, "deploy_only_outer_fqns": only_deploy},
indent=2,
ensure_ascii=False,
),
encoding="utf-8",
)
print("Wrote JSON:", args.write_json)
if args.write_markdown:
write_markdown_report(
args.write_markdown,
summary,
rows_sorted,
only_deploy,
)
print("Wrote MD:", args.write_markdown)
return 0
def write_markdown_report(
path: Path,
summary: Dict[str, Any],
rows: List[Dict[str, Any]],
only_deploy: List[str],
) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
lines: List[str] = []
lines.append("# 组织组件:全量 Java 文件 vs 部署包 javap 核对报告\n")
lines.append(f"- **反应堆:** `{summary['reactor']}`\n")
lines.append(f"- **部署解压:** `{summary['deploy_unpack']}`\n")
scope = "仅 `src/main/java`(与 Fat JAR 生产字节码对齐)" if summary.get("scope_main_java_only") else "含测试等全部 `.java``--all-sources`"
lines.append(f"- **范围:** {scope}\n")
lines.append("- **核对方式:** 每个 `*.java` 推导顶层 FQN`javap -p` 与部署 classpath / 本地 `target/classes` 对比。\n")
lines.append("\n## 1. 汇总\n\n")
lines.append("| 状态 | 数量 |\n|------|------|\n")
for k, v in sorted(summary["status_counts"].items()):
lines.append(f"| {k} | {v} |\n")
lines.append(f"\n- **部署包中存在、源码树无对应 `.java` 的顶层类型(估):** {summary['deploy_only_outer_types']} 个(见附录 B)。\n")
diff_rows = [r for r in rows if r.get("status") == "DIFFERENT"]
miss_d = [r for r in rows if r.get("status") == "MISSING_DEPLOY_JAVAP"]
miss_l = [r for r in rows if r.get("status") == "MISSING_LOCAL_JAVAP"]
lines.append("\n## 2. 解决方案与处置建议(按现象)\n\n")
lines.append("### 2.1 `DIFFERENT`(签名不一致)\n\n")
lines.append("- **含义:** 同 FQN 下,现场 JAR 与当前编译产物的 **字段/方法列表等对外形状** 不一致(含编译器生成的 synthetic/lambda 差异)。\n")
lines.append("- **建议:**\n")
lines.append(" 1. 以业务为准明确「权威版本」:若现场为基线,则 **检出与现场一致源码** 或在 CI 中 **与现场 JAR 做契约测试**;若仓库为权威,则 **升版发布** 替换现场。\n")
lines.append(" 2. 对差异类做 **关键路径回归**(人员/图库/设备同步 API)。\n")
lines.append(" 3. 对纯 lambda/synthetic 差异可辅以 **`javap -c` 抽样** 判断是否仅为编译差异。\n")
lines.append(f"\n**本仓 DIFFERENT 数量:** {len(diff_rows)}(完整列表见 JSON `rows` 或下表节选)。\n")
lines.append("\n### 2.2 `MISSING_DEPLOY_JAVAP`\n\n")
lines.append("- **含义:** 本地可 `javap`,部署 classpath 中 **找不到该类**(多为 **仓库新增类**,现场包尚未包含)。\n")
lines.append("- **建议:** 纳入发布变更说明,**部署新 Fat JAR** 或通过配置开关控制新功能。\n")
lines.append(f"\n**数量:** {len(miss_d)}\n")
lines.append("\n### 2.3 `MISSING_LOCAL_JAVAP`\n\n")
lines.append("- **含义:** 源码存在但 **未编译进 target/classes**(工程错误、条件编译、或 `package-info` 等特殊文件)。\n")
lines.append("- **建议:** `mvn clean compile`;检查模块归属;`package-info` 可忽略或单独标注。\n")
lines.append(f"\n**数量:** {len(miss_l)}\n")
lines.append("\n### 2.4 附录 B:部署侧多出类型\n\n")
lines.append("- **含义:** 现场 JAR 内含 **Starter 配置类、生成器、旧版独占类等**,当前仓库 **未以 `.java` 形式收录**(尤其 `cwos-component-organization-starter` 仅保留 `OrganizationServer`)。\n")
lines.append("- **建议:** 从现场 Fat JAR **反编译或回收历史分支** 补齐 Starter 与缺失资源(MyBatis XML、`component-org/messages*.properties`),使仓库可 **重现现场构建**。\n")
lines.append("\n## 3. DIFFERENT / MISSING 文件表(全量)\n\n")
lines.append("| 状态 | FQN | 文件 |\n|------|-----|------|\n")
for r in rows:
st = r.get("status", "")
if st in ("IDENTICAL", "SKIP_PATH"):
continue
fq = r.get("fqn") or ""
fn = r.get("file") or ""
lines.append(f"| {st} | `{fq}` | `{fn}` |\n")
lines.append("\n## 4. 附录 AIDENTICAL 统计\n\n")
lines.append(f"- 与部署 `javap` **完全一致** 的顶层类数量:**{summary['status_counts'].get('IDENTICAL', 0)}**(明细见 JSON)。\n")
lines.append("\n## 5. 附录 B:部署包顶层类型(源码无同名 `.java` 路径推导)节选\n\n")
for fq in only_deploy[:200]:
lines.append(f"- `{fq}`\n")
if len(only_deploy) > 200:
lines.append(f"\n… 另有 {len(only_deploy) - 200} 项,见 JSON `deploy_only_outer_fqns`。\n")
path.write_text("".join(lines), encoding="utf-8")
if __name__ == "__main__":
sys.exit(main())