WebUIController.java 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492
  1. package org.ssssssss.magicapi.config;
  2. import com.fasterxml.jackson.databind.ObjectMapper;
  3. import org.apache.commons.io.IOUtils;
  4. import org.apache.commons.lang3.StringUtils;
  5. import org.slf4j.Logger;
  6. import org.slf4j.LoggerFactory;
  7. import org.slf4j.MDC;
  8. import org.springframework.core.io.InputStreamSource;
  9. import org.springframework.http.ResponseEntity;
  10. import org.springframework.web.bind.annotation.RequestBody;
  11. import org.springframework.web.bind.annotation.RequestMapping;
  12. import org.springframework.web.bind.annotation.ResponseBody;
  13. import org.springframework.web.context.request.RequestContextHolder;
  14. import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
  15. import org.ssssssss.magicapi.functions.DatabaseQuery;
  16. import org.ssssssss.magicapi.logging.MagicLoggerContext;
  17. import org.ssssssss.magicapi.model.JsonBean;
  18. import org.ssssssss.magicapi.model.JsonBodyBean;
  19. import org.ssssssss.magicapi.provider.ApiServiceProvider;
  20. import org.ssssssss.magicapi.provider.MagicAPIService;
  21. import org.ssssssss.magicapi.provider.ResultProvider;
  22. import org.ssssssss.magicapi.script.ScriptManager;
  23. import org.ssssssss.script.*;
  24. import org.ssssssss.script.exception.MagicScriptAssertException;
  25. import org.ssssssss.script.exception.MagicScriptException;
  26. import org.ssssssss.script.functions.ObjectConvertExtension;
  27. import org.ssssssss.script.parsing.Span;
  28. import javax.script.Bindings;
  29. import javax.script.ScriptEngine;
  30. import javax.servlet.http.HttpServletRequest;
  31. import javax.servlet.http.HttpServletResponse;
  32. import java.io.IOException;
  33. import java.io.InputStream;
  34. import java.util.*;
  35. /**
  36. * 页面UI对应的Controller
  37. */
  38. public class WebUIController {
  39. private static Logger logger = LoggerFactory.getLogger(WebUIController.class);
  40. /**
  41. * debug 超时时间
  42. */
  43. private int debugTimeout;
  44. /**
  45. * 接口映射
  46. */
  47. private MappingHandlerMapping mappingHandlerMapping;
  48. /**
  49. * 接口查询service
  50. */
  51. private ApiServiceProvider magicApiService;
  52. /**
  53. * 自定义结果
  54. */
  55. private ResultProvider resultProvider;
  56. /**
  57. * 拦截器
  58. */
  59. private List<RequestInterceptor> requestInterceptors = new ArrayList<>();
  60. public WebUIController() {
  61. // 给前端添加代码提示
  62. MagicScriptEngine.addScriptClass(DatabaseQuery.class);
  63. MagicScriptEngine.addScriptClass(MagicAPIService.class);
  64. }
  65. public void addRequestInterceptor(RequestInterceptor requestInterceptor) {
  66. this.requestInterceptors.add(requestInterceptor);
  67. }
  68. public void setResultProvider(ResultProvider resultProvider) {
  69. this.resultProvider = resultProvider;
  70. }
  71. public void setDebugTimeout(int debugTimeout) {
  72. this.debugTimeout = debugTimeout;
  73. }
  74. public void setMappingHandlerMapping(MappingHandlerMapping mappingHandlerMapping) {
  75. this.mappingHandlerMapping = mappingHandlerMapping;
  76. }
  77. public void setMagicApiService(ApiServiceProvider magicApiService) {
  78. this.magicApiService = magicApiService;
  79. }
  80. /**
  81. * 删除接口
  82. *
  83. * @param request
  84. * @param id 接口ID
  85. */
  86. @RequestMapping("/delete")
  87. @ResponseBody
  88. public JsonBean<Boolean> delete(HttpServletRequest request, String id) {
  89. if (!allowVisit(request, RequestInterceptor.Authorization.DELETE)) {
  90. return new JsonBean<>(-10, "无权限执行删除方法");
  91. }
  92. try {
  93. boolean success = this.magicApiService.delete(id);
  94. if (success) { //删除成功时在取消注册
  95. mappingHandlerMapping.unregisterMapping(id, true);
  96. }
  97. return new JsonBean<>(success);
  98. } catch (Exception e) {
  99. logger.error("删除接口出错", e);
  100. return new JsonBean<>(-1, e.getMessage());
  101. }
  102. }
  103. /**
  104. * 删除接口分组
  105. *
  106. * @param apiIds 接口ID列表,逗号分隔
  107. * @param groupName 分组名称
  108. */
  109. @RequestMapping("/group/delete")
  110. @ResponseBody
  111. public JsonBean<Boolean> deleteGroup(HttpServletRequest request, String apiIds, String groupName) {
  112. if (!allowVisit(request, RequestInterceptor.Authorization.DELETE)) {
  113. return new JsonBean<>(-10, "无权限执行删除方法");
  114. }
  115. try {
  116. boolean success = this.magicApiService.deleteGroup(groupName);
  117. if (success) { //删除成功时取消注册
  118. if (StringUtils.isNotBlank(apiIds)) {
  119. String[] ids = apiIds.split(",");
  120. if (ids != null && ids.length > 0) {
  121. for (String id : ids) {
  122. mappingHandlerMapping.unregisterMapping(id, true);
  123. }
  124. }
  125. }
  126. }
  127. return new JsonBean<>(success);
  128. } catch (Exception e) {
  129. logger.error("删除接口出错", e);
  130. return new JsonBean<>(-1, e.getMessage());
  131. }
  132. }
  133. /**
  134. * 修改分组
  135. *
  136. * @param groupName 分组名称
  137. * @param oldGroupName 原分组名称
  138. * @param prefix 分组前缀
  139. */
  140. @RequestMapping("/group/update")
  141. @ResponseBody
  142. public JsonBean<Boolean> groupUpdate(String groupName, String oldGroupName, String prefix, HttpServletRequest request) {
  143. if (!allowVisit(request, RequestInterceptor.Authorization.SAVE)) {
  144. return new JsonBean<>(-10, "无权限执行删除方法");
  145. }
  146. try {
  147. boolean success = magicApiService.updateGroup(oldGroupName, groupName, prefix);
  148. if (success) {
  149. mappingHandlerMapping.updateGroupPrefix(oldGroupName, groupName, prefix);
  150. }
  151. return new JsonBean<>(success);
  152. } catch (Exception e) {
  153. logger.error("修改分组出错", e);
  154. return new JsonBean<>(-1, e.getMessage());
  155. }
  156. }
  157. /**
  158. * 查询所有接口
  159. */
  160. @RequestMapping("/list")
  161. @ResponseBody
  162. public JsonBean<List<ApiInfo>> list() {
  163. try {
  164. return new JsonBean<>(magicApiService.list());
  165. } catch (Exception e) {
  166. logger.error("查询接口列表失败", e);
  167. return new JsonBean<>(-1, e.getMessage());
  168. }
  169. }
  170. /**
  171. * debug 恢复断点
  172. *
  173. * @param id
  174. * @param breakpoints 断点
  175. * @param step 是否是单步
  176. */
  177. @RequestMapping("/continue")
  178. @ResponseBody
  179. public Object debugContinue(String id, String breakpoints, String step, HttpServletResponse response) throws IOException {
  180. MagicScriptDebugContext context = MagicScriptDebugContext.getDebugContext(id);
  181. if (context == null) {
  182. return new JsonBean<>(0, "debug session not found!", resultProvider.buildResult(0, "debug session not found!"));
  183. }
  184. if (breakpoints != null) {
  185. List<Integer> lines = new ArrayList<>();
  186. for (String line : breakpoints.split(",")) {
  187. int value = ObjectConvertExtension.asInt(line, -1);
  188. if (value != -1) {
  189. lines.add(value);
  190. }
  191. }
  192. context.setBreakpoints(lines); //重新调整断点
  193. }
  194. context.setStepInto("1".equals(step));
  195. try {
  196. context.singal(); //等待语句执行到断点或执行完毕
  197. } catch (InterruptedException e) {
  198. e.printStackTrace();
  199. }
  200. if (context.isRunning()) { //判断是否执行完毕
  201. return new JsonBodyBean<>(1000, context.getId(), resultProvider.buildResult(1000, context.getId()), context.getDebugInfo());
  202. } else if (context.isException()) {
  203. return resolveThrowable((Throwable) context.getReturnValue());
  204. }
  205. return convertResult(context.getReturnValue(), response);
  206. }
  207. /**
  208. * 获取所有class
  209. */
  210. @RequestMapping("/classes")
  211. @ResponseBody
  212. public JsonBean<Map<String, Map<String, ScriptClass>>> classes() {
  213. Map<String, ScriptClass> classMap = MagicScriptEngine.getScriptClassMap();
  214. classMap.putAll(MagicModuleLoader.getModules());
  215. Map<String, Map<String, ScriptClass>> values = new HashMap<>();
  216. values.put("classes", classMap);
  217. values.put("extensions", MagicScriptEngine.getExtensionScriptClass());
  218. return new JsonBean<>(values);
  219. }
  220. /**
  221. * 创建控制台输出
  222. */
  223. @RequestMapping("/console")
  224. public SseEmitter console() throws IOException {
  225. String sessionId = UUID.randomUUID().toString().replace("-", "");
  226. SseEmitter emitter = MagicLoggerContext.createEmitter(sessionId);
  227. emitter.send(SseEmitter.event().data(sessionId).name("create"));
  228. return emitter;
  229. }
  230. /**
  231. * 获取单个class
  232. *
  233. * @param className 类名
  234. */
  235. @RequestMapping("/class")
  236. @ResponseBody
  237. public JsonBean<List<ScriptClass>> clazz(String className) {
  238. return new JsonBean<>(MagicScriptEngine.getScriptClass(className));
  239. }
  240. /**
  241. * 测试运行
  242. *
  243. * @param request 请求参数
  244. */
  245. @RequestMapping("/test")
  246. @ResponseBody
  247. public Object test(HttpServletRequest servletRequest, @RequestBody(required = false) Map<String, Object> request, HttpServletResponse response) {
  248. if (!allowVisit(servletRequest, RequestInterceptor.Authorization.RUN)) {
  249. return new JsonBean<>(-10, "无权限执行测试方法");
  250. }
  251. Object script = request.get("script");
  252. if (script != null) {
  253. request.remove("script");
  254. Object breakpoints = request.get("breakpoints");
  255. request.remove("breakpoints");
  256. Object sessionId = request.remove("sessionId");
  257. MagicScriptDebugContext context = new MagicScriptDebugContext();
  258. try {
  259. context.putMapIntoContext((Map<String, Object>) request.get("request"));
  260. context.putMapIntoContext((Map<String, Object>) request.get("path"));
  261. context.set("cookie", request.get("cookie"));
  262. context.set("session", request.get("session"));
  263. context.set("header", request.get("header"));
  264. context.set("body", request.get("body"));
  265. } catch (Exception e) {
  266. return new JsonBean<>(0, "请求参数填写错误", resultProvider.buildResult(0, "请求参数填写错误"));
  267. }
  268. try {
  269. context.setBreakpoints((List<Integer>) breakpoints); //设置断点
  270. context.setTimeout(this.debugTimeout); //设置断点超时时间
  271. RequestContextHolder.setRequestAttributes(RequestContextHolder.getRequestAttributes(), true);
  272. if (sessionId != null) {
  273. context.setId(sessionId.toString());
  274. context.onComplete(() -> {
  275. logger.info("Close Console Session : {}", sessionId);
  276. MagicLoggerContext.remove(sessionId.toString());
  277. });
  278. context.onStart(() -> {
  279. MDC.put(MagicLoggerContext.MAGIC_CONSOLE_SESSION, sessionId.toString());
  280. logger.info("Create Console Session : {}", sessionId);
  281. });
  282. }
  283. ScriptEngine scriptEngine = ScriptManager.getEngine("MagicScript");
  284. Bindings bindings = scriptEngine.createBindings();
  285. bindings.put(MagicScript.CONTEXT_ROOT, context);
  286. Object result = scriptEngine.eval(script.toString(), bindings);
  287. if (context.isRunning()) { //判断是否执行完毕
  288. return new JsonBodyBean<>(1000, context.getId(), resultProvider.buildResult(1000, context.getId(), result), result);
  289. } else if (context.isException()) { //判断是否出现异常
  290. return resolveThrowable((Throwable) context.getReturnValue());
  291. }
  292. return convertResult(result, response);
  293. } catch (Exception e) {
  294. return resolveThrowable(e);
  295. }
  296. }
  297. return new JsonBean<>(resultProvider.buildResult(0, "脚本不能为空"));
  298. }
  299. /**
  300. * 转换请求结果
  301. */
  302. private Object convertResult(Object result, HttpServletResponse response) throws IOException {
  303. if (result instanceof ResponseEntity) {
  304. ResponseEntity entity = (ResponseEntity) result;
  305. for (Map.Entry<String, List<String>> entry : entity.getHeaders().entrySet()) {
  306. String key = entry.getKey();
  307. for (String value : entry.getValue()) {
  308. response.addHeader("MA-" + key, value);
  309. }
  310. }
  311. if (entity.getHeaders().isEmpty()) {
  312. return ResponseEntity.ok(new JsonBean<>(entity.getBody()));
  313. }
  314. return ResponseEntity.ok(new JsonBean<>(convertToBase64(entity.getBody())));
  315. }
  316. return new JsonBean<>(resultProvider.buildResult(result));
  317. }
  318. /**
  319. * 将结果转为base64
  320. */
  321. private String convertToBase64(Object value) throws IOException {
  322. if (value instanceof String || value instanceof Number) {
  323. return convertToBase64(value.toString().getBytes());
  324. } else if (value instanceof byte[]) {
  325. return Base64.getEncoder().encodeToString((byte[]) value);
  326. } else if (value instanceof InputStream) {
  327. return convertToBase64(IOUtils.toByteArray((InputStream) value));
  328. } else if (value instanceof InputStreamSource) {
  329. InputStreamSource iss = (InputStreamSource) value;
  330. return convertToBase64(iss.getInputStream());
  331. } else {
  332. return convertToBase64(new ObjectMapper().writeValueAsString(value));
  333. }
  334. }
  335. /**
  336. * 解决异常
  337. */
  338. private JsonBean<Object> resolveThrowable(Throwable root) {
  339. MagicScriptException se = null;
  340. Throwable parent = root;
  341. do {
  342. if (parent instanceof MagicScriptAssertException) {
  343. MagicScriptAssertException sae = (MagicScriptAssertException) parent;
  344. return new JsonBean<>(resultProvider.buildResult(sae.getCode(), sae.getMessage()));
  345. }
  346. if (parent instanceof MagicScriptException) {
  347. se = (MagicScriptException) parent;
  348. }
  349. } while ((parent = parent.getCause()) != null);
  350. logger.error("测试脚本出错", root);
  351. if (se != null) {
  352. Span.Line line = se.getLine();
  353. return new JsonBodyBean<>(-1000, se.getSimpleMessage(), resultProvider.buildResult(-1000, se.getSimpleMessage()), line == null ? null : Arrays.asList(line.getLineNumber(), line.getEndLineNumber(), line.getStartCol(), line.getEndCol()));
  354. }
  355. return new JsonBean<>(-1, root.getMessage(), resultProvider.buildResult(-1, root.getMessage()));
  356. }
  357. /**
  358. * 查询接口详情
  359. *
  360. * @param id 接口ID
  361. */
  362. @RequestMapping("/get")
  363. @ResponseBody
  364. public JsonBean<ApiInfo> get(HttpServletRequest request, String id) {
  365. if (!allowVisit(request, RequestInterceptor.Authorization.DETAIL)) {
  366. return new JsonBean<>(-10, "无权限执行查看详情方法");
  367. }
  368. try {
  369. return new JsonBean<>(this.magicApiService.get(id));
  370. } catch (Exception e) {
  371. logger.error("查询接口出错");
  372. return new JsonBean<>(-1, e.getMessage());
  373. }
  374. }
  375. /**
  376. * 查询历史记录
  377. * @param id 接口ID
  378. */
  379. @RequestMapping("/backups")
  380. @ResponseBody
  381. public JsonBean<List<Long>> backups(String id) {
  382. return new JsonBean<>(magicApiService.backupList(id));
  383. }
  384. /**
  385. * 获取历史记录
  386. * @param id 接口ID
  387. * @param timestamp 时间点
  388. */
  389. @RequestMapping("/backup/get")
  390. @ResponseBody
  391. public JsonBean<ApiInfo> backups(String id, Long timestamp) {
  392. return new JsonBean<>(magicApiService.backupInfo(id, timestamp));
  393. }
  394. /**
  395. * 保存接口
  396. *
  397. * @param info 接口信息
  398. */
  399. @RequestMapping("/save")
  400. @ResponseBody
  401. public JsonBean<String> save(HttpServletRequest request, ApiInfo info) {
  402. if (!allowVisit(request, RequestInterceptor.Authorization.SAVE)) {
  403. return new JsonBean<>(-10, "无权限执行保存方法");
  404. }
  405. try {
  406. if (StringUtils.isBlank(info.getMethod())) {
  407. return new JsonBean<>(0, "请求方法不能为空");
  408. }
  409. if (info.getGroupName() != null && (info.getGroupName().contains("'") || info.getGroupName().contains("\""))) {
  410. return new JsonBean<>(0, "分组名不能包含特殊字符' \"");
  411. }
  412. if (info.getGroupPrefix() != null && (info.getGroupPrefix().contains("'") || info.getGroupPrefix().contains("\""))) {
  413. return new JsonBean<>(0, "分组前缀不能包含特殊字符' \"");
  414. }
  415. if (StringUtils.isBlank(info.getPath())) {
  416. return new JsonBean<>(0, "请求路径不能为空");
  417. }
  418. if (StringUtils.isBlank(info.getName())) {
  419. return new JsonBean<>(0, "接口名称不能为空");
  420. }
  421. if (StringUtils.isBlank(info.getScript())) {
  422. return new JsonBean<>(0, "脚本内容不能为空");
  423. }
  424. if(mappingHandlerMapping.hasRegisterMapping(info)){
  425. return new JsonBean<>(0, "该路径已被映射,请换一个请求方法或路径");
  426. }
  427. if (StringUtils.isBlank(info.getId())) {
  428. // 先判断接口是否存在
  429. if (magicApiService.exists(info.getGroupPrefix(), info.getMethod(), info.getPath())) {
  430. return new JsonBean<>(0, String.format("接口%s:%s已存在", info.getMethod(), info.getPath()));
  431. }
  432. magicApiService.insert(info);
  433. } else {
  434. // 先判断接口是否存在
  435. if (magicApiService.existsWithoutId(info.getGroupPrefix(), info.getMethod(), info.getPath(), info.getId())) {
  436. return new JsonBean<>(0, String.format("接口%s:%s已存在", info.getMethod(), info.getPath()));
  437. }
  438. magicApiService.update(info);
  439. }
  440. magicApiService.backup(info.getId());
  441. // 注册接口
  442. mappingHandlerMapping.registerMapping(info, true);
  443. return new JsonBean<>(info.getId());
  444. } catch (Exception e) {
  445. logger.error("保存接口出错", e);
  446. return new JsonBean<>(-1, e.getMessage());
  447. }
  448. }
  449. /**
  450. * 判断是否有权限访问按钮
  451. */
  452. private boolean allowVisit(HttpServletRequest request, RequestInterceptor.Authorization authorization) {
  453. for (RequestInterceptor requestInterceptor : requestInterceptors) {
  454. if (!requestInterceptor.allowVisit(request, authorization)) {
  455. return false;
  456. }
  457. }
  458. return true;
  459. }
  460. }