一、新建工程
新建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(); } }