MagicWorkbenchController.java 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289
  1. package org.ssssssss.magicapi.controller;
  2. import org.apache.commons.lang3.StringUtils;
  3. import org.slf4j.Logger;
  4. import org.slf4j.LoggerFactory;
  5. import org.springframework.core.io.ClassPathResource;
  6. import org.springframework.core.io.InputStreamResource;
  7. import org.springframework.http.HttpHeaders;
  8. import org.springframework.http.MediaType;
  9. import org.springframework.http.ResponseEntity;
  10. import org.springframework.util.ResourceUtils;
  11. import org.springframework.web.bind.annotation.RequestMapping;
  12. import org.springframework.web.bind.annotation.ResponseBody;
  13. import org.springframework.web.multipart.MultipartFile;
  14. import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
  15. import org.ssssssss.magicapi.adapter.Resource;
  16. import org.ssssssss.magicapi.adapter.resource.ZipResource;
  17. import org.ssssssss.magicapi.config.MagicConfiguration;
  18. import org.ssssssss.magicapi.config.Valid;
  19. import org.ssssssss.magicapi.exception.MagicLoginException;
  20. import org.ssssssss.magicapi.interceptor.Authorization;
  21. import org.ssssssss.magicapi.interceptor.MagicUser;
  22. import org.ssssssss.magicapi.logging.MagicLoggerContext;
  23. import org.ssssssss.magicapi.model.*;
  24. import org.ssssssss.magicapi.modules.ResponseModule;
  25. import org.ssssssss.magicapi.modules.SQLModule;
  26. import org.ssssssss.magicapi.provider.GroupServiceProvider;
  27. import org.ssssssss.magicapi.provider.MagicAPIService;
  28. import org.ssssssss.magicapi.provider.StoreServiceProvider;
  29. import org.ssssssss.magicapi.utils.JsonUtils;
  30. import org.ssssssss.magicapi.utils.PathUtils;
  31. import org.ssssssss.script.MagicResourceLoader;
  32. import org.ssssssss.script.MagicScriptEngine;
  33. import org.ssssssss.script.ScriptClass;
  34. import org.ssssssss.script.parsing.Span;
  35. import javax.servlet.http.HttpServletRequest;
  36. import javax.servlet.http.HttpServletResponse;
  37. import java.io.ByteArrayOutputStream;
  38. import java.io.File;
  39. import java.io.IOException;
  40. import java.nio.file.Files;
  41. import java.nio.file.Paths;
  42. import java.util.*;
  43. import java.util.stream.Collectors;
  44. import java.util.stream.Stream;
  45. public class MagicWorkbenchController extends MagicController implements MagicExceptionHandler {
  46. private static final Logger logger = LoggerFactory.getLogger(MagicWorkbenchController.class);
  47. public MagicWorkbenchController(MagicConfiguration configuration) {
  48. super(configuration);
  49. // 给前端添加代码提示
  50. MagicScriptEngine.addScriptClass(SQLModule.class);
  51. MagicScriptEngine.addScriptClass(MagicAPIService.class);
  52. }
  53. /**
  54. * 获取所有class
  55. */
  56. @RequestMapping("/classes")
  57. @ResponseBody
  58. @Valid(requireLogin = false)
  59. public JsonBean<Map<String, Object>> classes() {
  60. Map<String, ScriptClass> classMap = MagicScriptEngine.getScriptClassMap();
  61. classMap.putAll(MagicResourceLoader.getModules());
  62. Map<String, Object> values = new HashMap<>();
  63. values.put("classes", classMap);
  64. values.put("extensions", MagicScriptEngine.getExtensionScriptClass());
  65. values.put("functions", MagicScriptEngine.getFunctions());
  66. return new JsonBean<>(values);
  67. }
  68. /**
  69. * 获取单个class
  70. *
  71. * @param className 类名
  72. */
  73. @RequestMapping("/class")
  74. @ResponseBody
  75. public JsonBean<List<ScriptClass>> clazz(String className) {
  76. return new JsonBean<>(MagicScriptEngine.getScriptClass(className));
  77. }
  78. /**
  79. * 登录
  80. */
  81. @RequestMapping("/login")
  82. @ResponseBody
  83. @Valid(requireLogin = false)
  84. public JsonBean<Boolean> login(String username, String password, HttpServletRequest request, HttpServletResponse response) throws MagicLoginException {
  85. if (configuration.getAuthorizationInterceptor().requireLogin()) {
  86. if (StringUtils.isBlank(username) && StringUtils.isBlank(password)) {
  87. try {
  88. configuration.getAuthorizationInterceptor().getUserByToken(request.getHeader(Constants.MAGIC_TOKEN_HEADER));
  89. } catch (MagicLoginException ignored) {
  90. return new JsonBean<>(false);
  91. }
  92. } else {
  93. MagicUser user = configuration.getAuthorizationInterceptor().login(username, password);
  94. response.setHeader(Constants.MAGIC_TOKEN_HEADER, user.getToken());
  95. response.setHeader(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, Constants.MAGIC_TOKEN_HEADER);
  96. }
  97. }
  98. return new JsonBean<>(true);
  99. }
  100. /**
  101. * 创建控制台输出
  102. */
  103. @RequestMapping("/console")
  104. @Valid(requireLogin = false)
  105. public SseEmitter console() throws IOException {
  106. String sessionId = UUID.randomUUID().toString().replace("-", "");
  107. SseEmitter emitter = MagicLoggerContext.createEmitter(sessionId);
  108. emitter.send(SseEmitter.event().data(sessionId).name("create"));
  109. return emitter;
  110. }
  111. @RequestMapping("/options")
  112. @ResponseBody
  113. @Valid(requireLogin = false)
  114. public JsonBean<List<List<String>>> options() {
  115. return new JsonBean<>(Stream.of(Options.values()).map(item -> Arrays.asList(item.getValue(), item.getName(), item.getDefaultValue())).collect(Collectors.toList()));
  116. }
  117. @RequestMapping("/search")
  118. @ResponseBody
  119. public JsonBean<List<Map<String, Object>>> search(String keyword, String type) {
  120. if (StringUtils.isBlank(keyword)) {
  121. return new JsonBean<>(Collections.emptyList());
  122. }
  123. List<MagicEntity> entities = new ArrayList<>();
  124. if (!"2".equals(type)) {
  125. entities.addAll(configuration.getMappingHandlerMapping().getApiInfos());
  126. }
  127. if (!"1".equals(type)) {
  128. entities.addAll(configuration.getMagicFunctionManager().getFunctionInfos());
  129. }
  130. return new JsonBean<>(entities.stream().filter(it -> it.getScript().contains(keyword)).map(it -> {
  131. String script = it.getScript();
  132. int index = script.indexOf(keyword);
  133. int endIndex = script.indexOf("\n", index + keyword.length());
  134. Span span = new Span(script, index, endIndex == -1 ? script.length() : endIndex);
  135. return new HashMap<String, Object>() {
  136. {
  137. put("id", it.getId());
  138. put("text", span.getText().trim());
  139. put("line", span.getLine().getLineNumber());
  140. put("type", it instanceof ApiInfo ? 1 : 2);
  141. }
  142. };
  143. }).collect(Collectors.toList()));
  144. }
  145. @RequestMapping(value = "/config-js")
  146. @ResponseBody
  147. @Valid(requireLogin = false)
  148. public ResponseEntity<?> configJs() {
  149. ResponseEntity.BodyBuilder responseBuilder = ResponseEntity.ok().contentType(MediaType.parseMediaType("application/javascript"));
  150. if (configuration.getEditorConfig() != null) {
  151. try {
  152. String path = configuration.getEditorConfig();
  153. if (path.startsWith(ResourceUtils.CLASSPATH_URL_PREFIX)) {
  154. path = path.substring(ResourceUtils.CLASSPATH_URL_PREFIX.length());
  155. return responseBuilder.body(new InputStreamResource(new ClassPathResource(path).getInputStream()));
  156. }
  157. File file = ResourceUtils.getFile(configuration.getEditorConfig());
  158. return responseBuilder.body(Files.readAllBytes(Paths.get(file.toURI())));
  159. } catch (IOException e) {
  160. logger.warn("读取编辑器配置文件{}失败", configuration.getEditorConfig());
  161. }
  162. }
  163. return responseBuilder.body("var MAGIC_EDITOR_CONFIG = {}".getBytes());
  164. }
  165. @RequestMapping("/download")
  166. @Valid(authorization = Authorization.DOWNLOAD)
  167. @ResponseBody
  168. public ResponseEntity<?> download(String groupId) throws IOException {
  169. if (StringUtils.isBlank(groupId)) {
  170. return download(configuration.getWorkspace(), "magic-api-all.zip");
  171. } else {
  172. Resource resource = configuration.getGroupServiceProvider().getGroupResource(groupId);
  173. notNull(resource, GROUP_NOT_FOUND);
  174. return download(resource, "magic-api-group.zip");
  175. }
  176. }
  177. @RequestMapping("/upload")
  178. @Valid(readonly = false, authorization = Authorization.UPLOAD)
  179. @ResponseBody
  180. public JsonBean<Boolean> upload(MultipartFile file, String mode) throws IOException {
  181. notNull(file, FILE_IS_REQUIRED);
  182. ZipResource root = new ZipResource(file.getInputStream());
  183. Set<String> apiPaths = new HashSet<>();
  184. Set<String> functionPaths = new HashSet<>();
  185. Set<Group> groups = new HashSet<>();
  186. Set<ApiInfo> apiInfos = new HashSet<>();
  187. Set<FunctionInfo> functionInfos = new HashSet<>();
  188. // 检查上传资源中是否有冲突
  189. isTrue(readPaths(groups, apiPaths, functionPaths, apiInfos, functionInfos, "/", root), UPLOAD_PATH_CONFLICT);
  190. // 判断是否是强制上传
  191. if (!"force".equals(mode)) {
  192. // 检测与已注册的接口和函数是否有冲突
  193. isTrue(!configuration.getMappingHandlerMapping().hasRegister(apiPaths), UPLOAD_PATH_CONFLICT.format("接口"));
  194. isTrue(!configuration.getMagicFunctionManager().hasRegister(apiPaths), UPLOAD_PATH_CONFLICT.format("函数"));
  195. }
  196. Resource item = root.getResource("group.json");
  197. GroupServiceProvider groupServiceProvider = configuration.getGroupServiceProvider();
  198. if (item.exists()) {
  199. Group group = groupServiceProvider.readGroup(item);
  200. // 检查分组是否存在
  201. isTrue("0".equals(group.getParentId()) || groupServiceProvider.getGroupResource(group.getParentId()).exists(), GROUP_NOT_FOUND);
  202. groups.removeIf(it -> it.getId().equalsIgnoreCase(group.getId()));
  203. }
  204. for (Group group : groups) {
  205. Resource groupResource = groupServiceProvider.getGroupResource(group.getId());
  206. if (groupResource != null && groupResource.exists()) {
  207. groupServiceProvider.update(group);
  208. } else {
  209. groupServiceProvider.insert(group);
  210. }
  211. }
  212. Resource backups = configuration.getWorkspace().getDirectory(Constants.PATH_BACKUPS);
  213. // 保存
  214. write(configuration.getApiServiceProvider(), backups, apiInfos);
  215. write(configuration.getFunctionServiceProvider(), backups, functionInfos);
  216. // 重新注册
  217. configuration.getMappingHandlerMapping().registerAllMapping();
  218. configuration.getMagicFunctionManager().registerAllFunction();
  219. return new JsonBean<>(SUCCESS, true);
  220. }
  221. private <T extends MagicEntity> void write(StoreServiceProvider<T> provider, Resource backups, Set<T> infos) {
  222. for (T info : infos) {
  223. Resource resource = configuration.getGroupServiceProvider().getGroupResource(info.getGroupId());
  224. resource = resource.getResource(info.getName() + ".ms");
  225. byte[] content = provider.serialize(info);
  226. resource.write(content);
  227. Resource directory = backups.getDirectory(info.getId());
  228. if (!directory.exists()) {
  229. directory.mkdir();
  230. }
  231. directory.getResource(System.currentTimeMillis() + ".ms").write(content);
  232. resource.write(content);
  233. }
  234. }
  235. private boolean readPaths(Set<Group> groups, Set<String> apiPaths, Set<String> functionPaths, Set<ApiInfo> apiInfos, Set<FunctionInfo> functionInfos, String parentPath, Resource root) {
  236. Resource resource = root.getResource("group.json");
  237. String path = "";
  238. if (resource.exists()) {
  239. Group group = JsonUtils.readValue(resource.read(), Group.class);
  240. groups.add(group);
  241. path = Objects.toString(group.getPath(), "");
  242. boolean isApi = Constants.GROUP_TYPE_API.equals(group.getType());
  243. for (Resource file : root.files(".ms")) {
  244. boolean conflict;
  245. if (isApi) {
  246. ApiInfo info = configuration.getApiServiceProvider().deserialize(file.read());
  247. apiInfos.add(info);
  248. conflict = !apiPaths.add(Objects.toString(info.getMethod(), "GET") + ":" + PathUtils.replaceSlash(parentPath + "/" + path + "/" + info.getPath()));
  249. } else {
  250. FunctionInfo info = configuration.getFunctionServiceProvider().deserialize(file.read());
  251. functionInfos.add(info);
  252. conflict = !functionPaths.add(PathUtils.replaceSlash(parentPath + "/" + path + "/" + info.getPath()));
  253. }
  254. if (conflict) {
  255. return false;
  256. }
  257. }
  258. }
  259. for (Resource directory : root.dirs()) {
  260. if (!readPaths(groups, apiPaths, functionPaths, apiInfos, functionInfos, PathUtils.replaceSlash(parentPath + "/" + path), directory)) {
  261. return false;
  262. }
  263. }
  264. return true;
  265. }
  266. private ResponseEntity<?> download(Resource resource, String filename) throws IOException {
  267. ByteArrayOutputStream os = new ByteArrayOutputStream();
  268. resource.export(os, Constants.PATH_BACKUPS);
  269. return ResponseModule.download(os.toByteArray(), filename);
  270. }
  271. }