migrate_api_mapping_annotations.py 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. """把 api 模块控制器里的 @RequestMapping(value=..., method={GET,POST}) 改成 @GetMapping / @PostMapping。
  2. HTTP 方法依据 uniapp 前端 api/*.js 的实际调用(request.get / request.post)逐条确定,
  3. admin / store 模块不动。
  4. """
  5. import re
  6. import sys
  7. from pathlib import Path
  8. sys.path.insert(0, str(Path(__file__).parent))
  9. from _root import ROOT # noqa: E402
  10. CTRL_DIR = ROOT / "yoshop-java" / "src/main/java/cn/yoshop/api/controller"
  11. # (完整路径 -> 方法),路径已含控制器类上的前缀
  12. METHODS = {
  13. # 收货地址
  14. "/api/address/list": "Get",
  15. "/api/address/defaultId": "Get",
  16. "/api/address/detail": "Get",
  17. "/api/address/add": "Post",
  18. "/api/address/edit": "Post",
  19. "/api/address/setDefault": "Post",
  20. "/api/address/remove": "Post",
  21. # 图形 / 短信验证码
  22. "/api/captcha/image": "Get",
  23. "/api/captcha/sendSmsCaptcha": "Post",
  24. # 购物车
  25. "/api/cart/list": "Get",
  26. "/api/cart/total": "Get",
  27. "/api/cart/add": "Post",
  28. "/api/cart/update": "Post",
  29. "/api/cart/clear": "Post",
  30. # 收银台
  31. "/api/cashier/orderInfo": "Get",
  32. "/api/cashier/orderPay": "Post",
  33. "/api/cashier/tradeQuery": "Get",
  34. # 结算台
  35. "/api/checkout/order": "Get",
  36. "/api/checkout/submit": "Post",
  37. # 内容
  38. "/api/category/list": "Get",
  39. "/api/article/list": "Get",
  40. "/api/article/detail": "Get",
  41. "/api/article.category/list": "Get",
  42. "/api/help/list": "Get",
  43. "/api/page/detail": "Get",
  44. # 商品
  45. "/api/goods/list": "Get",
  46. "/api/goods/detail": "Get",
  47. "/api/goods/basic": "Get",
  48. "/api/goods/specData": "Get",
  49. "/api/goods/skuInfo": "Get",
  50. "/api/goods.service/list": "Get",
  51. # 站点入口(纯文本兜底)
  52. "/api/index/index": "Get",
  53. # 营销
  54. "/api/comment/list": "Get",
  55. "/api/comment/total": "Get",
  56. "/api/comment/listRows": "Get",
  57. "/api/coupon/list": "Get",
  58. "/api/myCoupon/list": "Get",
  59. "/api/myCoupon/receive": "Post",
  60. "/api/express/list": "Get",
  61. # 订单
  62. "/api/order/list": "Get",
  63. "/api/order/detail": "Get",
  64. "/api/order/express": "Get",
  65. "/api/order/cancel": "Post",
  66. "/api/order/receipt": "Post",
  67. "/api/order/todoCounts": "Get",
  68. "/api/order.comment/list": "Get",
  69. "/api/order.comment/submit": "Post",
  70. # 登录
  71. "/api/passport/login": "Post",
  72. "/api/passport/loginMpWx": "Post",
  73. "/api/passport/loginMpWxMobile": "Post",
  74. "/api/passport/isPersonalMpweixin": "Post",
  75. # 充值
  76. "/api/recharge/center": "Get",
  77. "/api/recharge/submit": "Post",
  78. "/api/recharge/tradeQuery": "Get",
  79. "/api/recharge.order/list": "Get",
  80. # 售后
  81. "/api/refund/list": "Get",
  82. "/api/refund/goods": "Get",
  83. "/api/refund/apply": "Post",
  84. "/api/refund/detail": "Get",
  85. "/api/refund/delivery": "Post",
  86. # 地区
  87. "/api/region/all": "Get",
  88. "/api/region/tree": "Get",
  89. # 商城 / 设置
  90. "/api/store/data": "Get",
  91. "/api/setting/data": "Get",
  92. # 上传
  93. "/api/upload/image": "Post",
  94. # 用户
  95. "/api/user/info": "Get",
  96. "/api/user/assets": "Get",
  97. "/api/user/bindMobile": "Post",
  98. "/api/user/personal": "Post",
  99. "/api/balance.log/list": "Get",
  100. "/api/points.log/list": "Get",
  101. }
  102. ANN_RE = re.compile(r"@RequestMapping\((.*?)\)", re.S)
  103. VALUE_RE = re.compile(r'value\s*=\s*(\{[^}]*\}|"[^"]*")')
  104. PRODUCES_RE = re.compile(r'produces\s*=\s*("[^"]*"|[A-Za-z0-9_.]+)')
  105. def paths_of(value_expr):
  106. return re.findall(r'"([^"]*)"', value_expr)
  107. def norm_path(p):
  108. """把下划线写法归一成驼峰写法,用于查表。"""
  109. if "_" in p:
  110. head, *rest = p.split("_")
  111. # spec_data / sku_info / list_rows / todo_counts / set_default / default_id
  112. return head + "".join(w[:1].upper() + w[1:] for w in rest)
  113. return p
  114. def process(path: Path):
  115. src = path.read_text(encoding="utf-8")
  116. cls = re.search(r'^@RequestMapping\("([^"]+)"\)', src, re.M)
  117. prefix = cls.group(1) if cls else ""
  118. changed = 0
  119. def repl(m):
  120. nonlocal changed
  121. body = m.group(1)
  122. vm = VALUE_RE.search(body)
  123. if not vm:
  124. return m.group(0)
  125. value_expr = vm.group(1)
  126. paths = paths_of(value_expr)
  127. full = prefix + paths[0]
  128. key = norm_path(full)
  129. if key not in METHODS:
  130. raise SystemExit(f"未定义 HTTP 方法: {path.name} -> {full}")
  131. ann = METHODS[key]
  132. pm = PRODUCES_RE.search(body)
  133. parts = [f"value = {value_expr}"]
  134. if pm:
  135. parts.append(f"produces = {pm.group(1)}")
  136. changed += 1
  137. return f"@{ann}Mapping({', '.join(parts)})"
  138. new_src = ANN_RE.sub(repl, src)
  139. if not changed:
  140. return 0
  141. # 清理 / 补齐 import
  142. if "RequestMethod." not in new_src:
  143. new_src = new_src.replace(
  144. "import org.springframework.web.bind.annotation.RequestMethod;\n", "")
  145. for name in ("GetMapping", "PostMapping"):
  146. if f"@{name}(" in new_src and f"import org.springframework.web.bind.annotation.{name};" not in new_src:
  147. new_src = new_src.replace(
  148. "import org.springframework.web.bind.annotation.RequestMapping;",
  149. f"import org.springframework.web.bind.annotation.{name};\n"
  150. "import org.springframework.web.bind.annotation.RequestMapping;", 1)
  151. # import 块按字母排序
  152. lines = new_src.split("\n")
  153. idx = [i for i, ln in enumerate(lines) if ln.startswith("import ")]
  154. if idx:
  155. start, end = idx[0], idx[-1]
  156. block = lines[start:end + 1]
  157. if all(ln.startswith("import ") for ln in block):
  158. lines[start:end + 1] = sorted(set(block))
  159. new_src = "\n".join(lines)
  160. path.write_text(new_src, encoding="utf-8")
  161. return changed
  162. total = 0
  163. for f in sorted(CTRL_DIR.glob("*.java")):
  164. if f.name == "ApiNotifyController.java":
  165. continue
  166. n = process(f)
  167. if n:
  168. print(f"{f.name}: {n}")
  169. total += n
  170. print("合计:", total)