一、新建工程
新建jansens-backup工程,這是一個獨立運行於admin的服務模塊,可以分開獨立部署
二、添加依賴
在pom.xml文件中添加web、swagger、common依賴包。
<dependencies> <!-- spring boot --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- swagger --> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.9.2</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.9.2</version> </dependency> <dependency> <groupId>com.read</groupId> <artifactId>jansens-common</artifactId> <version>1.0-SNAPSHOT</version> </dependency> </dependencies>
三、添加配置
application.yml
# tomcat server: port: 8002 spring: application: name: jansens-backup # backup datasource jansens: backup: datasource: host: localhost userName: root password: 123456 database: jansens
四、自定義Banner文件
在resources目錄下添加一個自定義banner.txt文件
//////////////////////////////////////////////////////////////////// // _ooOoo_ // // o8888888o // // 88" . "88 // // (| ^_^ |) // // O\ = /O // // ____/`---'\____ // // .' \\| |// `. // // / \\||| : |||// \ // // / _||||| -:- |||||- \ // // | | \\\ - /// | | // // | \_| ''\---/'' | | // // \ .-\__ `-` ___/-. / // // ___`. .' /--.--\ `. . ___ // // ."" '< `.___\_<|>_/___.' >'"". // // | | : `- \`.;`\ _ /`;.`/ - ` : | | // // \ \ `-. \_ __\ /__ _/ .-` / / // // ========`-.____`-.___\_____/___.-`____.-'======== // // `=---=' // // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ // // 佛祖保佑 上海研發 永無BUG // ////////////////////////////////////////////////////////////////////
五、修改啟動類
修改啟動類為JansensBackupApplication,指定掃描路徑為com.louis.jansens
JansensBackupApplication.java
@SpringBootApplication(scanBasePackages={"com.louis.jansens"}) @EnableSwagger2 public class JansensBackupApplication { public static void main(String[] args) { SpringApplication.run(JansensBackupApplication.class, args); } }
六、跨域配置
在config包添加跨域配置類
CorsConfig.java
@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 } }
七、Swagger配置
在config包添加swagger配置類
SwaggerConfig.java
/** * Swagger配置 */ @Configuration @EnableSwagger2 public class SwaggerConfig { @Bean public Docket createRestApi() { return new Docket(DocumentationType.SWAGGER_2).select() .apis(RequestHandlerSelectors.any()).paths(PathSelectors.any()).build(); } }
八、數據源屬性
BackupDataSourceProperties.java
/** * 數據源 */ @Component @ConfigurationProperties(prefix = "jansens.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; } }
九、備份還原接口
在service包下添加MysqlBackupService接口
MysqlBackupService.java
/** * MySql命令行備份恢復服務 */ 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; }
十、備份還原實現
在service.impl包下添加MysqlBackupServiceImpl實現類
MysqlBackupServiceImpl.java
@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); } }
十一、備份還原邏輯
在util包下創建一個備份還原工具類
MySqlBackupRestoreUtils.java
/** * MySQL備份還原工具類 */ 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(".sql").append(" --default-character-set=utf8 ").append(database); System.out.println(stringBuilder.toString()); // 調用外部執行 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/bash"; String c = "-c"; if(os.toLowerCase().startsWith("win")){ shell = "cmd"; c = "/c"; } 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 = "jansens"; System.out.println("開始備份"); String backupFolderPath = "d:/dev/"; String fileName = "mdh"; backup(host, userName, password, backupFolderPath, fileName, database); System.out.println("備份成功"); /*System.out.println("開始還原"); String restoreFilePath = "d:/dev/mdh.sql"; restore(restoreFilePath, host, userName, password, database); System.out.println("還原成功");*/ } }
在util包HTTP結果封裝類
HttpResult.java
/** * HTTP結果封裝 */ public class HttpResult { private int code = 200; private String msg; private Object data; public static HttpResult error() { return error(500, "未知異常,請聯系管理員"); } public static HttpResult error(String msg) { return error(500, msg); } public static HttpResult error(int code, String msg) { HttpResult r = new HttpResult(); r.setCode(code); r.setMsg(msg); return r; } public static HttpResult ok(String msg) { HttpResult r = new HttpResult(); r.setMsg(msg); return r; } public static HttpResult ok(Object data) { HttpResult r = new HttpResult(); r.setData(data); return r; } public static HttpResult ok() { return new HttpResult(); } public int getCode() { return code; } public void setCode(int code) { this.code = code; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } public Object getData() { return data; } public void setData(Object data) { this.data = data; } }
在constants包新建一個BackupConstants常量類
/** * 常量類 */ public interface BackupConstants { /** 備份目錄名稱 */ public static final String BACKUP_FOLDER_NAME = "_jansens_backup"; /** 備份目錄 */ public static final String BACKUP_FOLDER = System.getProperty("user.home") + File.separator + BACKUP_FOLDER_NAME + File.separator; /** 還原目錄,默認就是備份目錄 */ public static final String RESTORE_FOLDER = BACKUP_FOLDER; /** 日期格式 */ public static final String DATE_FORMAT = "yyyy-MM-dd_HHmmss"; /** SQL拓展名 */ public static final String SQL_EXT = ".sql"; /** 默認備份文件名 */ public static final String BACKUP_FILE_NAME = "jansens" + SQL_EXT; /** 默認備份還原目錄名稱 */ public static final String DEFAULT_BACKUP_NAME = "backup"; /** 默認備份還原文件 */ public static final String DEFAULT_RESTORE_FILE = BACKUP_FOLDER + DEFAULT_BACKUP_NAME + File.separator + BACKUP_FILE_NAME; }
十二、備份還原控制器
在controller包新建MySqlBackupController控制器
MySqlBackupController.java
/** * 系統數據備份還原 */ @RestController @RequestMapping("/backup") public class MySqlBackupController { @Autowired MysqlBackupService mysqlBackupService; @Autowired BackupDataSourceProperties properties; @GetMapping("/backup") public HttpResult backup() { String backupFodlerName = BackupConstants.DEFAULT_BACKUP_NAME + "_" + (new SimpleDateFormat(BackupConstants.DATE_FORMAT)).format(new Date()); return backup(backupFodlerName); } private HttpResult backup(String backupFodlerName) { String host = properties.getHost(); String userName = properties.getUserName(); String password = properties.getPassword(); String database = properties.getDatabase(); String backupFolderPath = BackupConstants.BACKUP_FOLDER + backupFodlerName + File.separator; String fileName = BackupConstants.BACKUP_FILE_NAME; try { boolean success = mysqlBackupService.backup(host, userName, password, backupFolderPath, fileName, database); if(!success) { HttpResult.error("數據備份失敗"); } } catch (Exception e) { return HttpResult.error(500, e.getMessage()); } return HttpResult.ok(); } @GetMapping("/restore") public HttpResult restore(@RequestParam String name) throws IOException { 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() { if(!new File(BackupConstants.DEFAULT_RESTORE_FILE).exists()) { // 初始默認備份文件 backup(BackupConstants.DEFAULT_BACKUP_NAME); } List<Map<String, String>> backupRecords = new ArrayList<>(); File restoreFolderFile = new File(BackupConstants.RESTORE_FOLDER); if(restoreFolderFile.exists()) { for(File file:restoreFolderFile.listFiles()) { Map<String, String> backup = new HashMap<>(); backup.put("name", file.getName()); backup.put("title", file.getName()); if(BackupConstants.DEFAULT_BACKUP_NAME.equalsIgnoreCase(file.getName())) { backup.put("title", "系統默認備份"); } backupRecords.add(backup); } } // 排序,默認備份最前,然后按時間戳排序,新備份在前面 backupRecords.sort((o1, o2) -> BackupConstants.DEFAULT_BACKUP_NAME.equalsIgnoreCase(o1.get("name")) ? -1 : BackupConstants.DEFAULT_BACKUP_NAME.equalsIgnoreCase(o2.get("name")) ? 1 : o2.get("name").compareTo(o1.get("name"))); 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.BACKUP_FOLDER + name; try { FileUtils.deleteFile(new File(restoreFilePath)); } catch (Exception e) { return HttpResult.error(500, e.getMessage()); } return HttpResult.ok(); } }