Browse Source

备份、备份记录查询

mxd 3 years ago
parent
commit
afbec53c98

+ 11 - 24
magic-api-spring-boot-starter/src/main/java/org/ssssssss/magicapi/spring/boot/starter/BackupConfig.java

@@ -4,19 +4,14 @@ package org.ssssssss.magicapi.spring.boot.starter;
  * 备份配置
  *
  * @author mxd
- * @since 1.3.5
+ * @since 2.0.0
  */
 public class BackupConfig {
 
 	/**
-	 * 存储类型,可选 file, database
+	 * 是否启用备份配置,默认不启用
 	 */
-	private String resourceType = "file";
-
-	/**
-	 * 存储位置,选择存储为文件时专用
-	 */
-	private String location = "/data/magic-api/backup";
+	private boolean enable = false;
 
 	/**
 	 * 保留天数,<=0 为不限制
@@ -33,22 +28,6 @@ public class BackupConfig {
 	 */
 	private String datasource;
 
-	public String getResourceType() {
-		return resourceType;
-	}
-
-	public void setResourceType(String resourceType) {
-		this.resourceType = resourceType;
-	}
-
-	public String getLocation() {
-		return location;
-	}
-
-	public void setLocation(String location) {
-		this.location = location;
-	}
-
 	public int getMaxHistory() {
 		return maxHistory;
 	}
@@ -72,4 +51,12 @@ public class BackupConfig {
 	public void setDatasource(String datasource) {
 		this.datasource = datasource;
 	}
+
+	public boolean isEnable() {
+		return enable;
+	}
+
+	public void setEnable(boolean enable) {
+		this.enable = enable;
+	}
 }

+ 14 - 10
magic-api-spring-boot-starter/src/main/java/org/ssssssss/magicapi/spring/boot/starter/MagicAPIAutoConfiguration.java

@@ -270,7 +270,7 @@ public class MagicAPIAutoConfiguration implements WebMvcConfigurer, WebSocketCon
 
 	@Bean
 	@ConditionalOnMissingBean(MagicBackupService.class)
-	@ConditionalOnProperty(prefix = "magic-api", name = "backup-config.resource-type", havingValue = "database")
+	@ConditionalOnProperty(prefix = "magic-api", name = "backup-config.enable", havingValue = "true")
 	public MagicBackupService magicDatabaseBackupService(MagicDynamicDataSource magicDynamicDataSource) {
 		BackupConfig backupConfig = properties.getBackupConfig();
 		MagicDynamicDataSource.DataSourceNode dataSourceNode = magicDynamicDataSource.getDataSource(backupConfig.getDatasource());
@@ -446,8 +446,8 @@ public class MagicAPIAutoConfiguration implements WebMvcConfigurer, WebSocketCon
 	 * 注册模块、类型扩展
 	 */
 	private void setupMagicModules(MagicDynamicDataSource dynamicDataSource,
-                                   SQLModule sqlModule,
-                                   ResultProvider resultProvider,
+								   SQLModule sqlModule,
+								   ResultProvider resultProvider,
 								   List<MagicModule> magicModules,
 								   List<ExtensionMethod> extensionMethods,
 								   List<LanguageProvider> languageProviders) {
@@ -479,7 +479,7 @@ public class MagicAPIAutoConfiguration implements WebMvcConfigurer, WebSocketCon
 				}).orElse(null)
 		);
 		logger.info("注册模块:{} -> {}", "log", Logger.class);
-		MagicResourceLoader.addModule("log", new DynamicModuleImport(Logger.class, context -> LoggerFactory.getLogger(Objects.toString(context.getScriptName(),"Unknown"))));
+		MagicResourceLoader.addModule("log", new DynamicModuleImport(Logger.class, context -> LoggerFactory.getLogger(Objects.toString(context.getScriptName(), "Unknown"))));
 		List<String> importModules = properties.getAutoImportModuleList();
 		logger.info("注册模块:{} -> {}", "env", EnvModule.class);
 		MagicResourceLoader.addModule("env", new EnvModule(environment));
@@ -493,13 +493,13 @@ public class MagicAPIAutoConfiguration implements WebMvcConfigurer, WebSocketCon
 			logger.info("注册模块:{} -> {}", module.getModuleName(), module.getClass());
 			MagicResourceLoader.addModule(module.getModuleName(), module);
 		});
-        MagicResourceLoader.addModule(sqlModule.getModuleName(), new DynamicModuleImport(SQLModule.class, context -> {
+		MagicResourceLoader.addModule(sqlModule.getModuleName(), new DynamicModuleImport(SQLModule.class, context -> {
 			String dataSourceKey = context.getString(Options.DEFAULT_DATA_SOURCE.getValue());
-			if(StringUtils.isEmpty(dataSourceKey)) return sqlModule;
+			if (StringUtils.isEmpty(dataSourceKey)) return sqlModule;
 			SQLModule newSqlModule = sqlModule.cloneSQLModule();
 			newSqlModule.setDataSourceNode(dynamicDataSource.getDataSource(dataSourceKey));
-            return newSqlModule;
-        }));
+			return newSqlModule;
+		}));
 		MagicResourceLoader.getModuleNames().stream().filter(importModules::contains).forEach(moduleName -> {
 			logger.info("自动导入模块:{}", moduleName);
 			MagicScriptEngine.addDefaultImport(moduleName, MagicResourceLoader.loadModule(moduleName));
@@ -571,7 +571,8 @@ public class MagicAPIAutoConfiguration implements WebMvcConfigurer, WebSocketCon
 			configuration.setEnableWeb(true);
 			mapping.registerController(magicWorkbenchController)
 					.registerController(new MagicResourceController(configuration))
-					.registerController(new MagicDataSourceController(configuration));
+					.registerController(new MagicDataSourceController(configuration))
+					.registerController(new MagicBackupController(configuration));
 		}
 		// 注册接收推送的接口
 		if (StringUtils.isNotBlank(properties.getSecretKey())) {
@@ -586,8 +587,11 @@ public class MagicAPIAutoConfiguration implements WebMvcConfigurer, WebSocketCon
 		if (this.properties.isBanner()) {
 			configuration.printBanner();
 		}
+		if (magicBackupService == null) {
+			logger.error("当前备份设置未配置,强烈建议配置备份设置,以免代码丢失。");
+		}
 		// 备份清理
-		if (properties.getBackupConfig().getMaxHistory() > 0) {
+		if (properties.getBackupConfig().getMaxHistory() > 0 && magicBackupService != null) {
 			long interval = properties.getBackupConfig().getMaxHistory() * 86400000L;
 			// 1小时执行1次
 			new ScheduledThreadPoolExecutor(1, r -> new Thread(r, "magic-api-clean-task")).scheduleAtFixedRate(() -> {

+ 44 - 0
magic-api/src/main/java/org/ssssssss/magicapi/controller/MagicBackupController.java

@@ -0,0 +1,44 @@
+package org.ssssssss.magicapi.controller;
+
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.ssssssss.magicapi.config.MagicConfiguration;
+import org.ssssssss.magicapi.model.Backup;
+import org.ssssssss.magicapi.model.JsonBean;
+import org.ssssssss.magicapi.provider.MagicBackupService;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.List;
+
+public class MagicBackupController extends MagicController {
+
+	private final MagicBackupService service;
+
+	public MagicBackupController(MagicConfiguration configuration) {
+		super(configuration);
+		this.service = configuration.getMagicBackupService();
+	}
+
+	@GetMapping("/backups")
+	@ResponseBody
+	public JsonBean<List<Backup>> backups(Long timestamp) {
+		return new JsonBean<>(service.backupList(timestamp == null ? System.currentTimeMillis() : timestamp));
+	}
+
+	@PostMapping("/backup")
+	@ResponseBody
+	public JsonBean<Boolean> doBackup(String tag) throws IOException {
+		ByteArrayOutputStream baos = new ByteArrayOutputStream();
+		MagicConfiguration.getMagicResourceService().export(null, null, baos);
+		Backup backup = new Backup();
+		backup.setId("full");
+		backup.setType("full");
+		backup.setName("全量备份");
+		backup.setContent(baos.toByteArray());
+		backup.setTag(tag);
+		service.doBackup(backup);
+		return new JsonBean<>(true);
+	}
+}

+ 16 - 14
magic-api/src/main/java/org/ssssssss/magicapi/controller/MagicResourceController.java

@@ -6,6 +6,7 @@ import org.ssssssss.magicapi.exception.InvalidArgumentException;
 import org.ssssssss.magicapi.interceptor.Authorization;
 import org.ssssssss.magicapi.model.*;
 import org.ssssssss.magicapi.service.MagicDynamicRegistry;
+import org.ssssssss.magicapi.service.MagicResourceService;
 import org.ssssssss.magicapi.utils.IoUtils;
 
 import javax.servlet.http.HttpServletRequest;
@@ -17,14 +18,17 @@ import java.util.function.Function;
 
 public class MagicResourceController extends MagicController implements MagicExceptionHandler {
 
+	private MagicResourceService service;
+
 	public MagicResourceController(MagicConfiguration configuration) {
 		super(configuration);
+		this.service = MagicConfiguration.getMagicResourceService();
 	}
 
 	@PostMapping("/resource/folder/save")
 	@ResponseBody
 	public JsonBean<String> saveFolder(@RequestBody Group group) {
-		if (configuration.getMagicResourceService().saveGroup(group)) {
+		if (service.saveGroup(group)) {
 			return new JsonBean<>(group.getId());
 		}
 		return new JsonBean<>((String) null);
@@ -33,7 +37,7 @@ public class MagicResourceController extends MagicController implements MagicExc
 	@PostMapping("/resource/delete")
 	@ResponseBody
 	public JsonBean<Boolean> delete(String id, HttpServletRequest request) {
-		return new JsonBean<>(configuration.getMagicResourceService().delete(id));
+		return new JsonBean<>(service.delete(id));
 	}
 
 	@PostMapping("/resource/file/{folder}/save")
@@ -47,7 +51,7 @@ public class MagicResourceController extends MagicController implements MagicExc
 				.orElseThrow(() -> new InvalidArgumentException(GROUP_NOT_FOUND))
 				.read(bytes);
 		isTrue(allowVisit(request, Authorization.SAVE, entity), PERMISSION_INVALID);
-		if (configuration.getMagicResourceService().saveFile(entity)) {
+		if (MagicConfiguration.getMagicResourceService().saveFile(entity)) {
 			return new JsonBean<>(entity.getId());
 		}
 		return new JsonBean<>(null);
@@ -56,37 +60,37 @@ public class MagicResourceController extends MagicController implements MagicExc
 	@GetMapping("/resource/file/{id}")
 	@ResponseBody
 	public JsonBean<MagicEntity> detail(@PathVariable("id") String id, HttpServletRequest request) {
-		MagicEntity entity = configuration.getMagicResourceService().file(id);
+		MagicEntity entity = MagicConfiguration.getMagicResourceService().file(id);
 		isTrue(allowVisit(request, Authorization.VIEW, entity), PERMISSION_INVALID);
-		return new JsonBean<>(configuration.getMagicResourceService().file(id));
+		return new JsonBean<>(MagicConfiguration.getMagicResourceService().file(id));
 	}
 
 	@PostMapping("/resource/move")
 	@ResponseBody
 	public JsonBean<Boolean> move(String src, String groupId) {
-		return new JsonBean<>(configuration.getMagicResourceService().move(src, groupId));
+		return new JsonBean<>(service.move(src, groupId));
 	}
 
 	@PostMapping("/resource/lock")
 	@ResponseBody
 	public JsonBean<Boolean> lock(String id, HttpServletRequest request) {
-		MagicEntity entity = configuration.getMagicResourceService().file(id);
+		MagicEntity entity = service.file(id);
 		isTrue(allowVisit(request, Authorization.LOCK, entity), PERMISSION_INVALID);
-		return new JsonBean<>(configuration.getMagicResourceService().lock(id));
+		return new JsonBean<>(service.lock(id));
 	}
 
 	@PostMapping("/resource/unlock")
 	@ResponseBody
 	public JsonBean<Boolean> unlock(String id, HttpServletRequest request) {
-		MagicEntity entity = configuration.getMagicResourceService().file(id);
+		MagicEntity entity = service.file(id);
 		isTrue(allowVisit(request, Authorization.UNLOCK, entity), PERMISSION_INVALID);
-		return new JsonBean<>(configuration.getMagicResourceService().unlock(id));
+		return new JsonBean<>(service.unlock(id));
 	}
 
 	@GetMapping("/resource")
 	@ResponseBody
 	public JsonBean<Map<String, TreeNode<Attributes<Object>>>> resources() {
-		Map<String, TreeNode<Group>> tree = configuration.getMagicResourceService().tree();
+		Map<String, TreeNode<Group>> tree = service.tree();
 		Map<String, TreeNode<Attributes<Object>>> result = new HashMap<>();
 		tree.forEach((key, value) -> result.put(key, process(value)));
 		return new JsonBean<>(result);
@@ -97,7 +101,7 @@ public class MagicResourceController extends MagicController implements MagicExc
 		value.setNode(groupNode.getNode());
 		groupNode.getChildren().stream().map(this::process).forEach(value::addChild);
 		if (!Constants.ROOT_ID.equals(groupNode.getNode().getId())) {
-			configuration.getMagicResourceService()
+			service
 					.listFiles(groupNode.getNode().getId())
 					.stream()
 					.map(MagicEntity::simple)
@@ -106,6 +110,4 @@ public class MagicResourceController extends MagicController implements MagicExc
 		}
 		return value;
 	}
-
-
 }

+ 0 - 38
magic-api/src/main/java/org/ssssssss/magicapi/provider/MagicBackupService.java

@@ -1,9 +1,6 @@
 package org.ssssssss.magicapi.provider;
 
-import org.ssssssss.magicapi.model.ApiInfo;
 import org.ssssssss.magicapi.model.Backup;
-import org.ssssssss.magicapi.model.DataSourceInfo;
-import org.ssssssss.magicapi.model.FunctionInfo;
 
 import java.util.List;
 
@@ -16,33 +13,6 @@ public interface MagicBackupService {
 
 	int FETCH_SIZE = 100;
 
-	/**
-	 * 备份接口
-	 *
-	 * @param apiInfo 接口信息
-	 */
-	default void backup(ApiInfo apiInfo) {
-//		doBackup(new Backup(apiInfo.getId(), Constants.PATH_API, apiInfo.getName(), JsonUtils.toJsonString(apiInfo)));
-	}
-
-	/**
-	 * 备份函数
-	 *
-	 * @param functionInfo 函数信息
-	 */
-	default void backup(FunctionInfo functionInfo) {
-//		doBackup(new Backup(functionInfo.getId(), Constants.PATH_FUNCTION, functionInfo.getName(), JsonUtils.toJsonString(functionInfo)));
-	}
-
-	/**
-	 * 备份数据源
-	 *
-	 * @param dataSourceInfo 数据源信息
-	 */
-	default void backup(DataSourceInfo dataSourceInfo) {
-		// doBackup(new Backup(dataSourceInfo.getId(), Constants.PATH_DATASOURCE, dataSourceInfo.get("name"), JsonUtils.toJsonString(dataSourceInfo)));
-	}
-
 	/**
 	 * 执行备份动作
 	 *
@@ -87,14 +57,6 @@ public interface MagicBackupService {
 	 */
 	long removeBackup(String id);
 
-	/**
-	 * 删除一组备份信息
-	 *
-	 * @param idList 对象ID集合
-	 * @return 返回删除的记录数
-	 */
-	long removeBackup(List<String> idList);
-
 	/**
 	 * 根据13位时间戳删除备份记录(清除小于该值的记录)
 	 *

+ 65 - 13
magic-api/src/main/java/org/ssssssss/magicapi/provider/impl/MagicDatabaseBackupService.java

@@ -1,9 +1,17 @@
 package org.ssssssss.magicapi.provider.impl;
 
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.context.event.EventListener;
 import org.springframework.jdbc.core.BeanPropertyRowMapper;
 import org.springframework.jdbc.core.JdbcTemplate;
+import org.ssssssss.magicapi.event.FileEvent;
+import org.ssssssss.magicapi.event.GroupEvent;
 import org.ssssssss.magicapi.model.Backup;
+import org.ssssssss.magicapi.model.Group;
+import org.ssssssss.magicapi.model.MagicEntity;
 import org.ssssssss.magicapi.provider.MagicBackupService;
+import org.ssssssss.magicapi.utils.JsonUtils;
 import org.ssssssss.magicapi.utils.WebUtils;
 
 import java.util.List;
@@ -37,6 +45,8 @@ public class MagicDatabaseBackupService implements MagicBackupService {
 
 	private final BeanPropertyRowMapper<Backup> rowMapper = new BeanPropertyRowMapper<>(Backup.class);
 
+	private static final Logger logger = LoggerFactory.getLogger(MagicDatabaseBackupService.class);
+
 	public MagicDatabaseBackupService(JdbcTemplate template, String tableName) {
 		this.template = template;
 		this.INSERT_SQL = String.format("insert into %s(%s,content) values(?,?,?,?,?,?,?)", tableName, DEFAULT_COLUMNS);
@@ -50,19 +60,24 @@ public class MagicDatabaseBackupService implements MagicBackupService {
 
 	@Override
 	public void doBackup(Backup backup) {
-		if (backup.getCreateDate() == 0) {
-			backup.setCreateDate(System.currentTimeMillis());
-		}
-		if (backup.getCreateBy() == null) {
-			backup.setCreateBy(WebUtils.currentUserName());
+		try {
+			if (backup.getCreateDate() == 0) {
+				backup.setCreateDate(System.currentTimeMillis());
+			}
+			if (backup.getCreateBy() == null) {
+				backup.setCreateBy(WebUtils.currentUserName());
+			}
+			template.update(INSERT_SQL, backup.getId(), backup.getCreateDate(), backup.getTag(), backup.getType(), backup.getName(), backup.getCreateBy(), backup.getContent());
+		} catch (Exception e) {
+			logger.warn("备份失败", e);
 		}
-		template.update(INSERT_SQL, backup.getId(), backup.getCreateDate(), backup.getTag(), backup.getType(), backup.getName(), backup.getCreateBy(), backup.getContent());
 	}
 
 	@Override
 	public List<Backup> backupList(long timestamp) {
-		Stream<Backup> stream = template.queryForStream(FIND_BY_TIMESTAMP, rowMapper, timestamp);
-		return stream.limit(FETCH_SIZE).collect(Collectors.toList());
+		try (Stream<Backup> stream = template.queryForStream(FIND_BY_TIMESTAMP, rowMapper, timestamp)) {
+			return stream.limit(FETCH_SIZE).collect(Collectors.toList());
+		}
 	}
 
 	@Override
@@ -86,12 +101,49 @@ public class MagicDatabaseBackupService implements MagicBackupService {
 	}
 
 	@Override
-	public long removeBackup(List<String> idList) {
-		return idList.stream().mapToLong(this::removeBackup).sum();
+	public long removeBackupByTimestamp(long timestamp) {
+		try {
+			return template.update(DELETE_BY_TIMESTAMP, timestamp);
+		} catch (Exception e) {
+			logger.warn("删除备份失败", e);
+			return -1;
+		}
 	}
 
-	@Override
-	public long removeBackupByTimestamp(long timestamp) {
-		return template.update(DELETE_BY_TIMESTAMP, timestamp);
+	@EventListener(condition = "#event.source != T(org.ssssssss.magicapi.model.Constants).EVENT_SOURCE_NOTIFY")
+	public void onFileEvent(FileEvent event) {
+		switch (event.getAction()) {
+			case SAVE:
+			case CREATE:
+			case MOVE:
+				break;
+			default:
+				return;
+		}
+		MagicEntity entity = event.getEntity();
+		doBackup(entity.getId(), JsonUtils.toJsonBytes(entity), entity.getName(), event.getType());
+	}
+
+	@EventListener(condition = "#event.source != T(org.ssssssss.magicapi.model.Constants).EVENT_SOURCE_NOTIFY")
+	public void onFolderEvent(GroupEvent event) {
+		switch (event.getAction()) {
+			case SAVE:
+			case CREATE:
+			case MOVE:
+				break;
+			default:
+				return;
+		}
+		Group group = event.getGroup();
+		doBackup(group.getId(), JsonUtils.toJsonBytes(group), group.getName(), group.getType() + "-group");
+	}
+
+	private void doBackup(String id, byte[] content, String name, String type) {
+		Backup backup = new Backup();
+		backup.setName(name);
+		backup.setId(id);
+		backup.setContent(content);
+		backup.setType(type);
+		doBackup(backup);
 	}
 }

+ 6 - 0
magic-api/src/main/java/org/ssssssss/magicapi/utils/JsonUtils.java

@@ -10,6 +10,7 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.io.IOException;
+import java.nio.charset.StandardCharsets;
 
 /**
  * JSON工具包
@@ -80,4 +81,9 @@ public class JsonUtils {
 		}
 	}
 
+	public static byte[] toJsonBytes(Object target) {
+		String json = toJsonString(target);
+		return json == null ? new byte[0] : json.getBytes(StandardCharsets.UTF_8);
+	}
+
 }