<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.1.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.louis</groupId> <artifactId>mango-backup</artifactId> <version>1.0.0</version> <packaging>jar</packaging> <name>mango-backup</name> <description>mango-backup</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <!-- spring boot --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <exclusion> <groupId>org.hibernate.validator</groupId> <artifactId>hibernate-validator</artifactId> </exclusion> </exclusions> </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.louis</groupId> <artifactId>mango-common</artifactId> <version>1.0.0</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency> </dependencies> <build> <resources> <resource> <directory>src/main/resources</directory> <includes> <include>**/*</include> </includes> <filtering>false</filtering> </resource> </resources> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
# tomcat
server:
port: 8002
spring:
application:
name: mango-backup
# backup datasource
mango:
backup:
datasource:
host: localhost
userName: root
password: admin
database: mango
package com.louis.mango.backup.config; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; /** * 跨域配置 * @author Louis * @date Jan 15, 2019 */ @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 } }
package com.louis.mango.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; /** * Swagger配置 * @author Louis * @date Jan 15, 2019 */ @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.mango.backup.constants; import java.io.File; /** * 常量类 * @author Louis * @date Jan 15, 2019 */ public interface BackupConstants { /** 备份目录名称 */ public static final String BACKUP_FOLDER_NAME = "_mango_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 = "mango" + 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; }
package com.louis.mango.backup.datasource; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; /** * 数据源 * @author Louis * @date Jan 15, 2019 */ @Component @ConfigurationProperties(prefix = "mango.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; } }
package com.louis.mango.backup.service; import java.io.IOException; /** * MySql命令行备份恢复服务 * @author Louis * @date Jan 15, 2019 */ 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; }
package com.louis.mango.backup.service.impl; import org.springframework.stereotype.Service; import com.louis.mango.backup.service.MysqlBackupService; import com.louis.mango.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); } }
package com.louis.mango.backup.util; /** * HTTP结果封装 * @author Louis * @date Jan 15, 2019 */ 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; } }
package com.louis.mango.backup.util; import java.io.File; import java.io.IOException; /** * MySQL备份还原工具类 * @author Louis * @date Jan 15, 2019 */ 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/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 = "admin"; String database = "mango"; System.out.println("开始备份"); String backupFolderPath = "c:/dev/"; String fileName = "mango.sql"; backup(host, userName, password, backupFolderPath, fileName, database); System.out.println("备份成功"); System.out.println("开始还原"); String restoreFilePath = "c:/dev/mango.sql"; restore(restoreFilePath, host, userName, password, database); System.out.println("还原成功"); } }
package com.louis.mango.backup.controller; import java.io.File; import java.io.IOException; 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.mango.backup.constants.BackupConstants; import com.louis.mango.backup.datasource.BackupDataSourceProperties; import com.louis.mango.backup.service.MysqlBackupService; import com.louis.mango.backup.util.HttpResult; import com.louis.mango.common.utils.FileUtils; /** * 系统数据备份还原 * @author Louis * @date Jan 15, 2019 */ @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(); } }
package com.louis.mango.common.utils; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileReader; import java.io.InputStream; import javax.servlet.http.HttpServletResponse; /** * 文件相关操作 * * @author Louis * @date Jan 14, 2019 */ public class FileUtils { /** * 下载文件 * * @param response * @param file * @param newFileName */ public static void downloadFile(HttpServletResponse response, File file, String newFileName) { try { response.setHeader("Content-Disposition", "attachment; filename=" + new String(newFileName.getBytes("ISO-8859-1"), "UTF-8")); BufferedOutputStream bos = new BufferedOutputStream(response.getOutputStream()); InputStream is = new FileInputStream(file.getAbsolutePath()); BufferedInputStream bis = new BufferedInputStream(is); int length = 0; byte[] temp = new byte[1 * 1024 * 10]; while ((length = bis.read(temp)) != -1) { bos.write(temp, 0, length); } bos.flush(); bis.close(); bos.close(); is.close(); } catch (Exception e) { e.printStackTrace(); } } /** * 递归删除文件 * * @param file */ public static void deleteFile(File file) { // 判断是否是一个目录, 不是的话跳过, 直接删除; 如果是一个目录, 先将其内容清空. if (file.isDirectory()) { // 获取子文件/目录 File[] subFiles = file.listFiles(); // 遍历该目录 for (File subFile : subFiles) { // 递归调用删除该文件: 如果这是一个空目录或文件, 一次递归就可删除. // 如果这是一个非空目录, 多次递归清空其内容后再删除 deleteFile(subFile); } } // 删除空目录或文件 file.delete(); } /** * 获取项目根路径 * * @return */ public static String getProjectPath() { String classPath = getClassPath(); return new File(classPath).getParentFile().getParentFile().getAbsolutePath(); } /** * 获取类路径 * * @return */ public static String getClassPath() { String classPath = FileUtils.class.getClassLoader().getResource("").getPath(); return classPath; } /** * 读取txt文件的内容 * * @param file 想要读取的文件路径 * @return 返回文件内容 */ public static String readFile(String file) { return readFile(new File(file)); } /** * 读取txt文件的内容 * * @param file 想要读取的文件对象 * @return 返回文件内容 */ public static String readFile(File file) { StringBuilder result = new StringBuilder(); try { BufferedReader br = new BufferedReader(new FileReader(file));// 构造一个BufferedReader类来读取文件 String s = null; while ((s = br.readLine()) != null) { // 使用readLine方法,一次读一行 result.append(System.lineSeparator() + s); } br.close(); } catch (Exception e) { e.printStackTrace(); } return result.toString(); } }
package com.louis.mango.backup; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** * 启动器 * @author Louis * @date Jan 15, 2019 */ @SpringBootApplication(scanBasePackages={"com.louis.mango"}) public class MangoBackupApplication { public static void main(String[] args) { SpringApplication.run(MangoBackupApplication.class, args); } }