专栏首页朝雨忆轻尘Spring Boot + Spring Cloud 实现权限管理系统 后端篇(十三):系统备份还原

Spring Boot + Spring Cloud 实现权限管理系统 后端篇(十三):系统备份还原

系统备份还原

在很多时候,我们需要系统数据进行备份还原。我们这里就使用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作为参数,即可调用还原接口还原备份。

前台测试

结合前台页面操作,我们可以以界面的方式查询、创建、删除和还原备份。

我们在前台页面添加备份还原操作入口,如下图所示。

在系统备份还原操作界面,提供查询、创建、删除、还原备份的功能。

前台页面的实现参考前台篇章教程。

规则调整

由于我们用的是Spring Boot,在实际部署的时候,很可能采用的是jar包部署方式,那么我们原先定的以类路径来定位读写备份目录和文件的方式受到了限制,所以这里对备份目录存放的位置重新制定。新的存放路径规则:System.getProperty("user.home") + /backup_时间戳/kitty.sql ,user.home 在windows对应C:\Users\xxxx\,在Linux上对应/root/。

注意:

此版本数据库备份还原功能依赖于服务器本地MySQL的备份还原工具,所以部署服务器必须先安装MySQL数据库。

且要保证在命令行能够成功执行MySQL的备份还原数据库命令,这是系统数据备份还原功能的前提条件。

如果是在Linux环境,且执行MySQL相关命令报出以下错误,可参考下面解决方案:

ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/tmp/mysql.sock' (2)

这是因为本地MySQL工具一本都是通过Socket方式连接数据库的,出现以上错误是没有为相应工具指定Socker文件。

可以通过修改MySQL的 my.cnf 配置文件,在其中指定客户端工具的socket文件 , 默认是 /var/lib/mysql/mysql.sock。

my.cnf 添加如下内容:

[client]
socket=/var/lib/mysql.sock

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Spring Security Oauth2 单点登录案例实现和执行流程剖析

    OAuth是一个关于授权的开放网络标准,在全世界得到的广泛的应用,目前是2.0的版本。OAuth2在“客户端”与“服务提供商”之间,设置了一个授权层(autho...

    朝雨忆轻尘
  • Java第三方支付接入案例(支付宝)

    到蚂蚁金服注册开发者账号,注册地址:https://open.alipay.com,用你的 支付宝 账号扫码登录,完善个人信息,选择服务类型。

    朝雨忆轻尘
  • Spring Security 案例实现和执行流程剖析

    Spring Security 是 Spring 社区的一个顶级项目,也是 Spring Boot 官方推荐使用的安全框架。除了常规的认证(Authentica...

    朝雨忆轻尘
  • MySQL备份恢复的自动化设计

    MySQL的备份恢复是一直想要改进的地方,其中恢复是重中之重,这部分的工作要做成平台化的工作,算是有了前期的很多铺垫和延迟,最近在和同事的共同协作下,总算有了一...

    jeanron100
  • ADX3000的组网配置

    路由规划: ? 1 组网配置 ? 2 接口信任域设置 ? 3 静态路由设置 ? 4 包过滤策略 ?

    用户1154259
  • org.apache.hadoop.mapreduce.JobContext

    在java中调用sqoop接口进行mysql和hdfs直接数据传输时,遇到以下错误: Found interface org.apache.hadoop.ma...

    闵开慧
  • linux修改max user processes limits

    说明: 这篇是网上看到的有关修改 max user processes limits,觉得还可以,就保留了下来!稍微加了点东西 具体: 最近新上了一批服务...

    明哥的运维笔记
  • 媒体控制接口(MCI)的学习与使用

    使用MCI(媒体控制接口)播放音频文件,MCI为程序员提供了两种方式访问MCI设备或文件:一种是基于消息的命令接口函数;另一种是使用字符串接口函数。两者的区别在...

    _gongluck
  • 计算机视觉算法工程师面试总结

    校招尘埃落定了,由于最近一直在忙毕业论文,现在才腾出时间总结一下面试经历,因为最近人工智能的火爆,所以今年算法岗竞争也相当激烈,投了很多公司也踩了很多坑,最后也...

    AI研习社
  • [android] 手机卫士黑名单功能(短信拦截)

    前面我们把需要拦截的手机号都存储和展示出来了,接下来是使用广播接收者拦截短信了,这个广播接收者需要和一个服务绑定,服务开启的时候,接收者存在,服务停掉时,接收者...

    陶士涵

扫码关注云+社区

领取腾讯云代金券