手把手教你實現Java許可權管理系統 後端篇(十三):系統備份還原
系統備份還原
在很多時候,我們需要系統資料進行備份還原。我們這裡就使用MySql的備份還原命令實現系統備份還原的功能。
新建工程
新建一個maven專案,並新增相關依賴,可以用Spring boot腳手架生成。
新建 kitty-bakcup 工程,這是一個獨立運行於後臺系統的應用程式,可以分開部署。
pom.xml 檔案新增相關依賴。
<!-- spring boot --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- swagger --> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>${swagger.version}</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>${swagger.version}</version> </dependency>
新增Spring boot啟動類。
package com.louis.kitty.backup; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication(scanBasePackages={"com.louis.kitty"}) public class KittyBackupApplication { public static void main(String[] args) { SpringApplication.run(KittyBackupApplication.class, args); } }
新增配置
建立專案配置檔案,新增備份還原資料來源配置。
resources/application.yml
# backup datasource spring: backup: datasource: host: localhost userName: root password: 123456 database: kitty
新增配置屬性讀取配置類。
BackupDataSourceProperties.java
package com.louis.kitty.backup.datasource;import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; @Component @ConfigurationProperties(prefix = "spring.backup.datasource") public class BackupDataSourceProperties { private String host; private String userName; private String password; private String database; public String getHost() { return host; } public void setHost(String host) { this.host = host; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getDatabase() { return database; } public void setDatabase(String database) { this.database = database; } }
新增swagger配置類,用於測試備份還原介面。
package com.louis.kitty.backup.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import springfox.documentation.builders.PathSelectors; import springfox.documentation.builders.RequestHandlerSelectors; import springfox.documentation.spi.DocumentationType; import springfox.documentation.spring.web.plugins.Docket; import springfox.documentation.swagger2.annotations.EnableSwagger2; @Configuration @EnableSwagger2 public class SwaggerConfig { @Bean public Docket createRestApi() { return new Docket(DocumentationType.SWAGGER_2).select() .apis(RequestHandlerSelectors.any()).paths(PathSelectors.any()).build(); } }
新增跨域配置類,因為前後端分離,跨域肯定是要支援的。
package com.louis.kitty.backup.config; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") // 允許跨域訪問的路徑 .allowedOrigins("*") // 允許跨域訪問的源 .allowedMethods("POST", "GET", "PUT", "OPTIONS", "DELETE") // 允許請求方法 .maxAge(168000) // 預檢間隔時間 .allowedHeaders("*") // 允許頭部設定 .allowCredentials(true); // 是否傳送cookie } }
備份還原邏輯
備份還原邏輯封裝在一個工具類中,可以單獨從專案中提取出來,方便複用。
內部main方法提供簡單實用示例,可以方便快速的知曉實用方法。
備份還原功能主要是藉助命令列執行MySql的資料備份還原命令實現的。
package com.louis.kitty.backup.util; import java.io.File; import java.io.IOException; /** * MySQL備份還原工具類 * @author Louis * @date Sep 21, 2018 */ public class MySqlBackupRestoreUtils { /** * 備份資料庫 * @param host host地址,可以是本機也可以是遠端 * @param userName 資料庫的使用者名稱 * @param password 資料庫的密碼 * @param savePath 備份的路徑 * @param fileName 備份的檔名 * @param databaseName 需要備份的資料庫的名稱 * @return * @throws IOException */ public static boolean backup(String host, String userName, String password, String backupFolderPath, String fileName, String database) throws Exception { File backupFolderFile = new File(backupFolderPath); if (!backupFolderFile.exists()) { // 如果目錄不存在則建立 backupFolderFile.mkdirs(); } if (!backupFolderPath.endsWith(File.separator) || !backupFolderPath.endsWith("/")) { backupFolderPath = backupFolderPath + File.separator; } // 拼接命令列的命令 String backupFilePath = backupFolderPath + fileName; StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append("mysqldump --opt ").append(" --add-drop-database ").append(" --add-drop-table "); stringBuilder.append(" -h").append(host).append(" -u").append(userName).append(" -p").append(password); stringBuilder.append(" --result-file=").append(backupFilePath).append(" --default-character-set=utf8 ").append(database); // 呼叫外部執行 exe 檔案的 Java API Process process = Runtime.getRuntime().exec(getCommand(stringBuilder.toString())); if (process.waitFor() == 0) { // 0 表示執行緒正常終止 System.out.println("資料已經備份到 " +backupFilePath + " 檔案中"); return true; } return false; } /** * 還原資料庫 * @param restoreFilePath 資料庫備份的指令碼路徑 * @param host IP地址 * @param database 資料庫名稱 * @param userName 使用者名稱 * @param password 密碼 * @return */ public static boolean restore(String restoreFilePath, String host, String userName, String password, String database) throws Exception { File restoreFile = new File(restoreFilePath); if (restoreFile.isDirectory()) { for (File file : restoreFile.listFiles()) { if (file.exists() && file.getPath().endsWith(".sql")) { restoreFilePath = file.getAbsolutePath(); break; } } } StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append("mysql -h").append(host).append(" -u").append(userName).append(" -p").append(password); stringBuilder.append(" ").append(database).append(" < ").append(restoreFilePath); try { Process process = Runtime.getRuntime().exec(getCommand(stringBuilder.toString())); if (process.waitFor() == 0) { System.out.println("資料已從 " + restoreFilePath + " 匯入到資料庫中"); } } catch (IOException e) { e.printStackTrace(); return false; } return true; } private static String[] getCommand(String command) { String os = System.getProperty("os.name"); String shell = "/bin/sh"; if(os.toLowerCase().startsWith("win")){ shell = "cmd"; } String[] cmd = { shell, "/c", command }; return cmd; } public static void main(String[] args) throws Exception { String host = "localhost"; String userName = "root"; String password = "123456"; String database = "kitty"; System.out.println("開始備份"); String backupFolderPath = "c:/dev/"; String fileName = "kitty.sql"; backup(host, userName, password, backupFolderPath, fileName, database); System.out.println("備份成功"); System.out.println("開始還原"); String restoreFilePath = "c:/dev/kitty.sql"; restore(restoreFilePath, host, userName, password, database); System.out.println("還原成功"); } }
備份還原服務
備份還原服務通過呼叫工具類實現備份還原功能。
MysqlBackupService.java
package com.louis.kitty.backup.service; import java.io.IOException; /** * MySql命令列備份恢復服務 * @author Louis * @date Sep 20, 2018 */ public interface MysqlBackupService { /** * 備份資料庫 * @param host host地址,可以是本機也可以是遠端 * @param userName 資料庫的使用者名稱 * @param password 資料庫的密碼 * @param savePath 備份的路徑 * @param fileName 備份的檔名 * @param databaseName 需要備份的資料庫的名稱 * @return * @throws IOException */ boolean backup(String host, String userName, String password, String backupFolderPath, String fileName, String database) throws Exception; /** * 恢復資料庫 * @param restoreFilePath 資料庫備份的指令碼路徑 * @param host IP地址 * @param database 資料庫名稱 * @param userName 使用者名稱 * @param password 密碼 * @return */ boolean restore(String restoreFilePath, String host, String userName, String password, String database) throws Exception; }
MysqlBackupServiceImpl.java
package com.louis.kitty.backup.service.impl; import org.springframework.stereotype.Service; import com.louis.kitty.backup.service.MysqlBackupService; import com.louis.kitty.backup.util.MySqlBackupRestoreUtils; @Service public class MysqlBackupServiceImpl implements MysqlBackupService { @Override public boolean backup(String host, String userName, String password, String backupFolderPath, String fileName, String database) throws Exception { return MySqlBackupRestoreUtils.backup(host, userName, password, backupFolderPath, fileName, database); } @Override public boolean restore(String restoreFilePath, String host, String userName, String password, String database) throws Exception { return MySqlBackupRestoreUtils.restore(restoreFilePath, host, userName, password, database); } }
備份還原介面
備份還原服務通過呼叫服務類實現備份還原的REST介面。
提供備份查詢、建立備份、版本還原、刪除備份的功能。
MySqlBackupController.java
package com.louis.kitty.backup.controller; import java.io.File; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import com.louis.kitty.backup.constants.BackupConstants; import com.louis.kitty.backup.datasource.BackupDataSourceProperties; import com.louis.kitty.backup.service.MysqlBackupService; import com.louis.kitty.backup.util.FileUtils; import com.louis.kitty.backup.util.HttpResult; /** * 系統資料備份還原 * 採用MYSQL備份還原命令 * @author Louis * @date Sep 20, 2018 */ @RestController @RequestMapping("/backup") public class MySqlBackupController { @Autowired MysqlBackupService mysqlBackupService; @Autowired BackupDataSourceProperties properties; @GetMapping("/backup") public HttpResult backup() { String host = properties.getHost(); String userName = properties.getUserName(); String password = properties.getPassword(); String database = properties.getDatabase(); String backupFodlerName = BackupConstants.DEFAULT_BACKUP_NAME+ "_" + (new SimpleDateFormat(BackupConstants.DATE_FORMAT)).format(new Date()); String backupFolderPath = BackupConstants.BACKUP_FOLDER + backupFodlerName + File.separator; String fileName = BackupConstants.BACKUP_FILE_NAME; try { mysqlBackupService.backup(host, userName, password, backupFolderPath, fileName, database); } catch (Exception e) { return HttpResult.error(500, e.getMessage()); } return HttpResult.ok(); } @GetMapping("/restore") public HttpResult restore(@RequestParam String name) { String host = properties.getHost(); String userName = properties.getUserName(); String password = properties.getPassword(); String database = properties.getDatabase(); String restoreFilePath = BackupConstants.RESTORE_FOLDER + name; try { mysqlBackupService.restore(restoreFilePath, host, userName, password, database); } catch (Exception e) { return HttpResult.error(500, e.getMessage()); } return HttpResult.ok(); } @GetMapping("/findRecords") public HttpResult findBackupRecords() { List<Map<String, Object>> backupRecords = new ArrayList<>(); File restoreFolderFile = new File(BackupConstants.RESTORE_FOLDER); if(restoreFolderFile.exists()) { for(File file:restoreFolderFile.listFiles()) { Map<String, Object> bean = new HashMap<>(); bean.put("name", file.getName()); bean.put("title", file.getName()); if(BackupConstants.DEFAULT_BACKUP_NAME.equals(file.getName())) { bean.put("title", "系統預設備份"); } backupRecords.add(bean); } } return HttpResult.ok(backupRecords); } @GetMapping("/delete") public HttpResult deleteBackupRecord(@RequestParam String name) { if(BackupConstants.DEFAULT_BACKUP_NAME.equals(name)) { return HttpResult.error("系統預設備份無法刪除!"); } String restoreFilePath = BackupConstants.RESTORE_FOLDER + name; try { FileUtils.deleteFile(new File(restoreFilePath)); } catch (Exception e) { return HttpResult.error(500, e.getMessage()); } return HttpResult.ok(); } }
介面測試
建立備份
呼叫備份介面,生成備份。
備份建立成功之後,會在_backup目錄下生成以時間戳相關的備份目錄,目錄下包含資料庫備份SQL檔案。
為防止所有備份被刪除,backup目錄下提供系統預設備份,放置系統初始化資料,通過刪除介面,不可刪除。
查詢備份
通過備份查詢介面,可以查詢所有備份記錄,顯示在前臺,用於提供備份的還原和刪除。
備份查詢介面返回如下資料結構,name為操作標識,title用於前臺顯示備份資訊。
{ "code": 200, "msg": null, "data": [ { "name": "backup", "title": "系統預設備份" }, { "name": "backup_2018-09-22_103504", "title": "backup_2018-09-22_103504" }, { "name": "backup_2018-09-22_103506", "title": "backup_2018-09-22_103506" }, { "name": "backup_2018-09-22_103508", "title": "backup_2018-09-22_103508" } ] }
刪除備份
根據查詢結果,傳入備份的name作為引數,即可呼叫刪除介面刪除備份。
還原備份
根據查詢結果,傳入備份的name作為引數,即可呼叫還原介面還原備份。
前臺測試
結合前臺頁面操作,我們可以以介面的方式查詢、建立、刪除和還原備份。
我們在前臺頁面新增備份還原操作入口,如下圖所示。
在系統備份還原操作介面,提供查詢、建立、刪除、還原備份的功能。
前臺頁面的實現參考前臺篇章教程。