| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164 |
- # -*- coding: utf-8 -*-
- """把 store / admin 模块控制器的 @RequestMapping(method={GET,POST}) 改成 @GetMapping / @PostMapping。
- HTTP 方法依据两个后台前端仓库的实际 axios 调用:
- store: F:/shop/yoshop2.0-store/src/api/**/*.js
- admin: F:/shop/yoshop2.0-admin/src/api/api.config.js + 各调用文件(接口少,显式列表)
- 用法:python migrate_store_admin_mapping.py [--apply]
- """
- import re
- import sys
- from pathlib import Path
- sys.path.insert(0, str(Path(__file__).parent))
- from _root import ROOT # noqa: E402
- JAVA = ROOT / "yoshop-java/src/main/java/cn/yoshop"
- STORE_API = Path(r"F:/shop/yoshop2.0-store/src/api")
- APPLY = "--apply" in sys.argv
- # admin 前端接口 -> 方法(api.config.js 里的 key 与调用处 method 对应)
- ADMIN_METHODS = {
- "/admin/passport/login": "Post",
- "/admin/passport/logout": "Post",
- "/admin/admin.user/detail": "Get",
- "/admin/admin.user/renew": "Post",
- "/admin/store/index": "Get",
- "/admin/store/superLogin": "Get",
- "/admin/store/recycle": "Get",
- "/admin/store/add": "Post",
- "/admin/store/edit": "Post",
- "/admin/store/recovery": "Post",
- "/admin/store/delete": "Post",
- "/admin/store/move": "Post",
- "/admin/store.api/index": "Get",
- "/admin/store.api/add": "Post",
- "/admin/store.api/edit": "Post",
- "/admin/store.api/delete": "Post",
- "/admin/store.menu/index": "Get",
- "/admin/store.menu/info": "Get",
- "/admin/store.menu/add": "Post",
- "/admin/store.menu/edit": "Post",
- "/admin/store.menu/setApis": "Post",
- "/admin/store.menu/delete": "Post",
- "/admin/setting.cache/clear": "Post",
- "/admin/setting.science/info": "Get",
- "/admin/setting.queue/test": "Post",
- "/admin/setting.timer/test": "Post",
- }
- MAP_RE = re.compile(r"(\w+)\s*:\s*'([^']+)'")
- CALL_RE = re.compile(r"axios\(\{(.*?)\}\)", re.S)
- URL_RE = re.compile(r"url:\s*([A-Za-z0-9_.]+|'[^']*')")
- METHOD_RE = re.compile(r"method:\s*'(\w+)'")
- def parse_store_front():
- table = {}
- for f in sorted(STORE_API.rglob("*.js")):
- text = f.read_text(encoding="utf-8", errors="ignore")
- const = re.search(r"const\s+api\s*=\s*\{(.*?)\n\}", text, re.S)
- local = dict(MAP_RE.findall(const.group(1))) if const else {}
- for block in CALL_RE.findall(text):
- um, mm = URL_RE.search(block), METHOD_RE.search(block)
- if not um or not mm:
- continue
- url = um.group(1).strip("'")
- if not url.startswith("/"):
- parts = [p for p in url.split(".") if p != "api"]
- if parts[-1] not in local:
- print(f"!! 跳过无法解析的 url: {url} ({f.name})")
- continue
- url = local[parts[-1]]
- table["/store" + url] = mm.group(1).capitalize()
- return table
- ANN_RE = re.compile(r"@RequestMapping\((.*?)\)", re.S)
- VALUE_RE = re.compile(r'value\s*=\s*(\{[^}]*\}|"[^"]*")')
- METHOD_ATTR_RE = re.compile(r'method\s*=\s*(RequestMethod\.\w+|\{[^}]*\})')
- PRODUCES_RE = re.compile(r'produces\s*=\s*("[^"]*"|[A-Za-z0-9_.]+)')
- def norm_path(p):
- if "_" in p:
- head, *rest = p.split("_")
- return head + "".join(w[:1].upper() + w[1:] for w in rest)
- return p
- def migrate(module: str, methods: dict):
- ctrl = JAVA / module / "controller"
- total = 0
- for f in sorted(ctrl.glob("*.java")):
- src = f.read_text(encoding="utf-8")
- cls = re.search(r'^@RequestMapping\("([^"]+)"\)', src, re.M)
- prefix = cls.group(1) if cls else ""
- missed = []
- def repl(m):
- nonlocal total
- body = m.group(1)
- vm = VALUE_RE.search(body)
- if not vm:
- return m.group(0)
- value_expr = vm.group(1)
- paths = re.findall(r'"([^"]*)"', value_expr)
- full = prefix + paths[0]
- key = norm_path(full)
- if key not in methods:
- missed.append(full)
- return m.group(0)
- ann = methods[key]
- parts = [f"value = {value_expr}"]
- pm = PRODUCES_RE.search(body)
- if pm:
- parts.append(f"produces = {pm.group(1)}")
- total += 1
- return f"@{ann}Mapping({', '.join(parts)})"
- new_src = ANN_RE.sub(repl, src)
- if missed:
- print(f" !! {module}/{f.name} 前端未找到: {missed}")
- if new_src == src:
- continue
- if "RequestMethod." not in new_src:
- new_src = new_src.replace(
- "import org.springframework.web.bind.annotation.RequestMethod;\n", "")
- for name in ("GetMapping", "PostMapping"):
- imp = f"import org.springframework.web.bind.annotation.{name};"
- if f"@{name}(" in new_src and imp not in new_src:
- new_src = new_src.replace(
- "import org.springframework.web.bind.annotation.RequestMapping;",
- imp + "\nimport org.springframework.web.bind.annotation.RequestMapping;", 1)
- # 组内排序,tools.jackson / com.baomidou 保持原有靠前位置
- lines = new_src.split("\n")
- out, i = [], 0
- while i < len(lines):
- if lines[i].startswith("import "):
- j = i
- while j < len(lines) and lines[j].startswith("import "):
- j += 1
- grp = lines[i:j]
- head = [x for x in grp if x.startswith(("import com.baomidou.", "import tools.jackson."))]
- rest = sorted(x for x in grp if x not in head)
- out.extend(sorted(head) + rest)
- i = j
- else:
- out.append(lines[i])
- i += 1
- new_src = "\n".join(out)
- if APPLY:
- f.write_text(new_src, encoding="utf-8")
- return total
- store_methods = parse_store_front()
- # 前端不调用的 PHP 生命周期钩子(无副作用、无请求体):按浏览器/探活方式用 GET
- store_methods["/store/controller/initialize"] = "Get"
- store_methods["/store/statistics.data/initialize"] = "Get"
- print("store 前端接口数:", len(store_methods))
- n1 = migrate("store", store_methods)
- n2 = migrate("admin", ADMIN_METHODS)
- print(f"{'已改写' if APPLY else '待改写'} store={n1} admin={n2}")
|