| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004 |
- # -*- coding: utf-8 -*-
- """Generate a complete JeeSite MES inventory for RuoYi-Vue migration."""
- from __future__ import annotations
- import os
- import re
- import sys
- from collections import defaultdict
- from datetime import datetime
- from pathlib import Path
- ROOT = Path(r"D:\IdeaProjects\mes-199-0Y7A\mescloud")
- JAVA_MES = ROOT / "src/main/java/com/jeesite/modules/mes"
- JAVA_UTILS = ROOT / "src/main/java/com/jeesite/modules/utils"
- VIEWS = ROOT / "src/main/resources/views/modules/mes"
- WEB = JAVA_MES / "web"
- ENTITY = JAVA_MES / "entity"
- SQL = ROOT / "mes_cloud_199_0y7a.sql"
- APP = ROOT / "src/main/java/com/jeesite/modules/Application.java"
- STANDARD_CRUD = {"list", "listData", "form", "save", "delete", "disable", "enable"}
- def read_text(p: Path) -> str:
- return p.read_text(encoding="utf-8", errors="replace")
- def match_parens(s: str, start: int) -> int:
- """start points at '('; return index after matching ')'."""
- depth = 0
- i = start
- in_str = False
- quote = ""
- while i < len(s):
- c = s[i]
- if in_str:
- if c == "\\" and i + 1 < len(s):
- i += 2
- continue
- if c == quote:
- in_str = False
- i += 1
- continue
- if c in ('"', "'"):
- in_str = True
- quote = c
- i += 1
- continue
- if c == "(":
- depth += 1
- elif c == ")":
- depth -= 1
- if depth == 0:
- return i + 1
- i += 1
- return -1
- def match_braces(s: str, start: int) -> int:
- depth = 0
- i = start
- in_str = False
- quote = ""
- while i < len(s):
- c = s[i]
- if in_str:
- if c == "\\" and i + 1 < len(s):
- i += 2
- continue
- if c == quote:
- in_str = False
- i += 1
- continue
- if c in ('"', "'"):
- in_str = True
- quote = c
- i += 1
- continue
- if c == "{":
- depth += 1
- elif c == "}":
- depth -= 1
- if depth == 0:
- return i + 1
- i += 1
- return -1
- def attr_str(block: str, key: str) -> str | None:
- m = re.search(rf'{key}\s*=\s*"([^"]*)"', block)
- return m.group(1) if m else None
- def attr_ident(block: str, key: str) -> str | None:
- m = re.search(rf"{key}\s*=\s*([A-Za-z0-9_.]+)", block)
- return m.group(1) if m else None
- def parse_mapping_value(ann: str) -> str:
- """Parse Spring mapping annotation body into a display string."""
- body = ann.strip()
- if body.startswith("(") and body.endswith(")"):
- body = body[1:-1].strip()
- if not body:
- return ""
- m = re.search(r"value\s*=\s*", body)
- if m:
- rest = body[m.end() :].lstrip()
- if rest.startswith("{"):
- end = match_braces(rest, 0)
- inner = rest[1 : end - 1]
- parts = re.findall(r'"([^"]*)"', inner)
- return "{" + ", ".join(f'"{p}"' for p in parts) + "}"
- m2 = re.match(r'"([^"]*)"', rest)
- if m2:
- return m2.group(1)
- return rest.split(",")[0].strip()
- m2 = re.match(r'"([^"]*)"', body)
- if m2:
- return m2.group(1)
- if body.startswith("{"):
- end = match_braces(body, 0)
- inner = body[1 : end - 1]
- parts = re.findall(r'"([^"]*)"', inner)
- return "{" + ", ".join(f'"{p}"' for p in parts) + "}"
- return body.split(",")[0].strip()
- def mapping_paths(val: str) -> list[str]:
- if not val:
- return [""]
- if val.startswith("{") and val.endswith("}"):
- return re.findall(r'"([^"]*)"', val) or [""]
- return [val]
- # ---------------------------------------------------------------------------
- # 1. Views
- # ---------------------------------------------------------------------------
- def collect_views():
- groups = {"List": [], "Form": [], "Info": [], "Screen": [], "other": []}
- files = sorted(VIEWS.rglob("*.html"))
- for f in files:
- rel = f.relative_to(VIEWS).as_posix()
- name = f.name
- # Screen dashboards: mesScreen.html / mesScreenN.html (not mesScreenPlan*)
- if re.fullmatch(r"mesScreen(\d*)\.html", name, re.I):
- groups["Screen"].append(rel)
- elif "List" in name:
- groups["List"].append(rel)
- elif "Form" in name:
- groups["Form"].append(rel)
- elif "Info" in name:
- groups["Info"].append(rel)
- else:
- groups["other"].append(rel)
- return files, groups
- # ---------------------------------------------------------------------------
- # 2. Controllers
- # ---------------------------------------------------------------------------
- ANN_RE = re.compile(
- r"@(RequestMapping|GetMapping|PostMapping|PutMapping|DeleteMapping|PatchMapping)\s*(\([^;{]*?\)|(?=\s))",
- re.S,
- )
- def strip_java_comments(src: str) -> str:
- src = re.sub(r"/\*.*?\*/", lambda m: "\n" * m.group(0).count("\n"), src, flags=re.S)
- src = re.sub(r"//.*?$", "", src, flags=re.M)
- return src
- def parse_controller(path: Path) -> dict:
- raw = read_text(path)
- src = strip_java_comments(raw)
- class_m = re.search(r"public\s+class\s+(\w+)", src)
- if not class_m:
- return None
- cls = class_m.group(1)
- class_annos = src[: class_m.start()]
- class_path = ""
- for m in re.finditer(r"@RequestMapping\s*(\([^)]*(?:\([^)]*\)[^)]*)*\)|)", class_annos):
- ann = m.group(1) or ""
- # handle nested parens better
- start = m.start()
- if src[m.end() - 1 if m.group(1) else m.end() :]:
- pass
- at = src.find("@RequestMapping", 0)
- # more reliable: find last @RequestMapping before class
- last = None
- for m in re.finditer(r"@RequestMapping", class_annos):
- last = m
- if last:
- i = last.end()
- while i < len(src) and src[i].isspace():
- i += 1
- if i < len(src) and src[i] == "(":
- end = match_parens(src, i)
- class_path = parse_mapping_value(src[i:end])
- else:
- class_path = ""
- methods = []
- # Find mapping annotations followed eventually by a public method
- i = class_m.end()
- while True:
- m = re.search(
- r"@(RequestMapping|GetMapping|PostMapping|PutMapping|DeleteMapping|PatchMapping)\b",
- src[i:],
- )
- if not m:
- break
- abs_start = i + m.start()
- kind = m.group(1)
- j = i + m.end()
- while j < len(src) and src[j].isspace():
- j += 1
- if j < len(src) and src[j] == "(":
- end = match_parens(src, j)
- val = parse_mapping_value(src[j:end])
- after = end
- else:
- val = ""
- after = j
- # look ahead for public method (skip other annotations)
- ahead = src[after : after + 800]
- mm = re.search(
- r"public\s+(?:static\s+)?(?:[\w.<>,\s?\[\]]+)\s+(\w+)\s*\(",
- ahead,
- )
- if mm:
- name = mm.group(1)
- # skip constructors
- if name != cls:
- flag = name not in STANDARD_CRUD
- methods.append(
- {
- "name": name,
- "http": kind,
- "path": val,
- "business": flag,
- }
- )
- i = after
- return {"class": cls, "path": class_path, "file": path.name, "methods": methods}
- def collect_controllers():
- out = []
- for f in sorted(WEB.glob("*.java")):
- c = parse_controller(f)
- if c:
- out.append(c)
- return out
- # ---------------------------------------------------------------------------
- # 3. Entities
- # ---------------------------------------------------------------------------
- def split_top_level_annos(block: str, prefix: str) -> list[str]:
- """Split `@Xxx(...)` occurrences at top level of a `{...}` array."""
- items = []
- i = 0
- token = "@" + prefix
- while True:
- j = block.find(token, i)
- if j < 0:
- break
- k = j + len(token)
- while k < len(block) and block[k].isspace():
- k += 1
- if k < len(block) and block[k] == "(":
- end = match_parens(block, k)
- items.append(block[j:end])
- i = end
- else:
- i = k
- return items
- def parse_column_ann(ann: str) -> dict:
- body = ann[ann.find("(") :] if "(" in ann else ""
- return {
- "name": attr_str(body, "name") or "",
- "attrName": attr_str(body, "attrName") or "",
- "label": attr_str(body, "label") or "",
- }
- def parse_join_ann(ann: str) -> dict:
- body = ann[ann.find("(") :] if "(" in ann else ann
- entity = attr_ident(body, "entity") or ""
- alias = attr_str(body, "alias") or ""
- on = attr_str(body, "on") or ""
- typ = (attr_ident(body, "type") or "").replace("Type.", "").replace("JoinTable.", "")
- cols = []
- cm = re.search(r"columns\s*=\s*\{", body)
- if cm:
- end = match_braces(body, cm.end() - 1)
- inner = body[cm.end() - 1 : end]
- for c in split_top_level_annos(inner, "Column"):
- cols.append(parse_column_ann(c))
- return {
- "type": typ.replace("Type.", ""),
- "entity": entity.replace(".class", ""),
- "alias": alias,
- "on": on,
- "columns": cols,
- }
- def parse_entity(path: Path) -> dict:
- src = read_text(path)
- class_m = re.search(r"public\s+class\s+(\w+)", src)
- cls = class_m.group(1) if class_m else path.stem
- rec = {
- "class": cls,
- "file": path.name,
- "table": None,
- "label": None,
- "alias": None,
- "columns": [],
- "joins": [],
- "has_table": False,
- }
- tpos = src.find("@Table")
- if tpos < 0:
- return rec
- i = tpos + len("@Table")
- while i < len(src) and src[i].isspace():
- i += 1
- if i >= len(src) or src[i] != "(":
- return rec
- end = match_parens(src, i)
- body = src[i:end]
- rec["has_table"] = True
- rec["table"] = attr_str(body, "name")
- rec["label"] = attr_str(body, "label")
- rec["alias"] = attr_str(body, "alias")
- # columns at Table level: first columns={ that is not nested in joinTable
- # Extract joinTable first, then columns outside it
- join_m = re.search(r"joinTable\s*=\s*\{", body)
- join_span = None
- if join_m:
- jend = match_braces(body, join_m.end() - 1)
- join_span = (join_m.start(), jend)
- inner = body[join_m.end() - 1 : jend]
- for j in split_top_level_annos(inner, "JoinTable"):
- rec["joins"].append(parse_join_ann(j))
- # find columns= that is NOT inside join_span
- for cm in re.finditer(r"columns\s*=\s*\{", body):
- if join_span and join_span[0] <= cm.start() < join_span[1]:
- continue
- cend = match_braces(body, cm.end() - 1)
- inner = body[cm.end() - 1 : cend]
- for c in split_top_level_annos(inner, "Column"):
- rec["columns"].append(parse_column_ann(c))
- break
- return rec
- def collect_entities():
- out = []
- for f in sorted(ENTITY.glob("*.java")):
- out.append(parse_entity(f))
- return out
- # ---------------------------------------------------------------------------
- # 4. SQL menus + tables
- # ---------------------------------------------------------------------------
- def split_sql_values(s: str) -> list[str]:
- vals = []
- i = 0
- n = len(s)
- while i < n:
- while i < n and s[i] in " \t\r\n":
- i += 1
- if i >= n:
- break
- if s[i] == "'":
- i += 1
- buf = []
- while i < n:
- if s[i] == "'" and i + 1 < n and s[i + 1] == "'":
- buf.append("'")
- i += 2
- continue
- if s[i] == "'":
- i += 1
- break
- buf.append(s[i])
- i += 1
- vals.append("".join(buf))
- while i < n and s[i] in " \t\r\n":
- i += 1
- if i < n and s[i] == ",":
- i += 1
- continue
- # unquoted
- j = i
- while j < n and s[j] != ",":
- j += 1
- token = s[i:j].strip()
- vals.append(None if token.upper() == "NULL" else token)
- i = j + 1 if j < n and s[j] == "," else j
- return vals
- def collect_sql():
- tables = []
- menus = []
- if not SQL.exists():
- return tables, menus
- # Stream line by line — dump can be large
- with SQL.open(encoding="utf-8", errors="replace") as fh:
- for line in fh:
- if line.startswith("CREATE TABLE `mes_"):
- m = re.match(r"CREATE TABLE `(mes_[^`]+)`", line)
- if m:
- tables.append({"name": m.group(1), "comment": None})
- continue
- if tables and tables[-1]["comment"] is None:
- cm = re.search(r"COMMENT\s*=\s*'([^']*)'", line)
- if cm:
- tables[-1]["comment"] = cm.group(1)
- if line.startswith(")") or line.startswith("CREATE TABLE") or line.startswith("INSERT"):
- if tables[-1]["comment"] is None:
- tables[-1]["comment"] = ""
- if "INSERT INTO `js_sys_menu`" in line:
- m = re.search(r"VALUES\s*\((.*)\)\s*;?\s*$", line)
- if not m:
- continue
- rec = parse_menu_values_tuple(m.group(1))
- if rec:
- rec["_source"] = "mes_cloud_199_0y7a.sql"
- menus.append(rec)
- return tables, menus
- def parse_menu_values_tuple(inner: str) -> dict | None:
- vals = split_sql_values(inner.strip().rstrip(";").strip())
- if len(vals) < 20:
- return None
- return {
- "menu_code": vals[0] or "",
- "parent_code": vals[1] or "",
- "parent_codes": vals[2] or "",
- "tree_sort": vals[3],
- "tree_leaf": vals[5],
- "tree_level": vals[6],
- "tree_names": vals[7] or "",
- "menu_name": vals[8] or "",
- "menu_type": str(vals[9] or "").strip("'"),
- "menu_href": vals[10] or "",
- "menu_target": vals[11] or "",
- "menu_icon": vals[12] or "",
- "menu_color": vals[13] or "",
- "menu_title": vals[14] or "",
- "permission": vals[15] or "",
- "weight": vals[16],
- "is_show": str(vals[17] or "").strip("'"),
- "sys_code": vals[18] or "",
- "module_codes": vals[19] or "",
- "status": vals[22] if len(vals) > 22 else "",
- "_source": "dump",
- }
- def collect_incremental_menus(existing_codes: set[str]):
- extra = []
- ddl_dir = ROOT / "db" / "mysql"
- if not ddl_dir.exists():
- return extra
- skip = {"mes_cloud-2023-6-10.sql"}
- for f in sorted(ddl_dir.glob("*.sql")):
- if f.name in skip:
- continue
- txt = read_text(f)
- for m in re.finditer(
- r"INSERT INTO `js_sys_menu`(?:\s*\([^)]+\))?\s*VALUES\s*",
- txt,
- re.I,
- ):
- i = m.end()
- while i < len(txt) and txt[i].isspace():
- i += 1
- while i < len(txt) and txt[i] == "(":
- end = match_parens(txt, i)
- rec = parse_menu_values_tuple(txt[i + 1 : end - 1])
- if rec and rec["menu_code"] not in existing_codes:
- rec["_source"] = f.name
- extra.append(rec)
- existing_codes.add(rec["menu_code"])
- i = end
- while i < len(txt) and txt[i] in " \t\r\n":
- i += 1
- if i < len(txt) and txt[i] == ",":
- i += 1
- while i < len(txt) and txt[i].isspace():
- i += 1
- else:
- break
- for m in re.finditer(
- r"INSERT INTO `js_sys_menu`\s*SELECT\s+",
- txt,
- re.I,
- ):
- rest = txt[m.end() :]
- fm = re.search(r"\bFROM\b", rest, re.I)
- if not fm:
- continue
- rec = parse_menu_values_tuple(rest[: fm.start()])
- if rec and rec["menu_code"] not in existing_codes:
- rec["_source"] = f.name
- extra.append(rec)
- existing_codes.add(rec["menu_code"])
- return extra
- def is_mes_menu(m: dict) -> bool:
- mc = (m.get("module_codes") or "").lower()
- href = (m.get("menu_href") or "").lower()
- perm = (m.get("permission") or "").lower()
- parts = [p.strip() for p in mc.replace(";", ",").split(",") if p.strip()]
- if "mes" in parts:
- return True
- if "/mes/" in href or href.startswith("mes/") or href.startswith("/mes"):
- return True
- if "mes:" in perm:
- return True
- return False
- def is_visible_menu(m: dict) -> bool:
- return str(m.get("menu_type")) == "1" and str(m.get("is_show")) == "1"
- def sort_menu_key(m: dict):
- try:
- ts = float(m.get("tree_sort") or 0)
- except (TypeError, ValueError):
- ts = 0
- try:
- lv = float(m.get("tree_level") or 0)
- except (TypeError, ValueError):
- lv = 0
- return (lv, ts, m.get("menu_name") or "")
- def build_menu_tree(all_menus, mes_menus):
- """Nest visible MES menus under ancestor folders (folders often use module_codes=app)."""
- by_code = {m["menu_code"]: m for m in all_menus}
- visible_mes = [m for m in mes_menus if is_visible_menu(m)]
- included = {m["menu_code"]: m for m in visible_mes}
- for m in list(visible_mes):
- pc = m.get("parent_code") or "0"
- while pc and pc != "0" and pc in by_code:
- if pc not in included:
- parent = by_code[pc]
- if is_visible_menu(parent):
- included[pc] = parent
- parent["_ancestor_only"] = not is_mes_menu(parent)
- else:
- break
- pc = by_code[pc].get("parent_code") or "0"
- tree_nodes = list(included.values())
- visible_codes = set(included.keys())
- children = defaultdict(list)
- roots = []
- for m in sorted(tree_nodes, key=sort_menu_key):
- parent = m.get("parent_code") or "0"
- if parent in visible_codes:
- children[parent].append(m)
- else:
- roots.append(m)
- for k in children:
- children[k] = sorted(children[k], key=sort_menu_key)
- roots = sorted(roots, key=sort_menu_key)
- return roots, children, by_code, visible_mes
- def render_tree(roots, children, by_code, indent=0):
- lines = []
- def rec(nodes, level):
- for n in nodes:
- pad = " " * level
- parent_name = ""
- p = by_code.get(n["parent_code"])
- if p:
- parent_name = p["menu_name"]
- href = n["menu_href"] or "-"
- perm = n["permission"] or "-"
- icon = n["menu_icon"] or "-"
- mark = " *(folder ancestor)*" if n.get("_ancestor_only") else ""
- lines.append(
- f"{pad}- **{n['menu_name']}**{mark} href=`{href}` permission=`{perm}` icon=`{icon}` parent=`{parent_name or n['parent_code']}`"
- )
- rec(children.get(n["menu_code"], []), level + 1)
- rec(roots, 0)
- return lines
- # ---------------------------------------------------------------------------
- # 5. Packages
- # ---------------------------------------------------------------------------
- def list_java(dirpath: Path):
- if not dirpath.exists():
- return []
- return sorted(p.name for p in dirpath.glob("*.java"))
- def list_packages():
- pkgs = {}
- if JAVA_MES.exists():
- for d in sorted(p for p in JAVA_MES.iterdir() if p.is_dir()):
- pkgs[d.name] = list_java(d)
- return pkgs
- # ---------------------------------------------------------------------------
- # Extra DDL tables not in dump
- # ---------------------------------------------------------------------------
- def extra_ddl_tables():
- extra = []
- ddl_dir = ROOT / "db" / "mysql"
- if not ddl_dir.exists():
- return extra
- for f in sorted(ddl_dir.glob("*.sql")):
- txt = read_text(f)
- for m in re.finditer(r"CREATE TABLE(?:\s+IF NOT EXISTS)?\s+`?(mes_[a-z0-9_]+)`?", txt, re.I):
- extra.append((m.group(1).lower(), f.name))
- for m in re.finditer(r"ALTER TABLE\s+`?(mes_[a-z0-9_]+)`?", txt, re.I):
- extra.append((m.group(1).lower() + " (ALTER)", f.name))
- return extra
- def parse_netty():
- ports = []
- notes = []
- if APP.exists():
- src = read_text(APP)
- for m in re.finditer(r"new\s+(NettyServer|LdNettyServer)\s*\(\s*(\d+)\s*\)", src):
- commented = False
- # check if this occurrence is inside a line comment or block
- line_start = src.rfind("\n", 0, m.start()) + 1
- line = src[line_start : src.find("\n", m.start())]
- if line.lstrip().startswith("//") or "/*" in src[max(0, m.start() - 80) : m.start()]:
- # crude: if // before on same line
- before = src[line_start : m.start()]
- if "//" in before:
- commented = True
- ports.append(
- {
- "class": m.group(1),
- "port": int(m.group(2)),
- "active": not commented,
- "line": line.strip(),
- }
- )
- if "new LdNettyServer(7890)" in src:
- # specifically noted even if commented
- pass
- return ports
- # ---------------------------------------------------------------------------
- # Markdown
- # ---------------------------------------------------------------------------
- def md_escape(s: str) -> str:
- return (s or "").replace("|", "\\|")
- def write_md(path: Path, data: dict):
- views_files, view_groups = data["views"]
- controllers = data["controllers"]
- entities = data["entities"]
- tables, menus = data["sql"]
- mes_menus = [m for m in menus if is_mes_menu(m)]
- roots, children, by_code, visible = build_menu_tree(menus, mes_menus)
- pkgs = data["packages"]
- netty = data["netty"]
- extra = data["extra_ddl"]
- utils = data["utils"]
- biz_methods = 0
- for c in controllers:
- biz_methods += sum(1 for m in c["methods"] if m["business"])
- with_table = [e for e in entities if e["has_table"]]
- without_table = [e for e in entities if not e["has_table"]]
- dump_names = {t["name"] for t in tables}
- extra_new = sorted({n for n, _ in extra if not n.endswith("(ALTER)") and n not in dump_names})
- lines = []
- a = lines.append
- a("# JeeSite MES Inventory (mescloud)")
- a("")
- a(f"- Source: `{ROOT}`")
- a(f"- Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
- a("- Purpose: rebuild this MES on RuoYi-Vue")
- a("")
- a("## Summary counts")
- a("")
- a("| Item | Count |")
- a("|---|---|")
- a(f"| Controllers (`mes.web`) | {len(controllers)} |")
- a(f"| Controller methods (mapped) | {sum(len(c['methods']) for c in controllers)} |")
- a(f"| BUSINESS/CLIENT API methods | {biz_methods} |")
- a(f"| Entity classes | {len(entities)} |")
- a(f"| Entities with `@Table` | {len(with_table)} |")
- a(f"| HTML views (modules/mes) | {len(views_files)} |")
- a(f"| List pages | {len(view_groups['List'])} |")
- a(f"| Form pages | {len(view_groups['Form'])} |")
- a(f"| Info pages | {len(view_groups['Info'])} |")
- a(f"| Screen pages (`mesScreen*.html` dashboards) | {len(view_groups['Screen'])} |")
- a(f"| Other HTML | {len(view_groups['other'])} |")
- a(f"| `mes_` tables in SQL dump | {len(tables)} |")
- a(f"| MES-related `js_sys_menu` rows | {len(mes_menus)} |")
- a(f"| Visible MES menus (`menu_type=1`, `is_show=1`) | {len(visible)} |")
- a(f"| DAO classes | {len(pkgs.get('dao', []))} |")
- a(f"| Service classes | {len(pkgs.get('service', []))} |")
- a(f"| req DTOs | {len(pkgs.get('req', []))} |")
- a(f"| resp DTOs | {len(pkgs.get('resp', []))} |")
- a(f"| mes.util | {len(pkgs.get('util', []))} |")
- a(f"| com.jeesite.modules.utils (Netty etc.) | {len(utils)} |")
- a("")
- a("### Netty ports (`Application.java`)")
- a("")
- if netty:
- for n in netty:
- state = "ACTIVE" if n["active"] else "COMMENTED"
- a(f"- `{n['class']}({n['port']})` — **{state}** — `{n['line']}`")
- else:
- a("- (none parsed)")
- a("")
- a("- Active TCP: **NettyServer on port 3000** (client communications).")
- a("- Commented: `LdNettyServer(7890)` (laser engraving / 镭雕机 TCP).")
- a("")
- # ------------------------------------------------------------------
- a("## 1. HTML views (`src/main/resources/views/modules/mes/`)")
- a("")
- a(f"Total: **{len(views_files)}**. Grouped by filename convention (Screen = `mesScreen.html` / `mesScreenN.html` only; `mesScreenPlan*` goes to List/Form).")
- a("")
- for g in ("List", "Form", "Info", "Screen", "other"):
- a(f"### 1.{['List','Form','Info','Screen','other'].index(g)+1} {g} ({len(view_groups[g])})")
- a("")
- for rel in view_groups[g]:
- a(f"- `{rel}`")
- a("")
- # ------------------------------------------------------------------
- a("## 2. Controllers (`com.jeesite.modules.mes.web`)")
- a("")
- a("Standard CRUD method names: `list`, `listData`, `form`, `save`, `delete`, `disable`, `enable`.")
- a("Anything else is flagged **BUSINESS/CLIENT API**.")
- a("")
- a(f"Total controllers: **{len(controllers)}**.")
- a("")
- for c in controllers:
- a(f"### `{c['class']}`")
- a("")
- a(f"- File: `{c['file']}`")
- a(f"- Class `@RequestMapping`: `{c['path'] or '(none)'}`")
- a(f"- Methods: {len(c['methods'])}")
- a("")
- if c["methods"]:
- a("| Method | HTTP | Mapping | Flag |")
- a("|---|---|---|---|")
- for m in c["methods"]:
- flag = "BUSINESS/CLIENT API" if m["business"] else "CRUD"
- a(f"| `{m['name']}` | {m['http']} | `{md_escape(m['path'])}` | {flag} |")
- a("")
- else:
- a("_No mapped public methods found._")
- a("")
- a("### 2.x BUSINESS/CLIENT API index")
- a("")
- a("| Controller | Method | Mapping |")
- a("|---|---|---|")
- for c in controllers:
- for m in c["methods"]:
- if m["business"]:
- a(f"| `{c['class']}` | `{m['name']}` | `{md_escape(m['path'])}` |")
- a("")
- # ------------------------------------------------------------------
- a("## 3. Entities (`com.jeesite.modules.mes.entity`)")
- a("")
- a(f"Total classes: **{len(entities)}**. With `@Table`: **{len(with_table)}**. Without `@Table` (DTO/export): **{len(without_table)}**.")
- a("")
- if without_table:
- a("### 3.0 Entities without `@Table`")
- a("")
- for e in without_table:
- a(f"- `{e['class']}` (`{e['file']}`)")
- a("")
- for e in with_table:
- a(f"### `{e['class']}`")
- a("")
- a(f"- Table: `{e['table'] or '?'}`")
- a(f"- Label: {e['label'] or '-'}")
- a(f"- Alias: `{e['alias'] or '-'}`")
- a("")
- if e["columns"]:
- a("| Column | attrName | label |")
- a("|---|---|---|")
- for col in e["columns"]:
- a(f"| `{col['name']}` | `{col['attrName']}` | {md_escape(col['label'])} |")
- a("")
- else:
- a("_No `@Column` parsed._")
- a("")
- if e["joins"]:
- a("Join tables:")
- a("")
- for j in e["joins"]:
- a(f"- type=`{j['type']}` entity=`{j['entity']}` alias=`{j['alias']}` on=`{j['on']}`")
- if j["columns"]:
- a(" | Column | attrName | label |")
- a(" |---|---|---|")
- for col in j["columns"]:
- a(f" | `{col['name']}` | `{col['attrName']}` | {md_escape(col['label'])} |")
- a("")
- # ------------------------------------------------------------------
- a("## 4. MES menus (`js_sys_menu`)")
- a("")
- a("Filter: `module_codes` contains `mes`, **or** `menu_href` contains `/mes/`, **or** `permission` contains `mes:`.")
- a("Top-level folders (生产管理, 设备管理, 系统管理, …) often have `module_codes=app`/`core` and empty href; they are pulled into the tree as **folder ancestors** so nesting is preserved.")
- a("Also merged incremental `INSERT INTO js_sys_menu` from `db/mysql/*.sql` (except the old 2023 dump).")
- a("")
- a(f"- All menu INSERT rows parsed: **{len(menus)}**")
- a(f"- MES-related rows: **{len(mes_menus)}**")
- a(f"- Visible MES menus (`menu_type=1` AND `is_show=1`): **{len(visible)}**")
- a("")
- a("### 4.1 Visible menu tree")
- a("")
- tree_lines = render_tree(roots, children, by_code)
- if tree_lines:
- lines.extend(tree_lines)
- else:
- a("_No visible MES menus._")
- a("")
- a("### 4.2 All MES-related menu rows")
- a("")
- a("| menu_code | parent | name | type | href | permission | icon | is_show | module_codes | source |")
- a("|---|---|---|---|---|---|---|---|---|---|")
- for m in sorted(mes_menus, key=sort_menu_key):
- a(
- "| `{code}` | `{parent}` | {name} | {typ} | `{href}` | `{perm}` | `{icon}` | {show} | `{mod}` | {src} |".format(
- code=md_escape(m["menu_code"]),
- parent=md_escape(m["parent_code"]),
- name=md_escape(m["menu_name"]),
- typ=m["menu_type"],
- href=md_escape(m["menu_href"] or ""),
- perm=md_escape(m["permission"] or ""),
- icon=md_escape(m["menu_icon"] or ""),
- show=m["is_show"],
- mod=md_escape(m["module_codes"] or ""),
- src=md_escape(m.get("_source") or ""),
- )
- )
- a("")
- # ------------------------------------------------------------------
- a("## 5. Packages")
- a("")
- a("### 5.1 `com.jeesite.modules.mes` subpackages")
- a("")
- for name, files in pkgs.items():
- a(f"#### `{name}/` ({len(files)})")
- a("")
- for fn in files:
- a(f"- `{fn}`")
- a("")
- a("### 5.2 `com.jeesite.modules.utils` (Netty / protocol)")
- a("")
- a(f"Count: **{len(utils)}**")
- a("")
- for fn in utils:
- a(f"- `{fn}`")
- a("")
- # ------------------------------------------------------------------
- a("## 6. `mes_` tables (`CREATE TABLE` in SQL dump)")
- a("")
- a(f"Count: **{len(tables)}**")
- a("")
- a("| Table | Comment |")
- a("|---|---|")
- for t in tables:
- a(f"| `{t['name']}` | {md_escape(t['comment'] or '')} |")
- a("")
- a("### 6.1 Extra DDL under `db/mysql/` (not necessarily in dump)")
- a("")
- if extra:
- for name, src in extra:
- a(f"- `{name}` — `{src}`")
- else:
- a("- (none)")
- a("")
- if extra_new:
- a("Tables created in incremental DDL but **absent from dump**:")
- a("")
- for n in extra_new:
- a(f"- `{n}`")
- a("")
- # ------------------------------------------------------------------
- a("## 7. Screen pages and Netty")
- a("")
- a("### 7.1 Screen HTML (`mesScreen*.html` dashboards)")
- a("")
- for rel in view_groups["Screen"]:
- a(f"- `{rel}`")
- a("")
- a("Related (not dashboards): `mesScreenPlanList.html`, `mesScreenPlanForm.html` (plan CRUD).")
- a("")
- a("### 7.2 Netty")
- a("")
- a("| Server class | Port | Status | Source |")
- a("|---|---|---|---|")
- a("| `com.jeesite.modules.utils.NettyServer` | 3000 | **ACTIVE** | `Application.run()` starts `new NettyServer(3000).run()` in a thread |")
- a("| `com.jeesite.modules.utils.LdNettyServer` | 7890 | commented out | `Application.java` (镭雕机 TCP) |")
- a("")
- a("HTTP server port is configured separately (`BaseConfig.server_port` default `8980` / `server.port`).")
- a("")
- a("---")
- a("")
- a("_End of inventory._")
- path.parent.mkdir(parents=True, exist_ok=True)
- path.write_text("\n".join(lines) + "\n", encoding="utf-8")
- return {
- "controllers": len(controllers),
- "entities": len(entities),
- "list": len(view_groups["List"]),
- "form": len(view_groups["Form"]),
- "info": len(view_groups["Info"]),
- "screen": len(view_groups["Screen"]),
- "other_html": len(view_groups["other"]),
- "views": len(views_files),
- "tables": len(tables),
- "menus_all_mes": len(mes_menus),
- "menus_visible": len(visible),
- "biz": biz_methods,
- "mapped_methods": sum(len(c["methods"]) for c in controllers),
- "dao": len(pkgs.get("dao", [])),
- "service": len(pkgs.get("service", [])),
- "bytes": path.stat().st_size,
- "path": str(path),
- }
- def main():
- print("Collecting views...", flush=True)
- views = collect_views()
- print(f" views={len(views[0])}", flush=True)
- print("Collecting controllers...", flush=True)
- controllers = collect_controllers()
- print(f" controllers={len(controllers)}", flush=True)
- print("Collecting entities...", flush=True)
- entities = collect_entities()
- print(f" entities={len(entities)}", flush=True)
- print("Parsing SQL dump...", flush=True)
- tables, menus = collect_sql()
- extra_menus = collect_incremental_menus({m["menu_code"] for m in menus})
- menus.extend(extra_menus)
- print(f" tables={len(tables)} menus={len(menus)} extra_menus={len(extra_menus)}", flush=True)
- data = {
- "views": views,
- "controllers": controllers,
- "entities": entities,
- "sql": (tables, menus),
- "packages": list_packages(),
- "utils": list_java(JAVA_UTILS),
- "netty": parse_netty(),
- "extra_ddl": extra_ddl_tables(),
- }
- targets = [
- Path(r"D:\mes\_migrate\INVENTORY.md"),
- Path(r"D:\IdeaProjects\mes-199-0Y7A\mescloud\_migrate_inventory.md"),
- ]
- stats = None
- for t in targets:
- try:
- stats = write_md(t, data)
- print(f"Wrote {t} ({stats['bytes']} bytes)", flush=True)
- except Exception as e:
- print(f"FAILED writing {t}: {e}", flush=True)
- print("STATS", stats)
- return 0
- if __name__ == "__main__":
- sys.exit(main())
|