MagicWorkbenchController.java 10 KB

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