migrate_store_admin_mapping.py 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. # -*- coding: utf-8 -*-
  2. """把 store / admin 模块控制器的 @RequestMapping(method={GET,POST}) 改成 @GetMapping / @PostMapping。
  3. HTTP 方法依据两个后台前端仓库的实际 axios 调用:
  4. store: F:/shop/yoshop2.0-store/src/api/**/*.js
  5. admin: F:/shop/yoshop2.0-admin/src/api/api.config.js + 各调用文件(接口少,显式列表)
  6. 用法:python migrate_store_admin_mapping.py [--apply]
  7. """
  8. import re
  9. import sys
  10. from pathlib import Path
  11. sys.path.insert(0, str(Path(__file__).parent))
  12. from _root import ROOT # noqa: E402
  13. JAVA = ROOT / "yoshop-java/src/main/java/cn/yoshop"
  14. STORE_API = Path(r"F:/shop/yoshop2.0-store/src/api")
  15. APPLY = "--apply" in sys.argv
  16. # admin 前端接口 -> 方法(api.config.js 里的 key 与调用处 method 对应)
  17. ADMIN_METHODS = {
  18. "/admin/passport/login": "Post",
  19. "/admin/passport/logout": "Post",
  20. "/admin/admin.user/detail": "Get",
  21. "/admin/admin.user/renew": "Post",
  22. "/admin/store/index": "Get",
  23. "/admin/store/superLogin": "Get",
  24. "/admin/store/recycle": "Get",
  25. "/admin/store/add": "Post",
  26. "/admin/store/edit": "Post",
  27. "/admin/store/recovery": "Post",
  28. "/admin/store/delete": "Post",
  29. "/admin/store/move": "Post",
  30. "/admin/store.api/index": "Get",
  31. "/admin/store.api/add": "Post",
  32. "/admin/store.api/edit": "Post",
  33. "/admin/store.api/delete": "Post",
  34. "/admin/store.menu/index": "Get",
  35. "/admin/store.menu/info": "Get",
  36. "/admin/store.menu/add": "Post",
  37. "/admin/store.menu/edit": "Post",
  38. "/admin/store.menu/setApis": "Post",
  39. "/admin/store.menu/delete": "Post",
  40. "/admin/setting.cache/clear": "Post",
  41. "/admin/setting.science/info": "Get",
  42. "/admin/setting.queue/test": "Post",
  43. "/admin/setting.timer/test": "Post",
  44. }
  45. MAP_RE = re.compile(r"(\w+)\s*:\s*'([^']+)'")
  46. CALL_RE = re.compile(r"axios\(\{(.*?)\}\)", re.S)
  47. URL_RE = re.compile(r"url:\s*([A-Za-z0-9_.]+|'[^']*')")
  48. METHOD_RE = re.compile(r"method:\s*'(\w+)'")
  49. def parse_store_front():
  50. table = {}
  51. for f in sorted(STORE_API.rglob("*.js")):
  52. text = f.read_text(encoding="utf-8", errors="ignore")
  53. const = re.search(r"const\s+api\s*=\s*\{(.*?)\n\}", text, re.S)
  54. local = dict(MAP_RE.findall(const.group(1))) if const else {}
  55. for block in CALL_RE.findall(text):
  56. um, mm = URL_RE.search(block), METHOD_RE.search(block)
  57. if not um or not mm:
  58. continue
  59. url = um.group(1).strip("'")
  60. if not url.startswith("/"):
  61. parts = [p for p in url.split(".") if p != "api"]
  62. if parts[-1] not in local:
  63. print(f"!! 跳过无法解析的 url: {url} ({f.name})")
  64. continue
  65. url = local[parts[-1]]
  66. table["/store" + url] = mm.group(1).capitalize()
  67. return table
  68. ANN_RE = re.compile(r"@RequestMapping\((.*?)\)", re.S)
  69. VALUE_RE = re.compile(r'value\s*=\s*(\{[^}]*\}|"[^"]*")')
  70. METHOD_ATTR_RE = re.compile(r'method\s*=\s*(RequestMethod\.\w+|\{[^}]*\})')
  71. PRODUCES_RE = re.compile(r'produces\s*=\s*("[^"]*"|[A-Za-z0-9_.]+)')
  72. def norm_path(p):
  73. if "_" in p:
  74. head, *rest = p.split("_")
  75. return head + "".join(w[:1].upper() + w[1:] for w in rest)
  76. return p
  77. def migrate(module: str, methods: dict):
  78. ctrl = JAVA / module / "controller"
  79. total = 0
  80. for f in sorted(ctrl.glob("*.java")):
  81. src = f.read_text(encoding="utf-8")
  82. cls = re.search(r'^@RequestMapping\("([^"]+)"\)', src, re.M)
  83. prefix = cls.group(1) if cls else ""
  84. missed = []
  85. def repl(m):
  86. nonlocal total
  87. body = m.group(1)
  88. vm = VALUE_RE.search(body)
  89. if not vm:
  90. return m.group(0)
  91. value_expr = vm.group(1)
  92. paths = re.findall(r'"([^"]*)"', value_expr)
  93. full = prefix + paths[0]
  94. key = norm_path(full)
  95. if key not in methods:
  96. missed.append(full)
  97. return m.group(0)
  98. ann = methods[key]
  99. parts = [f"value = {value_expr}"]
  100. pm = PRODUCES_RE.search(body)
  101. if pm:
  102. parts.append(f"produces = {pm.group(1)}")
  103. total += 1
  104. return f"@{ann}Mapping({', '.join(parts)})"
  105. new_src = ANN_RE.sub(repl, src)
  106. if missed:
  107. print(f" !! {module}/{f.name} 前端未找到: {missed}")
  108. if new_src == src:
  109. continue
  110. if "RequestMethod." not in new_src:
  111. new_src = new_src.replace(
  112. "import org.springframework.web.bind.annotation.RequestMethod;\n", "")
  113. for name in ("GetMapping", "PostMapping"):
  114. imp = f"import org.springframework.web.bind.annotation.{name};"
  115. if f"@{name}(" in new_src and imp not in new_src:
  116. new_src = new_src.replace(
  117. "import org.springframework.web.bind.annotation.RequestMapping;",
  118. imp + "\nimport org.springframework.web.bind.annotation.RequestMapping;", 1)
  119. # 组内排序,tools.jackson / com.baomidou 保持原有靠前位置
  120. lines = new_src.split("\n")
  121. out, i = [], 0
  122. while i < len(lines):
  123. if lines[i].startswith("import "):
  124. j = i
  125. while j < len(lines) and lines[j].startswith("import "):
  126. j += 1
  127. grp = lines[i:j]
  128. head = [x for x in grp if x.startswith(("import com.baomidou.", "import tools.jackson."))]
  129. rest = sorted(x for x in grp if x not in head)
  130. out.extend(sorted(head) + rest)
  131. i = j
  132. else:
  133. out.append(lines[i])
  134. i += 1
  135. new_src = "\n".join(out)
  136. if APPLY:
  137. f.write_text(new_src, encoding="utf-8")
  138. return total
  139. store_methods = parse_store_front()
  140. # 前端不调用的 PHP 生命周期钩子(无副作用、无请求体):按浏览器/探活方式用 GET
  141. store_methods["/store/controller/initialize"] = "Get"
  142. store_methods["/store/statistics.data/initialize"] = "Get"
  143. print("store 前端接口数:", len(store_methods))
  144. n1 = migrate("store", store_methods)
  145. n2 = migrate("admin", ADMIN_METHODS)
  146. print(f"{'已改写' if APPLY else '待改写'} store={n1} admin={n2}")