Browse Source

改为文件存储。

mxd 4 years ago
parent
commit
eb196c6a4d
20 changed files with 891 additions and 488 deletions
  1. 88 0
      src/main/java/org/ssssssss/magicapi/adapter/Resource.java
  2. 255 0
      src/main/java/org/ssssssss/magicapi/adapter/ResourceAdapter.java
  3. 12 35
      src/main/java/org/ssssssss/magicapi/controller/MagicAPIController.java
  4. 4 2
      src/main/java/org/ssssssss/magicapi/controller/MagicConfigController.java
  5. 9 17
      src/main/java/org/ssssssss/magicapi/controller/MagicFunctionController.java
  6. 23 22
      src/main/java/org/ssssssss/magicapi/controller/MagicGroupController.java
  7. 0 2
      src/main/java/org/ssssssss/magicapi/logging/LogInfo.java
  8. 1 65
      src/main/java/org/ssssssss/magicapi/model/ApiInfo.java
  9. 3 41
      src/main/java/org/ssssssss/magicapi/model/FunctionInfo.java
  10. 72 0
      src/main/java/org/ssssssss/magicapi/model/MagicEntity.java
  11. 1 1
      src/main/java/org/ssssssss/magicapi/modules/SQLModule.java
  12. 19 4
      src/main/java/org/ssssssss/magicapi/provider/ApiServiceProvider.java
  13. 18 3
      src/main/java/org/ssssssss/magicapi/provider/FunctionServiceProvider.java
  14. 4 0
      src/main/java/org/ssssssss/magicapi/provider/GroupServiceProvider.java
  15. 149 37
      src/main/java/org/ssssssss/magicapi/provider/StoreServiceProvider.java
  16. 5 118
      src/main/java/org/ssssssss/magicapi/provider/impl/DefaultApiServiceProvider.java
  17. 5 118
      src/main/java/org/ssssssss/magicapi/provider/impl/DefaultFunctionServiceProvider.java
  18. 59 23
      src/main/java/org/ssssssss/magicapi/provider/impl/DefaultGroupServiceProvider.java
  19. 124 0
      src/main/java/org/ssssssss/magicapi/utils/IoUtils.java
  20. 40 0
      src/main/java/org/ssssssss/magicapi/utils/JsonUtils.java

+ 88 - 0
src/main/java/org/ssssssss/magicapi/adapter/Resource.java

@@ -0,0 +1,88 @@
+package org.ssssssss.magicapi.adapter;
+
+import java.util.List;
+
+public interface Resource {
+
+	/**
+	 * 是否是只读
+	 */
+	default boolean readonly(){
+		return false;
+	}
+
+	/**
+	 * 是否存在
+	 */
+	default boolean exists(){
+		return false;
+	}
+
+	/**
+	 * 是否是目录
+	 */
+	default boolean isDirectory(){
+		return false;
+	}
+
+	/**
+	 * 删除
+	 */
+	default boolean delete(){
+		return false;
+	}
+
+	/**
+	 * 创建目录
+	 */
+	default boolean mkdir(){
+		return false;
+	}
+
+	/**
+	 * 重命名
+	 */
+	default boolean renameTo(Resource resource){
+		return false;
+	}
+
+	/**
+	 * 写入
+	 */
+	default boolean write(String content){
+		return false;
+	}
+	/**
+	 * 写入
+	 */
+	default boolean write(byte[] bytes){
+		return false;
+	}
+
+	/**
+	 * 读取
+	 */
+	byte[] read();
+
+	/**
+	 * 获取子资源
+	 */
+	Resource getResource(String name);
+
+	/**
+	 * 获取资源名
+	 */
+	String name();
+
+	/**
+	 * 获取子资源集合
+	 */
+	List<Resource> resources();
+
+	Resource parent();
+
+	List<Resource> dirs();
+
+	List<Resource> files(String suffix);
+
+}

+ 255 - 0
src/main/java/org/ssssssss/magicapi/adapter/ResourceAdapter.java

@@ -0,0 +1,255 @@
+package org.ssssssss.magicapi.adapter;
+
+import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
+import org.springframework.util.ResourceUtils;
+import org.ssssssss.magicapi.utils.IoUtils;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.net.JarURLConnection;
+import java.net.URL;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import java.util.stream.Collectors;
+import java.util.zip.ZipEntry;
+
+public abstract class ResourceAdapter {
+
+	private static PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
+
+	public static Resource getResource(String location) throws IOException {
+		if (location == null) {
+			return null;
+		}
+		org.springframework.core.io.Resource resource;
+		if (location.startsWith(ResourceUtils.CLASSPATH_URL_PREFIX)) {
+			resource = resolver.getResource(location);
+			if (resource.exists()) {
+				return resolveResource(resource, true);
+			}else {
+				throw new FileNotFoundException(String.format("%s not found", resource.getDescription()));
+			}
+		} else {
+			resource = resolver.getResource(location);
+			if (!resource.exists()) {
+				resource = resolver.getResource(ResourceUtils.FILE_URL_PREFIX + location);
+			}
+		}
+		return resolveResource(resource, false);
+	}
+
+	private static Resource resolveResource(org.springframework.core.io.Resource resource, boolean readonly) throws IOException {
+		URL url = resource.getURL();
+		if (url.getProtocol().equals(ResourceUtils.URL_PROTOCOL_JAR)) {
+			JarURLConnection connection = (JarURLConnection) url.openConnection();
+			String entryName = connection.getEntryName();
+			JarFile jarFile = connection.getJarFile();
+			return new JarResource(jarFile, entryName, jarFile.getEntry(entryName), jarFile.stream()
+					.filter(it -> it.getName().startsWith(entryName))
+					.collect(Collectors.toList()));
+		} else {
+			return new FileResource(resource.getFile(), readonly);
+		}
+	}
+
+
+	private static class FileResource implements Resource {
+
+		private File file;
+
+		private boolean readonly;
+
+		public FileResource(File file, boolean readonly) {
+			this.file = file;
+			this.readonly = readonly;
+		}
+
+		@Override
+		public boolean readonly() {
+			return this.readonly;
+		}
+
+		@Override
+		public boolean exists() {
+			return this.file.exists();
+		}
+
+		@Override
+		public boolean delete() {
+			return IoUtils.delete(this.file);
+		}
+
+		@Override
+		public boolean isDirectory() {
+			return this.file.isDirectory();
+		}
+
+		@Override
+		public boolean mkdir() {
+			return this.file.mkdirs();
+		}
+
+		@Override
+		public byte[] read() {
+			return IoUtils.bytes(this.file);
+		}
+
+		@Override
+		public boolean renameTo(Resource resource) {
+			File target = ((FileResource) resource).file;
+			if (this.file.renameTo(target)) {
+				this.file = target;
+				return true;
+			}
+			return false;
+		}
+
+		@Override
+		public Resource getResource(String name) {
+			return new FileResource(new File(this.file, name), this.readonly);
+		}
+
+		@Override
+		public String name() {
+			return this.file.getName();
+		}
+
+		@Override
+		public List<Resource> resources() {
+			File[] files = this.file.listFiles();
+			return files == null ? Collections.emptyList() : Arrays.stream(files).map(it -> new FileResource(it, this.readonly)).collect(Collectors.toList());
+		}
+
+		@Override
+		public Resource parent() {
+			return new FileResource(this.file.getParentFile(), this.readonly);
+		}
+
+		@Override
+		public List<Resource> dirs() {
+			return IoUtils.dirs(this.file).stream().map(it -> new FileResource(it, this.readonly)).collect(Collectors.toList());
+		}
+
+		@Override
+		public boolean write(byte[] bytes) {
+			return IoUtils.write(this.file, bytes);
+		}
+
+		@Override
+		public boolean write(String content) {
+			return IoUtils.write(this.file, content);
+		}
+
+		@Override
+		public List<Resource> files(String suffix) {
+			return IoUtils.files(this.file, suffix).stream().map(it -> new FileResource(it,this.readonly)).collect(Collectors.toList());
+		}
+
+		@Override
+		public String toString() {
+			return String.format("file resource [%s]", this.file.getAbsolutePath());
+		}
+	}
+
+	private static class JarResource implements Resource {
+
+		private JarFile jarFile;
+
+		private ZipEntry entry;
+
+		private List<JarEntry> entries;
+
+		private String entryName;
+
+		private JarResource parent = this;
+
+		public JarResource(JarFile jarFile, String entryName, ZipEntry entry, List<JarEntry> entries) {
+			this.jarFile = jarFile;
+			this.entryName = entryName;
+			this.entry = entry;
+			this.entries = entries;
+		}
+
+		public JarResource(JarFile jarFile, String entryName, ZipEntry entry, List<JarEntry> entries, JarResource parent) {
+			this(jarFile, entryName, entry, entries);
+			this.parent = parent;
+		}
+
+		@Override
+		public boolean readonly() {
+			return true;
+		}
+
+		@Override
+		public byte[] read() {
+			try {
+				return IoUtils.bytes(this.jarFile.getInputStream(entry));
+			} catch (IOException e) {
+				return new byte[0];
+			}
+		}
+
+		@Override
+		public boolean isDirectory() {
+			return this.entry.isDirectory();
+		}
+
+		@Override
+		public boolean exists() {
+			return this.entry != null;
+		}
+
+		@Override
+		public Resource getResource(String name) {
+			String entryName = this.entryName + "/" + name;
+			String prefix = this.entryName + "/";
+			return new JarResource(this.jarFile, entryName, this.jarFile.getEntry(entryName), entries.stream().filter(it -> it.getName().startsWith(prefix)).collect(Collectors.toList()),this);
+		}
+
+		@Override
+		public String name() {
+			int index = this.entryName.lastIndexOf("/");
+			return index > -1 ? this.entryName.substring(index) : this.entryName;
+		}
+
+		@Override
+		public Resource parent() {
+			return this.parent;
+		}
+
+		@Override
+		public List<Resource> dirs() {
+			List<Resource> resources = resources();
+			resources.stream().filter(Resource::isDirectory).map(Resource::dirs).forEach(resources::addAll);
+			return resources;
+		}
+
+		@Override
+		public List<Resource> files(String suffix) {
+			return this.entries.stream().filter(it -> it.getName().endsWith(suffix))
+					.map(entry -> new JarResource(jarFile, entry.getName(), entry, Collections.emptyList()))
+					.collect(Collectors.toList());
+		}
+
+		@Override
+		public List<Resource> resources() {
+			String prefix = this.entryName + "/";
+			return entries.stream()
+					.filter(it -> it.getName().startsWith(prefix))
+					.map(entry -> new JarResource(jarFile, entry.getName(), entry, entries.stream()
+							.filter(item -> item.getName().startsWith(entry.getName() + "/"))
+							.collect(Collectors.toList()))
+					)
+					.collect(Collectors.toList());
+		}
+
+		@Override
+		public String toString() {
+			return String.format("class path resource [%s]", this.entryName);
+		}
+	}
+}

+ 12 - 35
src/main/java/org/ssssssss/magicapi/controller/MagicAPIController.java

@@ -13,6 +13,7 @@ import org.ssssssss.magicapi.provider.ApiServiceProvider;
 
 import javax.servlet.http.HttpServletRequest;
 import java.util.List;
+import java.util.Optional;
 
 /**
  * 接口相关操作
@@ -84,29 +85,6 @@ public class MagicAPIController extends MagicController {
 		}
 	}
 
-	/**
-	 * 查询历史记录
-	 *
-	 * @param id 接口ID
-	 */
-	@RequestMapping("/backups")
-	@ResponseBody
-	public JsonBean<List<Long>> backups(String id) {
-		return new JsonBean<>(magicApiService.backupList(id));
-	}
-
-	/**
-	 * 获取历史记录
-	 *
-	 * @param id        接口ID
-	 * @param timestamp 时间点
-	 */
-	@RequestMapping("/backup/get")
-	@ResponseBody
-	public JsonBean<ApiInfo> backups(String id, Long timestamp) {
-		return new JsonBean<>(magicApiService.backupInfo(id, timestamp));
-	}
-
 	/**
 	 * 移动接口
 	 */
@@ -120,6 +98,9 @@ public class MagicAPIController extends MagicController {
 			return new JsonBean<>(0, "找不到分组信息");
 		}
 		try {
+			if (!magicApiService.allowMove(id, groupId)) {
+				return new JsonBean<>(0, "移动后名称会重复,请修改名称后在试。");
+			}
 			if (!configuration.getMappingHandlerMapping().move(id, groupId)) {
 				return new JsonBean<>(0, "该路径已被映射,请换一个请求方法或路径");
 			} else {
@@ -163,25 +144,21 @@ public class MagicAPIController extends MagicController {
 				if (magicApiService.exists(info.getGroupId(), info.getMethod(), info.getPath())) {
 					return new JsonBean<>(0, String.format("接口%s:%s已存在", info.getMethod(), info.getPath()));
 				}
-				magicApiService.insert(info);
+				if (!magicApiService.insert(info)) {
+					return new JsonBean<>(0, "保存失败,请检查接口名称是否重复且不能包含特殊字符。");
+				}
 			} else {
 				// 先判断接口是否存在
 				if (magicApiService.existsWithoutId(info.getGroupId(), info.getMethod(), info.getPath(), info.getId())) {
 					return new JsonBean<>(0, String.format("接口%s:%s已存在", info.getMethod(), info.getPath()));
 				}
-				configuration.getMappingHandlerMapping().getApiInfos().stream()
+				Optional<ApiInfo> optional = configuration.getMappingHandlerMapping().getApiInfos().stream()
 						.filter(it -> it.getId().equals(info.getId()))
-						.findFirst()
-						.ifPresent(it -> {
-							// 有变化时才修改
-							if (!it.equals(info)) {
-								magicApiService.update(info);
-								magicApiService.backup(info.getId());
-							}
-						});
+						.findFirst();
+				if (optional.isPresent() && !optional.get().equals(info) && !magicApiService.update(info)) {
+					return new JsonBean<>(0, "保存失败,请检查接口名称是否重复且不能包含特殊字符。");
+				}
 			}
-			// 解除包装
-			magicApiService.unwrap(info);
 			// 注册接口
 			configuration.getMappingHandlerMapping().registerMapping(info, true);
 			return new JsonBean<>(info.getId());

+ 4 - 2
src/main/java/org/ssssssss/magicapi/controller/MagicConfigController.java

@@ -4,6 +4,7 @@ import org.apache.commons.lang3.StringUtils;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.ResponseBody;
 import org.ssssssss.magicapi.config.MagicConfiguration;
+import org.ssssssss.magicapi.config.MagicDynamicDataSource;
 import org.ssssssss.magicapi.model.JsonBean;
 import org.ssssssss.magicapi.modules.SQLModule;
 import org.ssssssss.magicapi.provider.MagicAPIService;
@@ -34,10 +35,11 @@ public class MagicConfigController extends MagicController {
 		Map<String, ScriptClass> classMap = MagicScriptEngine.getScriptClassMap();
 		classMap.putAll(MagicResourceLoader.getModules());
 		ScriptClass db = classMap.get(SQLModule.class.getName());
-		if (db != null) {
+		MagicDynamicDataSource datasource = configuration.getMagicDynamicDataSource();
+		if (db != null && datasource != null) {
 			List<ScriptClass.ScriptAttribute> attributes = new ArrayList<>();
 			// 给与前台动态数据源提示
-			configuration.getMagicDynamicDataSource().datasources().stream().filter(StringUtils::isNotBlank)
+			datasource.datasources().stream().filter(StringUtils::isNotBlank)
 					.forEach(item -> attributes.add(new ScriptClass.ScriptAttribute("db", item)));
 			db.setAttributes(attributes);
 		}

+ 9 - 17
src/main/java/org/ssssssss/magicapi/controller/MagicFunctionController.java

@@ -57,6 +57,9 @@ public class MagicFunctionController extends MagicController {
 			return new JsonBean<>(-10, "无权限执行移动函数");
 		}
 		try {
+			if (!functionService.allowMove(id, groupId)) {
+				return new JsonBean<>(0, "移动后名称会重复,请修改名称后在试。");
+			}
 			if (!configuration.getMagicFunctionManager().move(id, groupId)) {
 				return new JsonBean<>(0, "该路径已被映射,请换一个路径");
 			} else {
@@ -68,18 +71,6 @@ public class MagicFunctionController extends MagicController {
 		}
 	}
 
-	@RequestMapping("/function/backup/get")
-	@ResponseBody
-	public JsonBean<FunctionInfo> backups(String id, Long timestamp) {
-		return new JsonBean<>(functionService.backupInfo(id, timestamp));
-	}
-
-	@RequestMapping("/function/backups")
-	@ResponseBody
-	public JsonBean<List<Long>> backups(String id) {
-		return new JsonBean<>(functionService.backupList(id));
-	}
-
 
 	@RequestMapping("/function/save")
 	@ResponseBody
@@ -104,16 +95,17 @@ public class MagicFunctionController extends MagicController {
 				if (functionService.exists(functionInfo.getPath(), functionInfo.getGroupId())) {
 					return new JsonBean<>(0, String.format("函数%s已存在", functionInfo.getPath()));
 				}
-				functionService.insert(functionInfo);
+				if(!functionService.insert(functionInfo)){
+					return new JsonBean<>(0, "保存失败,请检查函数名称是否重复且不能包含特殊字符。");
+				}
 			} else {
 				if (functionService.existsWithoutId(functionInfo.getPath(), functionInfo.getGroupId(), functionInfo.getId())) {
 					return new JsonBean<>(0, String.format("函数%s已存在", functionInfo.getPath()));
 				}
-				functionService.update(functionInfo);
+				if(!functionService.update(functionInfo)){
+					return new JsonBean<>(0, "保存失败,请检查函数名称是否重复且不能包含特殊字符。");
+				}
 			}
-			functionService.backup(functionInfo.getId());
-			// 解除包装
-			functionService.unwrap(functionInfo);
 			configuration.getMagicFunctionManager().register(functionInfo);
 			return new JsonBean<>(functionInfo.getId());
 		} catch (Exception e) {

+ 23 - 22
src/main/java/org/ssssssss/magicapi/controller/MagicGroupController.java

@@ -54,22 +54,18 @@ public class MagicGroupController extends MagicController {
 				if (success = configuration.getMagicApiService().deleteGroup(groupIds)) {
 					// 取消注册
 					configuration.getMappingHandlerMapping().deleteGroup(groupIds);
-					// 删除分组
-					if (success = this.groupServiceProvider.delete(groupId)) {
-						// 重新加载分组
-						configuration.getMappingHandlerMapping().loadGroup();
-					}
+					groupIds.forEach(configuration.getGroupServiceProvider()::delete);
+					// 重新加载分组
+					configuration.getMappingHandlerMapping().loadGroup();
 				}
 			} else {
 				// 删除函数
 				if (success = configuration.getFunctionServiceProvider().deleteGroup(groupIds)) {
 					// 取消注册
 					configuration.getMagicFunctionManager().deleteGroup(groupIds);
-					// 删除分组
-					if (success = this.groupServiceProvider.delete(groupId)) {
-						// 重新加载分组
-						configuration.getMagicFunctionManager().loadGroup();
-					}
+					groupIds.forEach(configuration.getGroupServiceProvider()::delete);
+					// 重新加载分组
+					configuration.getMagicFunctionManager().loadGroup();
 				}
 			}
 			return new JsonBean<>(success);
@@ -101,17 +97,19 @@ public class MagicGroupController extends MagicController {
 			boolean isApiGroup = "1".equals(group.getType());
 			boolean isFunctionGroup = "2".equals(group.getType());
 			if (isApiGroup && configuration.getMappingHandlerMapping().checkGroup(group)) {
-				boolean success = groupServiceProvider.update(group);
-				if (success) {    // 如果数据库修改成功,则修改接口路径
+				if (groupServiceProvider.update(group)) {    // 如果数据库修改成功,则修改接口路径
 					configuration.getMappingHandlerMapping().updateGroup(group);
+				}else{
+					return new JsonBean<>(0, "修改分组失败,同一组下分组名称不能重复且不能包含特殊字符。");
 				}
-				return new JsonBean<>(success);
+				return new JsonBean<>(true);
 			} else if (isFunctionGroup && configuration.getMagicFunctionManager().checkGroup(group)) {
-				boolean success = groupServiceProvider.update(group);
-				if (success) {    // 如果数据库修改成功,则修改接口路径
+				if (groupServiceProvider.update(group)) {    // 如果数据库修改成功,则修改接口路径
 					configuration.getMagicFunctionManager().updateGroup(group);
+				}else{
+					return new JsonBean<>(0, "修改分组失败,同一组下分组名称不能重复且不能包含特殊字符。");
 				}
-				return new JsonBean<>(success);
+				return new JsonBean<>(true);
 			}
 			return new JsonBean<>(-20, "修改分组后,路径会有冲突,请检查!");
 		} catch (Exception e) {
@@ -153,13 +151,16 @@ public class MagicGroupController extends MagicController {
 			return new JsonBean<>(0, "分组类型不能为空");
 		}
 		try {
-			groupServiceProvider.insert(group);
-			if (Objects.equals(group.getType(), "1")) {
-				configuration.getMappingHandlerMapping().loadGroup();
-			} else {
-				configuration.getMagicFunctionManager().loadGroup();
+			if(groupServiceProvider.insert(group)){
+				if (Objects.equals(group.getType(), "1")) {
+					configuration.getMappingHandlerMapping().loadGroup();
+				} else {
+					configuration.getMagicFunctionManager().loadGroup();
+				}
+				return new JsonBean<>(group.getId());
+			}else {
+				return new JsonBean<>(-1,"保存分组失败,同一组下分组名称不能重复且不能包含特殊字符。");
 			}
-			return new JsonBean<>(group.getId());
 		} catch (Exception e) {
 			logger.error("保存分组出错", e);
 			return new JsonBean<>(-1, e.getMessage());

+ 0 - 2
src/main/java/org/ssssssss/magicapi/logging/LogInfo.java

@@ -1,10 +1,8 @@
 package org.ssssssss.magicapi.logging;
 
 import java.io.IOException;
-import java.io.PrintStream;
 import java.io.PrintWriter;
 import java.io.StringWriter;
-import java.util.List;
 
 /**
  * 打印的日志信息

+ 1 - 65
src/main/java/org/ssssssss/magicapi/model/ApiInfo.java

@@ -10,12 +10,7 @@ import java.util.Objects;
 /**
  * 接口信息
  */
-public class ApiInfo {
-
-	/**
-	 * 接口ID
-	 */
-	private String id;
+public class ApiInfo extends MagicEntity{
 
 	/**
 	 * 请求方法
@@ -27,21 +22,6 @@ public class ApiInfo {
 	 */
 	private String path;
 
-	/**
-	 * 脚本内容
-	 */
-	private String script;
-
-	/**
-	 * 接口名称
-	 */
-	private String name;
-
-	/**
-	 * 分组ID
-	 */
-	private String groupId;
-
 	/**
 	 * 设置的请求参数
 	 */
@@ -72,23 +52,11 @@ public class ApiInfo {
 	 */
 	private String description;
 
-	/**
-	 * 最后更新时间
-	 */
-	private Long updateTime;
-
 	/**
 	 * 接口选项json
 	 */
 	private JsonNode jsonNode;
 
-	public String getId() {
-		return id;
-	}
-
-	public void setId(String id) {
-		this.id = id;
-	}
 
 	public String getMethod() {
 		return method;
@@ -106,22 +74,6 @@ public class ApiInfo {
 		this.path = path;
 	}
 
-	public String getScript() {
-		return script;
-	}
-
-	public void setScript(String script) {
-		this.script = script;
-	}
-
-	public String getName() {
-		return name;
-	}
-
-	public void setName(String name) {
-		this.name = name;
-	}
-
 	public String getParameter() {
 		return parameter;
 	}
@@ -155,14 +107,6 @@ public class ApiInfo {
 		this.responseBody = responseBody;
 	}
 
-	public String getGroupId() {
-		return groupId;
-	}
-
-	public void setGroupId(String groupId) {
-		this.groupId = groupId;
-	}
-
 	public Map<String, Object> getOptionMap() {
 		Map<String, Object> map = new HashMap<>();
 		if (this.jsonNode == null) {
@@ -218,14 +162,6 @@ public class ApiInfo {
 		return null;
 	}
 
-	public Long getUpdateTime() {
-		return updateTime;
-	}
-
-	public void setUpdateTime(Long updateTime) {
-		this.updateTime = updateTime;
-	}
-
 	@Override
 	public boolean equals(Object o) {
 		if (this == o) return true;

+ 3 - 41
src/main/java/org/ssssssss/magicapi/model/FunctionInfo.java

@@ -7,18 +7,10 @@ import java.util.Collections;
 import java.util.List;
 import java.util.stream.Collectors;
 
-public class FunctionInfo {
-
-	private String id;
-
-	private String name;
+public class FunctionInfo extends MagicEntity {
 
 	private String path;
 
-	private String groupId;
-
-	private String script;
-
 	private String description;
 
 	private String parameter;
@@ -29,38 +21,6 @@ public class FunctionInfo {
 
 	private List<String> parameterNames = Collections.emptyList();
 
-	public String getId() {
-		return id;
-	}
-
-	public void setId(String id) {
-		this.id = id;
-	}
-
-	public String getName() {
-		return name;
-	}
-
-	public void setName(String name) {
-		this.name = name;
-	}
-
-	public String getGroupId() {
-		return groupId;
-	}
-
-	public void setGroupId(String groupId) {
-		this.groupId = groupId;
-	}
-
-	public String getScript() {
-		return script;
-	}
-
-	public void setScript(String script) {
-		this.script = script;
-	}
-
 	public String getDescription() {
 		return description;
 	}
@@ -110,4 +70,6 @@ public class FunctionInfo {
 	public void setReturnType(String returnType) {
 		this.returnType = returnType;
 	}
+
+
 }

+ 72 - 0
src/main/java/org/ssssssss/magicapi/model/MagicEntity.java

@@ -0,0 +1,72 @@
+package org.ssssssss.magicapi.model;
+
+public class MagicEntity implements Cloneable{
+
+	protected String id;
+
+	protected String script;
+
+	protected String groupId;
+
+	protected String name;
+
+	protected Long createTime;
+
+	protected Long updateTime;
+
+	public String getId() {
+		return id;
+	}
+
+	public void setId(String id) {
+		this.id = id;
+	}
+
+	public String getScript() {
+		return script;
+	}
+
+	public void setScript(String script) {
+		this.script = script;
+	}
+
+	public Long getCreateTime() {
+		return createTime;
+	}
+
+	public void setCreateTime(Long createTime) {
+		this.createTime = createTime;
+	}
+
+	public Long getUpdateTime() {
+		return updateTime;
+	}
+
+	public void setUpdateTime(Long updateTime) {
+		this.updateTime = updateTime;
+	}
+
+	public String getGroupId() {
+		return groupId;
+	}
+
+	public void setGroupId(String groupId) {
+		this.groupId = groupId;
+	}
+
+	public String getName() {
+		return name;
+	}
+
+	public void setName(String name) {
+		this.name = name;
+	}
+
+	public MagicEntity clone() {
+		try {
+			return (MagicEntity) super.clone();
+		} catch (CloneNotSupportedException e) {
+			return this;
+		}
+	}
+}

+ 1 - 1
src/main/java/org/ssssssss/magicapi/modules/SQLModule.java

@@ -7,9 +7,9 @@ import org.springframework.jdbc.support.KeyHolder;
 import org.ssssssss.magicapi.adapter.ColumnMapperAdapter;
 import org.ssssssss.magicapi.adapter.DialectAdapter;
 import org.ssssssss.magicapi.cache.SqlCache;
-import org.ssssssss.magicapi.config.MagicModule;
 import org.ssssssss.magicapi.config.MagicDynamicDataSource;
 import org.ssssssss.magicapi.config.MagicDynamicDataSource.DataSourceNode;
+import org.ssssssss.magicapi.config.MagicModule;
 import org.ssssssss.magicapi.dialect.Dialect;
 import org.ssssssss.magicapi.interceptor.SQLInterceptor;
 import org.ssssssss.magicapi.model.Page;

+ 19 - 4
src/main/java/org/ssssssss/magicapi/provider/ApiServiceProvider.java

@@ -1,12 +1,18 @@
 package org.ssssssss.magicapi.provider;
 
+
+import org.ssssssss.magicapi.adapter.Resource;
 import org.ssssssss.magicapi.model.ApiInfo;
 
 /**
  * API存储接口
  */
-public interface ApiServiceProvider extends StoreServiceProvider<ApiInfo> {
+public abstract class ApiServiceProvider extends StoreServiceProvider<ApiInfo> {
+
 
+	public ApiServiceProvider(Resource workspace, GroupServiceProvider groupServiceProvider) {
+		super(ApiInfo.class, workspace, groupServiceProvider);
+	}
 
 	/**
 	 * 判断接口是否存在
@@ -15,7 +21,10 @@ public interface ApiServiceProvider extends StoreServiceProvider<ApiInfo> {
 	 * @param method  请求方法
 	 * @param path    请求路径
 	 */
-	boolean exists(String groupId, String method, String path);
+	public boolean exists(String groupId, String method, String path){
+		return infos.values().stream()
+				.anyMatch(it -> groupId.equals(it.getGroupId()) && method.equals(it.getMethod()) && path.equals(it.getPath()));
+	}
 
 	/**
 	 * 判断接口是否存在
@@ -25,7 +34,13 @@ public interface ApiServiceProvider extends StoreServiceProvider<ApiInfo> {
 	 * @param path    请求路径
 	 * @param id      排除接口
 	 */
-	boolean existsWithoutId(String groupId, String method, String path, String id);
-
+	public boolean existsWithoutId(String groupId, String method, String path, String id){
+		return infos.values().stream()
+				.anyMatch(it -> !id.equals(it.getId()) && groupId.equals(it.getGroupId()) && method.equals(it.getMethod()) && path.equals(it.getPath()));
+	}
 
+	@Override
+	public byte[] serialize(ApiInfo info) {
+		return super.serialize(info);
+	}
 }

+ 18 - 3
src/main/java/org/ssssssss/magicapi/provider/FunctionServiceProvider.java

@@ -1,11 +1,26 @@
 package org.ssssssss.magicapi.provider;
 
+import org.ssssssss.magicapi.adapter.Resource;
 import org.ssssssss.magicapi.model.FunctionInfo;
 
-public interface FunctionServiceProvider extends StoreServiceProvider<FunctionInfo> {
+public abstract class FunctionServiceProvider extends StoreServiceProvider<FunctionInfo> {
 
-	boolean exists(String path, String groupId);
+	public FunctionServiceProvider(Resource workspace, GroupServiceProvider groupServiceProvider) {
+		super(FunctionInfo.class, workspace, groupServiceProvider);
+	}
 
-	boolean existsWithoutId(String path, String groupId, String id);
+	public boolean exists(String path, String groupId){
+		return infos.values().stream()
+				.anyMatch(it -> groupId.equals(it.getGroupId()) && path.equals(it.getPath()));
+	}
 
+	public boolean existsWithoutId(String path, String groupId, String id){
+		return infos.values().stream()
+				.anyMatch(it -> !id.equals(it.getId()) && groupId.equals(it.getGroupId()) && path.equals(it.getPath()));
+	}
+
+	@Override
+	public byte[] serialize(FunctionInfo info) {
+		return super.serialize(info);
+	}
 }

+ 4 - 0
src/main/java/org/ssssssss/magicapi/provider/GroupServiceProvider.java

@@ -1,10 +1,12 @@
 package org.ssssssss.magicapi.provider;
 
+import org.ssssssss.magicapi.adapter.Resource;
 import org.ssssssss.magicapi.model.Group;
 import org.ssssssss.magicapi.model.TreeNode;
 
 import java.util.List;
 
+
 public interface GroupServiceProvider {
 
 	/**
@@ -51,4 +53,6 @@ public interface GroupServiceProvider {
 	 * 根据分组Id获取分组名称
 	 */
 	String getFullName(String groupId);
+
+	Resource getGroupResource(String groupId);
 }

+ 149 - 37
src/main/java/org/ssssssss/magicapi/provider/StoreServiceProvider.java

@@ -1,84 +1,196 @@
 package org.ssssssss.magicapi.provider;
 
+import org.ssssssss.magicapi.adapter.Resource;
+import org.ssssssss.magicapi.model.MagicEntity;
+import org.ssssssss.magicapi.utils.JsonUtils;
+
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.stream.Collectors;
 
-public interface StoreServiceProvider<T> {
+public abstract class StoreServiceProvider<T extends MagicEntity> {
 
-	/**
-	 * 添加接口信息
-	 */
-	boolean insert(T info);
+	String separator = "\r\n================================\r\n";
 
-	/**
-	 * 修改接口信息
-	 */
-	boolean update(T info);
+	protected Resource workspace;
 
-	/**
-	 * 备份历史记录
-	 */
-	void backup(String id);
+	protected Map<String, Resource> mappings = new HashMap<>();
 
+	protected Map<String, T> infos = new HashMap<>();
 
+	protected GroupServiceProvider groupServiceProvider;
+
+	protected Class<T> clazz;
+
+	public StoreServiceProvider(Class<T> clazz, Resource workspace, GroupServiceProvider groupServiceProvider) {
+		this.clazz = clazz;
+		this.workspace = workspace;
+		this.groupServiceProvider = groupServiceProvider;
+	}
 	/**
-	 * 查询历史记录
-	 *
-	 * @return 时间戳列表
+	 * 添加
 	 */
-	List<Long> backupList(String id);
+	public boolean insert(T info){
+		info.setId(UUID.randomUUID().toString().replace("-", ""));
+		info.setUpdateTime(System.currentTimeMillis());
+		info.setCreateTime(info.getUpdateTime());
+		Resource dest = groupServiceProvider.getGroupResource(info.getGroupId()).getResource(info.getName() + ".ms");
+		if(!dest.exists() && dest.write(serialize(info))){
+			mappings.put(info.getId(),dest);
+			infos.put(info.getId(),info);
+			return true;
+		}
+		return false;
+	}
 
 	/**
-	 * 查询历史记录详情
-	 *
-	 * @param id        ID
-	 * @param timestamp 时间戳
+	 * 修改
 	 */
-	T backupInfo(String id, Long timestamp);
+	public boolean update(T info){
+		Resource dest = groupServiceProvider.getGroupResource(info.getGroupId()).getResource(info.getName() + ".ms");
+		Resource src = mappings.get(info.getId());
+		if(!src.name().equals(dest.name())){
+			if(dest.exists()){
+				return false;
+			}
+			src.renameTo(dest);
+		}
+		if(dest.write(serialize(info))){
+			mappings.put(info.getId(),dest);
+			infos.put(info.getId(),info);
+			return true;
+		}
+		return false;
+	}
 
 	/**
-	 * 删除接口
+	 * 删除
 	 */
-	boolean delete(String id);
+	public boolean delete(String id){
+		Resource resource = mappings.get(id);
+		if (resource != null && resource.delete()) {
+			mappings.remove(id);
+			infos.remove(id);
+			return true;
+		}
+		return false;
+	}
 
 	/**
 	 * 查询所有(提供给页面,无需带script)
 	 */
-	List<T> list();
+	public List<T> list(){
+		List<T> infos = listWithScript();
+		infos.forEach(info -> info.setScript(null));
+		return infos;
+	}
 
 	/**
 	 * 查询所有(内部使用,需要带Script)
 	 */
-	List<T> listWithScript();
+	public List<T> listWithScript(){
+		List<Resource> resources = workspace.files(".ms");
+		Map<String, Resource> mappings = new HashMap<>();
+		Map<String, T> infos = new HashMap<>();
+		List<T> result = resources.stream().map(r -> {
+			T info = deserialize(r.read());
+			infos.put(info.getId(), info);
+			mappings.put(info.getId(), r);
+			return (T) info.clone();
+		}).collect(Collectors.toList());
+		this.mappings = mappings;
+		this.infos = infos;
+		return result;
+	}
 
 	/**
-	 * 查询接口详情(主要给页面使用)
+	 * 查询详情(主要给页面使用)
 	 *
-	 * @param id 接口ID
+	 * @param id ID
 	 */
-	T get(String id);
+	public T get(String id){
+		return infos.get(id);
+	}
 
 	/**
-	 * 移动接口
+	 * 判断是否允许移动
+	 */
+	public boolean allowMove(String id, String groupId){
+		Resource resource = mappings.get(id);
+		if(resource == null){
+			return false;
+		}
+		return !resource.readonly() && !groupServiceProvider.getGroupResource(groupId).getResource(resource.name()).exists();
+	}
+
+	/**
+	 * 移动
 	 *
 	 * @param id      接口ID
 	 * @param groupId 分组ID
 	 */
-	boolean move(String id, String groupId);
+	public boolean move(String id, String groupId){
+		Resource dest = groupServiceProvider.getGroupResource(groupId);
+		Resource src = mappings.get(id);
+		dest = dest.getResource(src.name());
+		if(dest.exists()){
+			return false;
+		}
+		T info = infos.get(id);
+		src.renameTo(dest);
+		info.setGroupId(groupId);
+		mappings.put(id, dest);
+		return dest.write(serialize(info));
+	}
 
 	/**
 	 * 根据组ID删除
 	 */
-	boolean deleteGroup(List<String> groupIds);
+	public boolean deleteGroup(List<String> groupIds){
+		for (String groupId : groupIds) {
+			if (!groupServiceProvider.getGroupResource(groupId).delete()) {
+				return false;
+			}
+			List<String> infoIds = infos.values().stream().filter(info -> groupId.equals(info.getGroupId()))
+					.map(T::getId)
+					.collect(Collectors.toList());
+			infoIds.forEach(infos::remove);
+			infoIds.forEach(mappings::remove);
+		}
+		return true;
+	}
 
 	/**
-	 * 包装接口信息(可用于加密)
+	 * 包装信息(可用于加密)
 	 */
-	default void wrap(T info) {
-	}
+	public void wrap(T info){}
 
 	/**
-	 * 解除包装接口信息(可用于解密)
+	 * 解除包装信息(可用于解密)
 	 */
-	default void unwrap(T info) {
+	public void unwrap(T info){}
+
+	public byte[] serialize(T info){
+		wrap(info);
+		String script = info.getScript();
+		info.setScript(null);
+		String content = JsonUtils.toJsonString(info) + separator + script;
+		info.setScript(script);
+		unwrap(info);
+		return content.getBytes();
+	}
+
+	public T deserialize(byte[] data){
+		String content = new String(data);
+		int index = content.indexOf(separator);
+		if (index > -1) {
+			T info = JsonUtils.readValue(content.substring(0, index), clazz);
+			info.setScript(content.substring(index + separator.length()));
+			unwrap(info);
+			return info;
+		}
+		return null;
 	}
 }

+ 5 - 118
src/main/java/org/ssssssss/magicapi/provider/impl/DefaultApiServiceProvider.java

@@ -1,125 +1,12 @@
 package org.ssssssss.magicapi.provider.impl;
 
-import org.springframework.jdbc.core.BeanPropertyRowMapper;
-import org.springframework.jdbc.core.JdbcTemplate;
-import org.ssssssss.magicapi.model.ApiInfo;
+import org.ssssssss.magicapi.adapter.Resource;
 import org.ssssssss.magicapi.provider.ApiServiceProvider;
+import org.ssssssss.magicapi.provider.GroupServiceProvider;
 
-import java.util.Arrays;
-import java.util.List;
-import java.util.UUID;
-import java.util.stream.Collectors;
+public class DefaultApiServiceProvider extends ApiServiceProvider {
 
-public class DefaultApiServiceProvider extends BeanPropertyRowMapper<ApiInfo> implements ApiServiceProvider {
-
-	private final String COMMON_COLUMNS = "id,\n" +
-			"api_name,\n" +
-			"api_group_id,\n" +
-			"api_path,\n" +
-			"api_description,\n" +
-			"api_method";
-
-	private final String SCRIPT_COLUMNS = "api_script,\n" +
-			"api_parameter,\n" +
-			"api_request_header,\n" +
-			"api_request_body,\n" +
-			"api_response_body,\n" +
-			"api_option\n";
-
-	private JdbcTemplate template;
-
-	public DefaultApiServiceProvider(JdbcTemplate template) {
-		super(ApiInfo.class);
-		this.template = template;
-	}
-
-	public boolean delete(String id) {
-		String deleteById = "delete from magic_api_info where id = ?";
-		return template.update(deleteById, id) > 0;
-	}
-
-	public List<ApiInfo> list() {
-		String selectList = "select " + COMMON_COLUMNS + " from magic_api_info order by api_update_time desc";
-		return template.query(selectList, this);
-	}
-
-	public List<ApiInfo> listWithScript() {
-		String selectListWithScript = "select " + COMMON_COLUMNS + "," + SCRIPT_COLUMNS + " from magic_api_info";
-		List<ApiInfo> infos = template.query(selectListWithScript, this);
-		for (ApiInfo info : infos) {
-			unwrap(info);
-		}
-		return infos;
-	}
-
-	public ApiInfo get(String id) {
-		String selectOne = "select " + COMMON_COLUMNS + "," + SCRIPT_COLUMNS + " from magic_api_info where id = ?";
-		ApiInfo info = template.queryForObject(selectOne, this, id);
-		unwrap(info);
-		return info;
-	}
-
-	@Override
-	public boolean move(String id, String groupId) {
-		return template.update("update magic_api_info SET api_group_id = ? where id = ?", groupId, id) > 0;
-	}
-
-	@Override
-	public boolean deleteGroup(List<String> groupIds) {
-		List<Object[]> params = groupIds.stream().map(groupId -> new Object[]{groupId}).collect(Collectors.toList());
-		return Arrays.stream(template.batchUpdate("delete from magic_api_info where api_group_id = ?", params)).sum() >= 0;
-	}
-
-	public boolean exists(String groupId, String method, String path) {
-		String exists = "select count(*) from magic_api_info where api_method = ? and api_path = ? and api_group_id = ?";
-		return template.queryForObject(exists, Integer.class, method, path, groupId) > 0;
-	}
-
-	public boolean existsWithoutId(String groupId, String method, String path, String id) {
-		String existsWithoutId = "select count(*) from magic_api_info where api_method = ? and api_path = ? and api_group_id = ? and id !=?";
-		return template.queryForObject(existsWithoutId, Integer.class, method, path, groupId, id) > 0;
-	}
-
-	public boolean insert(ApiInfo info) {
-		info.setId(UUID.randomUUID().toString().replace("-", ""));
-		wrap(info);
-		long time = System.currentTimeMillis();
-		String insert = "insert into magic_api_info(id,api_method,api_path,api_script,api_name,api_group_id,api_parameter,api_description,api_option,api_request_header,api_request_body,api_response_body,api_create_time,api_update_time) values(?,?,?,?,?,?,?,?,?,?,?,?,?,?)";
-		return template.update(insert, info.getId(), info.getMethod(), info.getPath(), info.getScript(), info.getName(), info.getGroupId(), info.getParameter(), info.getDescription(), info.getOption(), info.getRequestHeader(), info.getRequestBody(), info.getResponseBody(), time, time) > 0;
-	}
-
-	public boolean update(ApiInfo info) {
-		wrap(info);
-		String update = "update magic_api_info set api_method = ?,api_path = ?,api_script = ?,api_name = ?,api_group_id = ?,api_description = ?,api_parameter = ?,api_option = ?,api_request_header = ?,api_request_body = ?,api_response_body = ?,api_update_time = ? where id = ?";
-		return template.update(update, info.getMethod(), info.getPath(), info.getScript(), info.getName(), info.getGroupId(), info.getDescription(), info.getParameter(), info.getOption(), info.getRequestHeader(), info.getRequestBody(), info.getResponseBody(), System.currentTimeMillis(), info.getId()) > 0;
-	}
-
-	@Override
-	public void backup(String apiId) {
-		String backupSql = "insert into magic_api_info_his select * from magic_api_info where id = ?";
-		template.update(backupSql, apiId);
-	}
-
-
-	@Override
-	public List<Long> backupList(String apiId) {
-		return template.queryForList("select api_update_time from magic_api_info_his where id = ? order by api_update_time desc", Long.class, apiId);
-	}
-
-	@Override
-	public ApiInfo backupInfo(String apiId, Long timestamp) {
-		String selectOne = "select " + COMMON_COLUMNS + "," + SCRIPT_COLUMNS + " from magic_api_info_his where id = ? and api_update_time = ?";
-		List<ApiInfo> list = template.query(selectOne, this, apiId, timestamp);
-		if (list != null && !list.isEmpty()) {
-			ApiInfo info = list.get(0);
-			unwrap(info);
-			return info;
-		}
-		return null;
-	}
-
-	@Override
-	protected String lowerCaseName(String name) {
-		return super.lowerCaseName(name).replace("api_", "");
+	public DefaultApiServiceProvider(GroupServiceProvider groupServiceProvider, Resource workspace) {
+		super(workspace.getResource("api"), groupServiceProvider);
 	}
 }

+ 5 - 118
src/main/java/org/ssssssss/magicapi/provider/impl/DefaultFunctionServiceProvider.java

@@ -1,128 +1,15 @@
 package org.ssssssss.magicapi.provider.impl;
 
-import org.springframework.jdbc.core.BeanPropertyRowMapper;
-import org.springframework.jdbc.core.JdbcTemplate;
-import org.ssssssss.magicapi.model.FunctionInfo;
+import org.ssssssss.magicapi.adapter.Resource;
 import org.ssssssss.magicapi.provider.FunctionServiceProvider;
+import org.ssssssss.magicapi.provider.GroupServiceProvider;
 
-import java.util.Arrays;
-import java.util.List;
-import java.util.UUID;
-import java.util.stream.Collectors;
+public class DefaultFunctionServiceProvider extends FunctionServiceProvider {
 
-public class DefaultFunctionServiceProvider extends BeanPropertyRowMapper<FunctionInfo> implements FunctionServiceProvider {
 
-	private final String COMMON_COLUMNS = "id,\n" +
-			"function_name,\n" +
-			"function_group_id,\n" +
-			"function_path,\n" +
-			"function_parameter,\n" +
-			"function_return_type,\n" +
-			"function_description,\n" +
-			"function_create_time,\n" +
-			"function_update_time";
-
-	private final String SCRIPT_COLUMNS = "function_script";
-
-	private JdbcTemplate template;
-
-	public DefaultFunctionServiceProvider(JdbcTemplate template) {
-		super(FunctionInfo.class);
-		this.template = template;
-	}
-
-	@Override
-	public boolean insert(FunctionInfo info) {
-		info.setId(UUID.randomUUID().toString().replace("-", ""));
-		wrap(info);
-		String insert = String.format("insert into magic_function(%s,%s) values(?,?,?,?,?,?,?,?,?,?)", COMMON_COLUMNS, SCRIPT_COLUMNS);
-		long time = System.currentTimeMillis();
-		return template.update(insert, info.getId(), info.getName(), info.getGroupId(), info.getPath(), info.getParameter(), info.getReturnType(), info.getDescription(), time, time, info.getScript()) > 0;
-	}
-
-	@Override
-	public boolean update(FunctionInfo info) {
-		wrap(info);
-		String update = "update magic_function set function_name = ?,function_parameter = ?,function_return_type = ?, function_script = ?,function_description = ?,function_path = ?,function_group_id = ?,function_update_time = ? where id = ?";
-		return template.update(update, info.getName(), info.getParameter(), info.getReturnType(), info.getScript(), info.getDescription(), info.getPath(), info.getGroupId(), System.currentTimeMillis(), info.getId()) > 0;
-	}
-
-	@Override
-	public void backup(String functionId) {
-		String backupSql = "insert into magic_function_his select * from magic_function where id = ?";
-		template.update(backupSql, functionId);
-	}
-
-	@Override
-	public List<Long> backupList(String id) {
-		return template.queryForList("select function_update_time from magic_function_his where id = ? order by function_update_time desc", Long.class, id);
-	}
-
-	@Override
-	public FunctionInfo backupInfo(String id, Long timestamp) {
-		String selectOne = "select " + COMMON_COLUMNS + "," + SCRIPT_COLUMNS + " from magic_function_his where id = ? and function_update_time = ?";
-		List<FunctionInfo> list = template.query(selectOne, this, id, timestamp);
-		if (list != null && !list.isEmpty()) {
-			FunctionInfo info = list.get(0);
-			unwrap(info);
-			return info;
-		}
-		return null;
-	}
-
-	@Override
-	public boolean delete(String id) {
-		return template.update("delete from magic_function where id = ?", id) > 0;
+	public DefaultFunctionServiceProvider(GroupServiceProvider groupServiceProvider, Resource workspace) {
+		super(workspace.getResource("function"), groupServiceProvider);
 	}
 
-	@Override
-	public List<FunctionInfo> list() {
-		String selectList = "select " + COMMON_COLUMNS + " from magic_function order by function_update_time desc";
-		return template.query(selectList, this);
-	}
-
-	@Override
-	public List<FunctionInfo> listWithScript() {
-		String selectListWithScript = "select " + COMMON_COLUMNS + "," + SCRIPT_COLUMNS + " from magic_function";
-		List<FunctionInfo> infos = template.query(selectListWithScript, this);
-		for (FunctionInfo info : infos) {
-			unwrap(info);
-		}
-		return infos;
-	}
-
-	@Override
-	public FunctionInfo get(String id) {
-		String selectOne = "select " + COMMON_COLUMNS + "," + SCRIPT_COLUMNS + " from magic_function where id = ?";
-		FunctionInfo info = template.queryForObject(selectOne, this, id);
-		unwrap(info);
-		return info;
-	}
-
-	@Override
-	public boolean move(String id, String groupId) {
-		return template.update("update magic_function SET function_group_id = ? where id = ?", groupId, id) > 0;
-	}
-
-	@Override
-	public boolean deleteGroup(List<String> groupIds) {
-		List<Object[]> params = groupIds.stream().map(groupId -> new Object[]{groupId}).collect(Collectors.toList());
-		return Arrays.stream(template.batchUpdate("delete from magic_function where function_group_id = ?", params)).sum() >= 0;
-	}
-
-	@Override
-	protected String lowerCaseName(String name) {
-		return super.lowerCaseName(name).replace("function_", "");
-	}
-
-	@Override
-	public boolean exists(String path, String groupId) {
-		return template.queryForObject("select count(*) from magic_function where function_group_id = ? and function_path = ?", Integer.class, groupId, path) > 0;
-	}
-
-	@Override
-	public boolean existsWithoutId(String path, String groupId, String id) {
-		return template.queryForObject("select count(*) from magic_function where function_group_id = ? and function_path = ? and id != ?", Integer.class, groupId, path, id) > 0;
-	}
 
 }

+ 59 - 23
src/main/java/org/ssssssss/magicapi/provider/impl/DefaultGroupServiceProvider.java

@@ -1,45 +1,71 @@
 package org.ssssssss.magicapi.provider.impl;
 
-import org.springframework.jdbc.core.BeanPropertyRowMapper;
-import org.springframework.jdbc.core.JdbcTemplate;
+import org.ssssssss.magicapi.adapter.Resource;
 import org.ssssssss.magicapi.model.Group;
 import org.ssssssss.magicapi.model.TreeNode;
 import org.ssssssss.magicapi.provider.GroupServiceProvider;
+import org.ssssssss.magicapi.utils.JsonUtils;
 import org.ssssssss.magicapi.utils.PathUtils;
 
 import java.util.*;
 import java.util.stream.Collectors;
 
-public class DefaultGroupServiceProvider extends BeanPropertyRowMapper<Group> implements GroupServiceProvider {
-
-	private JdbcTemplate template;
+public class DefaultGroupServiceProvider implements GroupServiceProvider {
 
 	private Map<String, Group> cacheApiTree = new HashMap<>();
 
 	private Map<String, Group> cacheFunctionTree = new HashMap<>();
 
-	public DefaultGroupServiceProvider(JdbcTemplate template) {
-		super(Group.class);
-		this.template = template;
+	private final Map<String, Resource> mappings = new HashMap<>();
+
+	private final Resource workspace;
+
+	private String metabase = "group.json";
+
+	public DefaultGroupServiceProvider(Resource workspace) {
+		this.workspace = workspace;
 	}
 
 	@Override
 	public boolean insert(Group group) {
 		group.setId(UUID.randomUUID().toString().replace("-", ""));
-		String insertGroup = "insert into magic_group(id,group_name,group_type,group_path,parent_id) values(?,?,?,?,?)";
-		return template.update(insertGroup, group.getId(), group.getName(), group.getType(), group.getPath(), group.getParentId()) > 0;
+		Resource directory = this.getGroupResource(group.getParentId());
+		directory = directory == null ? this.getGroupResource(group.getType(),group.getName()) : directory;
+		Resource resource = directory.getResource(group.getName());
+		if (!resource.exists() && resource.mkdir()) {
+			resource = resource.getResource(metabase);
+			if (resource.write(JsonUtils.toJsonString(group))) {
+				mappings.put(group.getId(), resource);
+				return true;
+			}
+		}
+		return false;
+	}
+
+	private Resource getGroupResource(String type, String name) {
+		return this.workspace.getResource("1".equals(type) ? "api" : "function").getResource(name);
 	}
 
 	@Override
 	public boolean update(Group group) {
-		String updateGroup = "update magic_group set group_name = ?,group_path=?,parent_id = ? where id = ?";
-		return template.update(updateGroup, group.getName(), group.getPath(), group.getParentId(), group.getId()) >= 0;
+		Resource oldResource = this.getGroupResource(group.getId());
+		Resource newResource = this.getGroupResource(group.getParentId());
+		newResource = newResource == null ? getGroupResource(group.getType(),group.getName()) : newResource.getResource(group.getName());
+		// 重命名或移动目录
+		if(oldResource.renameTo(newResource)){
+			Resource target = newResource.getResource(metabase);
+			if (target.write(JsonUtils.toJsonString(group))) {
+				mappings.put(group.getId(), target);
+				return true;
+			}
+		}
+		return false;
 	}
 
 	@Override
 	public boolean delete(String groupId) {
-		String deleteByGroupId = "delete from magic_group where id = ?";
-		return template.update(deleteByGroupId, groupId) > 0;
+		mappings.remove(groupId);
+		return true;
 	}
 
 	@Override
@@ -49,21 +75,28 @@ public class DefaultGroupServiceProvider extends BeanPropertyRowMapper<Group> im
 
 	@Override
 	public TreeNode<Group> apiGroupTree() {
-		List<Group> groups = template.query("select * from magic_group where group_type = '1'", this);
+		List<Group> groups = groupList("1");
 		cacheApiTree = groups.stream().collect(Collectors.toMap(Group::getId, value -> value));
 		return convertToTree(groups);
 	}
 
 	@Override
 	public TreeNode<Group> functionGroupTree() {
-		List<Group> groups = template.query("select * from magic_group where group_type = '2'", this);
+		List<Group> groups = groupList("2");
 		cacheFunctionTree = groups.stream().collect(Collectors.toMap(Group::getId, value -> value));
 		return convertToTree(groups);
 	}
 
 	@Override
 	public List<Group> groupList(String type) {
-		return template.query("select * from magic_group where group_type = ?", this, type);
+		Resource resource = this.workspace.getResource("1".equals(type) ? "api" : "function");
+		return resource.dirs().stream().map(it -> it.getResource(metabase)).filter(Resource::exists)
+				.map(it -> {
+					Group group = JsonUtils.readValue(it.read(), Group.class);
+					mappings.put(group.getId(), it);
+					return group;
+				})
+				.collect(Collectors.toList());
 	}
 
 	@Override
@@ -99,6 +132,14 @@ public class DefaultGroupServiceProvider extends BeanPropertyRowMapper<Group> im
 		return name.substring(1);
 	}
 
+	@Override
+	public Resource getGroupResource(String groupId) {
+		if (groupId == null || "0".equals(groupId)) {
+			return null;
+		}
+		return mappings.get(groupId).parent();
+	}
+
 	private TreeNode<Group> convertToTree(List<Group> groups) {
 		TreeNode<Group> root = new TreeNode<>();
 		root.setNode(new Group("0", "root"));
@@ -110,7 +151,7 @@ public class DefaultGroupServiceProvider extends BeanPropertyRowMapper<Group> im
 		Group temp;
 		List<TreeNode<Group>> childNodes = new LinkedList<>();
 		Iterator<Group> iterator = remains.iterator();
-		while (iterator.hasNext()){
+		while (iterator.hasNext()) {
 			temp = iterator.next();
 			if (current.getNode().getId().equals(temp.getParentId())) {
 				childNodes.add(new TreeNode<>(temp));
@@ -120,9 +161,4 @@ public class DefaultGroupServiceProvider extends BeanPropertyRowMapper<Group> im
 		current.setChildren(childNodes);
 		childNodes.forEach(it -> convertToTree(remains, it));
 	}
-
-	@Override
-	protected String lowerCaseName(String name) {
-		return super.lowerCaseName(name).replace("group_", "");
-	}
 }

+ 124 - 0
src/main/java/org/ssssssss/magicapi/utils/IoUtils.java

@@ -0,0 +1,124 @@
+package org.ssssssss.magicapi.utils;
+
+import java.io.*;
+import java.nio.file.Files;
+import java.util.ArrayList;
+import java.util.List;
+
+public class IoUtils {
+
+	public static List<File> files(File file, String suffix) {
+		List<File> list = new ArrayList<>();
+		if (file.isDirectory()) {
+			File[] files = file.listFiles((path) -> path.isDirectory() || path.getName().endsWith(suffix));
+			if (files != null) {
+				for (int i = files.length - 1; i >= 0; i--) {
+					list.addAll(files(files[i], suffix));
+				}
+			}
+		} else if (file.exists()) {
+			list.add(file);
+		}
+		return list;
+	}
+
+	public static List<File> dirs(File file) {
+		return subDirs(true, file);
+	}
+
+	private static List<File> subDirs(boolean isRoot, File file) {
+		List<File> list = new ArrayList<>();
+		if (file.isDirectory()) {
+			File[] files = file.listFiles(File::isDirectory);
+			if (files != null) {
+				for (int i = files.length - 1; i >= 0; i--) {
+					list.addAll(subDirs(false, files[i]));
+				}
+			}
+			if(!isRoot){
+				list.add(file);
+			}
+		}
+		return list;
+	}
+
+	public static byte[] bytes(File file) {
+		try {
+			return Files.readAllBytes(file.toPath());
+		} catch (IOException e) {
+			return new byte[0];
+		}
+	}
+
+	public static String string(File file) {
+		return new String(bytes(file));
+	}
+
+	public static byte[] bytes(InputStream inputStream) {
+		try {
+			ByteArrayOutputStream baos = new ByteArrayOutputStream();
+			byte[] buf = new byte[4096];
+			int len = -1;
+			while((len = inputStream.read(buf,0,buf.length)) != -1){
+				baos.write(buf,0,len);
+			}
+			return baos.toByteArray();
+		} catch (IOException e) {
+			return new byte[0];
+		}
+	}
+
+	public static String string(InputStream inputStream) {
+		try(BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))){
+			StringBuilder result = new StringBuilder();
+			String line;
+			boolean flag = false;
+			while ((line = reader.readLine()) != null) {
+				if(flag){
+					result.append("\r\n");
+				}
+				result.append(line);
+				flag = true;
+			}
+			return result.toString();
+		}catch(IOException e){
+			return "";
+		}
+	}
+
+	public static boolean write(File file, byte[] bytes) {
+		try {
+			Files.write(file.toPath(), bytes);
+			return true;
+		} catch (IOException e) {
+			return false;
+		}
+	}
+
+	public static boolean write(File file, String content) {
+		if (content == null) {
+			return false;
+		}
+		return write(file, content.getBytes());
+	}
+
+	public static boolean delete(File file) {
+		if(file == null){
+			return true;
+		}
+		if (file.isDirectory()) {
+			File[] files = file.listFiles();
+			if (files != null) {
+				for (int i = files.length - 1; i >= 0; i--) {
+					if (!delete(files[i])) {
+						return false;
+					}
+				}
+			}
+		}
+		if(!file.exists()){
+			return true;
+		}
+		return file.delete();
+	}
+}

+ 40 - 0
src/main/java/org/ssssssss/magicapi/utils/JsonUtils.java

@@ -0,0 +1,40 @@
+package org.ssssssss.magicapi.utils;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import java.io.IOException;
+
+public class JsonUtils {
+
+	private static ObjectMapper mapper = new ObjectMapper();
+
+	static{
+		mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+	}
+
+	public static String toJsonString(Object target) {
+		try {
+			return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(target);
+		} catch (JsonProcessingException e) {
+			return null;
+		}
+	}
+
+	public static <T> T readValue(String json, Class<T> clazz){
+		try {
+			return mapper.readValue(json, clazz);
+		} catch (IOException e) {
+			return null;
+		}
+	}
+
+	public static <T> T readValue(byte[] bytes, Class<T> clazz){
+		try {
+			return mapper.readValue(bytes, clazz);
+		} catch (IOException e) {
+			return null;
+		}
+	}
+}